| 1 | //! Utilities for binding globals with [`wl_registry`] in delegates. |
| 2 | //! |
| 3 | //! This module is based around the [`RegistryHandler`] trait and [`RegistryState`]. |
| 4 | //! |
| 5 | //! [`RegistryState`] provides an interface to bind globals regularly, creating an object with each new |
| 6 | //! instantiation or caching bound globals to prevent duplicate object instances from being created. Binding |
| 7 | //! a global regularly is accomplished through [`RegistryState::bind_one`]. |
| 8 | //! |
| 9 | //! The [`delegate_registry`](crate::delegate_registry) macro is used to implement handling for [`wl_registry`]. |
| 10 | //! |
| 11 | //! ## Sample implementation of [`RegistryHandler`] |
| 12 | //! |
| 13 | //! ``` |
| 14 | //! use smithay_client_toolkit::reexports::client::{ |
| 15 | //! Connection, Dispatch, QueueHandle, |
| 16 | //! delegate_dispatch, |
| 17 | //! globals::GlobalList, |
| 18 | //! protocol::wl_output, |
| 19 | //! }; |
| 20 | //! |
| 21 | //! use smithay_client_toolkit::registry::{ |
| 22 | //! GlobalProxy, ProvidesRegistryState, RegistryHandler, RegistryState, |
| 23 | //! }; |
| 24 | //! |
| 25 | //! struct ExampleApp { |
| 26 | //! /// The registry state is needed to use the global abstractions. |
| 27 | //! registry_state: RegistryState, |
| 28 | //! /// This is a type we want to delegate global handling to. |
| 29 | //! delegate_that_wants_registry: Delegate, |
| 30 | //! } |
| 31 | //! |
| 32 | //! /// The delegate a global should be provided to. |
| 33 | //! struct Delegate { |
| 34 | //! outputs: Vec<wl_output::WlOutput>, |
| 35 | //! } |
| 36 | //! |
| 37 | //! // When implementing RegistryHandler, you must be able to dispatch any type you could bind using the registry state. |
| 38 | //! impl<D> RegistryHandler<D> for Delegate |
| 39 | //! where |
| 40 | //! // In order to bind a global, you must statically assert the global may be handled with the data type. |
| 41 | //! D: Dispatch<wl_output::WlOutput, ()> |
| 42 | //! // ProvidesRegistryState provides a function to access the RegistryState within the impl. |
| 43 | //! + ProvidesRegistryState |
| 44 | //! // We need some way to access our part of the application's state. This uses AsMut, |
| 45 | //! // but you may prefer to create your own trait to avoid making .as_mut() ambiguous. |
| 46 | //! + AsMut<Delegate> |
| 47 | //! + 'static, |
| 48 | //! { |
| 49 | //! /// New global added after initial enumeration. |
| 50 | //! fn new_global( |
| 51 | //! data: &mut D, |
| 52 | //! conn: &Connection, |
| 53 | //! qh: &QueueHandle<D>, |
| 54 | //! name: u32, |
| 55 | //! interface: &str, |
| 56 | //! version: u32, |
| 57 | //! ) { |
| 58 | //! if interface == "wl_output" { |
| 59 | //! // Bind `wl_output` with newest version from 1 to 4 the compositor supports |
| 60 | //! let output = data.registry().bind_specific(qh, name, 1..=4, ()).unwrap(); |
| 61 | //! data.as_mut().outputs.push(output); |
| 62 | //! } |
| 63 | //! |
| 64 | //! // You could either handle errors here or when attempting to use the interface. Most |
| 65 | //! // Wayland protocols are optional, so if your application can function without a |
| 66 | //! // protocol it should try to do so; the From impl of GlobalProxy is written to make |
| 67 | //! // this straightforward. |
| 68 | //! } |
| 69 | //! } |
| 70 | //! ``` |
| 71 | |
| 72 | use crate::{error::GlobalError, globals::ProvidesBoundGlobal}; |
| 73 | use wayland_client::{ |
| 74 | globals::{BindError, Global, GlobalList, GlobalListContents}, |
| 75 | protocol::wl_registry, |
| 76 | Connection, Dispatch, Proxy, QueueHandle, |
| 77 | }; |
| 78 | |
| 79 | /// A trait implemented by modular parts of a smithay's client toolkit and protocol delegates that may be used |
| 80 | /// to receive notification of a global being created or destroyed. |
| 81 | /// |
| 82 | /// Delegates that choose to implement this trait may be used in [`registry_handlers`] which |
| 83 | /// automatically notifies delegates about the creation and destruction of globals. |
| 84 | /// |
| 85 | /// [`registry_handlers`]: crate::registry_handlers |
| 86 | /// |
| 87 | /// Note that in order to delegate registry handling to a type which implements this trait, your `D` data type |
| 88 | /// must implement [`ProvidesRegistryState`]. |
| 89 | pub trait RegistryHandler<D> |
| 90 | where |
| 91 | D: ProvidesRegistryState, |
| 92 | { |
| 93 | /// Called when a new global has been advertised by the compositor. |
| 94 | /// |
| 95 | /// The provided registry handle may be used to bind the global. This is not called during |
| 96 | /// initial enumeration of globals. It is primarily useful for multi-instance globals such as |
| 97 | /// `wl_output` and `wl_seat`. |
| 98 | /// |
| 99 | /// The default implementation does nothing. |
| 100 | fn new_global( |
| 101 | data: &mut D, |
| 102 | conn: &Connection, |
| 103 | qh: &QueueHandle<D>, |
| 104 | name: u32, |
| 105 | interface: &str, |
| 106 | version: u32, |
| 107 | ) { |
| 108 | let _ = (data, conn, qh, name, interface, version); |
| 109 | } |
| 110 | |
| 111 | /// Called when a global has been destroyed by the compositor. |
| 112 | /// |
| 113 | /// The default implementation does nothing. |
| 114 | fn remove_global( |
| 115 | data: &mut D, |
| 116 | conn: &Connection, |
| 117 | qh: &QueueHandle<D>, |
| 118 | name: u32, |
| 119 | interface: &str, |
| 120 | ) { |
| 121 | let _ = (data, conn, qh, name, interface); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Trait which asserts a data type may provide a mutable reference to the registry state. |
| 126 | /// |
| 127 | /// Typically this trait will be required by delegates or [`RegistryHandler`] implementations which need |
| 128 | /// to access the registry utilities provided by Smithay's client toolkit. |
| 129 | pub trait ProvidesRegistryState: Sized { |
| 130 | /// Returns a mutable reference to the registry state. |
| 131 | fn registry(&mut self) -> &mut RegistryState; |
| 132 | |
| 133 | /// Called when a new global has been advertised by the compositor. |
| 134 | /// |
| 135 | /// This is not called during initial global enumeration. |
| 136 | fn runtime_add_global( |
| 137 | &mut self, |
| 138 | conn: &Connection, |
| 139 | qh: &QueueHandle<Self>, |
| 140 | name: u32, |
| 141 | interface: &str, |
| 142 | version: u32, |
| 143 | ); |
| 144 | |
| 145 | /// Called when a global has been destroyed by the compositor. |
| 146 | fn runtime_remove_global( |
| 147 | &mut self, |
| 148 | conn: &Connection, |
| 149 | qh: &QueueHandle<Self>, |
| 150 | name: u32, |
| 151 | interface: &str, |
| 152 | ); |
| 153 | } |
| 154 | |
| 155 | /// State object associated with the registry handling for smithay's client toolkit. |
| 156 | /// |
| 157 | /// This object provides utilities to cache bound globals that are needed by multiple modules. |
| 158 | #[derive (Debug)] |
| 159 | pub struct RegistryState { |
| 160 | registry: wl_registry::WlRegistry, |
| 161 | globals: Vec<Global>, |
| 162 | } |
| 163 | |
| 164 | impl RegistryState { |
| 165 | /// Creates a new registry handle. |
| 166 | /// |
| 167 | /// This type may be used to bind globals as they are advertised. |
| 168 | pub fn new(global_list: &GlobalList) -> Self { |
| 169 | let registry = global_list.registry().clone(); |
| 170 | let globals = global_list.contents().clone_list(); |
| 171 | |
| 172 | RegistryState { registry, globals } |
| 173 | } |
| 174 | |
| 175 | pub fn registry(&self) -> &wl_registry::WlRegistry { |
| 176 | &self.registry |
| 177 | } |
| 178 | |
| 179 | /// Returns an iterator over all globals. |
| 180 | /// |
| 181 | /// This list may change if the compositor adds or removes globals after initial |
| 182 | /// enumeration. |
| 183 | /// |
| 184 | /// No guarantees are provided about the ordering of the globals in this iterator. |
| 185 | pub fn globals(&self) -> impl Iterator<Item = &Global> + '_ { |
| 186 | self.globals.iter() |
| 187 | } |
| 188 | |
| 189 | /// Returns an iterator over all globals implementing the given interface. |
| 190 | /// |
| 191 | /// This may be more efficient than searching [Self::globals]. |
| 192 | pub fn globals_by_interface<'a>( |
| 193 | &'a self, |
| 194 | interface: &'a str, |
| 195 | ) -> impl Iterator<Item = &Global> + 'a { |
| 196 | self.globals.iter().filter(move |g| g.interface == interface) |
| 197 | } |
| 198 | |
| 199 | /// Binds a global, returning a new object associated with the global. |
| 200 | /// |
| 201 | /// This should not be used to bind globals that have multiple instances such as `wl_output`; |
| 202 | /// use [Self::bind_all] instead. |
| 203 | pub fn bind_one<I, D, U>( |
| 204 | &self, |
| 205 | qh: &QueueHandle<D>, |
| 206 | version: std::ops::RangeInclusive<u32>, |
| 207 | udata: U, |
| 208 | ) -> Result<I, BindError> |
| 209 | where |
| 210 | D: Dispatch<I, U> + 'static, |
| 211 | I: Proxy + 'static, |
| 212 | U: Send + Sync + 'static, |
| 213 | { |
| 214 | bind_one(&self.registry, &self.globals, qh, version, udata) |
| 215 | } |
| 216 | |
| 217 | /// Binds a global, returning a new object associated with the global. |
| 218 | /// |
| 219 | /// This binds a specific object by its name as provided by the [RegistryHandler::new_global] |
| 220 | /// callback. |
| 221 | pub fn bind_specific<I, D, U>( |
| 222 | &self, |
| 223 | qh: &QueueHandle<D>, |
| 224 | name: u32, |
| 225 | version: std::ops::RangeInclusive<u32>, |
| 226 | udata: U, |
| 227 | ) -> Result<I, BindError> |
| 228 | where |
| 229 | D: Dispatch<I, U> + 'static, |
| 230 | I: Proxy + 'static, |
| 231 | U: Send + Sync + 'static, |
| 232 | { |
| 233 | let iface = I::interface(); |
| 234 | if *version.end() > iface.version { |
| 235 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
| 236 | panic!("Maximum version ( {}) was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
| 237 | version.end(), iface.version); |
| 238 | } |
| 239 | // Optimize for runtime_add_global which will use the last entry |
| 240 | for global in self.globals.iter().rev() { |
| 241 | if global.name != name || global.interface != iface.name { |
| 242 | continue; |
| 243 | } |
| 244 | if global.version < *version.start() { |
| 245 | return Err(BindError::UnsupportedVersion); |
| 246 | } |
| 247 | let version = global.version.min(*version.end()); |
| 248 | let proxy = self.registry.bind(global.name, version, qh, udata); |
| 249 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
| 250 | |
| 251 | return Ok(proxy); |
| 252 | } |
| 253 | Err(BindError::NotPresent) |
| 254 | } |
| 255 | |
| 256 | /// Binds all globals with a given interface. |
| 257 | pub fn bind_all<I, D, U, F>( |
| 258 | &self, |
| 259 | qh: &QueueHandle<D>, |
| 260 | version: std::ops::RangeInclusive<u32>, |
| 261 | make_udata: F, |
| 262 | ) -> Result<Vec<I>, BindError> |
| 263 | where |
| 264 | D: Dispatch<I, U> + 'static, |
| 265 | I: Proxy + 'static, |
| 266 | F: FnMut(u32) -> U, |
| 267 | U: Send + Sync + 'static, |
| 268 | { |
| 269 | bind_all(&self.registry, &self.globals, qh, version, make_udata) |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | /// Delegates the handling of [`wl_registry`]. |
| 274 | /// |
| 275 | /// Anything which implements [`RegistryHandler`] may be used in the delegate. |
| 276 | /// |
| 277 | /// ## Usage |
| 278 | /// |
| 279 | /// ``` |
| 280 | /// use smithay_client_toolkit::{ |
| 281 | /// delegate_registry, delegate_shm, registry_handlers, |
| 282 | /// shm::{ShmHandler, Shm}, |
| 283 | /// }; |
| 284 | /// |
| 285 | /// struct ExampleApp { |
| 286 | /// shm_state: Shm, |
| 287 | /// } |
| 288 | /// |
| 289 | /// // Here is the implementation of wl_shm to compile: |
| 290 | /// delegate_shm!(ExampleApp); |
| 291 | /// |
| 292 | /// impl ShmHandler for ExampleApp { |
| 293 | /// fn shm_state(&mut self) -> &mut Shm { |
| 294 | /// &mut self.shm_state |
| 295 | /// } |
| 296 | /// } |
| 297 | /// ``` |
| 298 | #[macro_export ] |
| 299 | macro_rules! delegate_registry { |
| 300 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { |
| 301 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
| 302 | [ |
| 303 | $crate::reexports::client::protocol::wl_registry::WlRegistry: $crate::reexports::client::globals::GlobalListContents |
| 304 | ] => $crate::registry::RegistryState |
| 305 | ); |
| 306 | }; |
| 307 | } |
| 308 | |
| 309 | impl<D> Dispatch<wl_registry::WlRegistry, GlobalListContents, D> for RegistryState |
| 310 | where |
| 311 | D: Dispatch<wl_registry::WlRegistry, GlobalListContents> + ProvidesRegistryState, |
| 312 | { |
| 313 | fn event( |
| 314 | state: &mut D, |
| 315 | _: &wl_registry::WlRegistry, |
| 316 | event: wl_registry::Event, |
| 317 | _: &GlobalListContents, |
| 318 | conn: &Connection, |
| 319 | qh: &QueueHandle<D>, |
| 320 | ) { |
| 321 | match event { |
| 322 | wl_registry::Event::Global { name, interface, version } => { |
| 323 | let iface = interface.clone(); |
| 324 | state.registry().globals.push(Global { name, interface, version }); |
| 325 | state.runtime_add_global(conn, qh, name, &iface, version); |
| 326 | } |
| 327 | |
| 328 | wl_registry::Event::GlobalRemove { name } => { |
| 329 | if let Some(i) = state.registry().globals.iter().position(|g| g.name == name) { |
| 330 | let global = state.registry().globals.swap_remove(i); |
| 331 | state.runtime_remove_global(conn, qh, name, &global.interface); |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | _ => unreachable!("wl_registry is frozen" ), |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | /// A helper for storing a bound global. |
| 341 | /// |
| 342 | /// This helper is intended to simplify the implementation of [RegistryHandler] for state objects |
| 343 | /// that cache a bound global. |
| 344 | #[derive (Debug)] |
| 345 | pub enum GlobalProxy<I> { |
| 346 | /// The requested global was not present after a complete enumeration. |
| 347 | NotPresent, |
| 348 | /// The cached global. |
| 349 | Bound(I), |
| 350 | } |
| 351 | |
| 352 | impl<I> From<Result<I, BindError>> for GlobalProxy<I> { |
| 353 | fn from(r: Result<I, BindError>) -> Self { |
| 354 | match r { |
| 355 | Ok(proxy: I) => GlobalProxy::Bound(proxy), |
| 356 | Err(_) => GlobalProxy::NotPresent, |
| 357 | } |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | impl<I: Proxy> GlobalProxy<I> { |
| 362 | pub fn get(&self) -> Result<&I, GlobalError> { |
| 363 | self.with_min_version(0) |
| 364 | } |
| 365 | |
| 366 | pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { |
| 367 | match self { |
| 368 | GlobalProxy::Bound(proxy: &I) => { |
| 369 | if proxy.version() < min_version { |
| 370 | Err(GlobalError::InvalidVersion { |
| 371 | name: I::interface().name, |
| 372 | required: min_version, |
| 373 | available: proxy.version(), |
| 374 | }) |
| 375 | } else { |
| 376 | Ok(proxy) |
| 377 | } |
| 378 | } |
| 379 | GlobalProxy::NotPresent => Err(GlobalError::MissingGlobal(I::interface().name)), |
| 380 | } |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | #[derive (Debug)] |
| 385 | pub struct SimpleGlobal<I, const MAX_VERSION: u32> { |
| 386 | proxy: GlobalProxy<I>, |
| 387 | } |
| 388 | |
| 389 | impl<I: Proxy + 'static, const MAX_VERSION: u32> SimpleGlobal<I, MAX_VERSION> { |
| 390 | pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError> |
| 391 | where |
| 392 | State: Dispatch<I, (), State> + 'static, |
| 393 | { |
| 394 | let proxy: I = globals.bind(qh, version:0..=MAX_VERSION, ())?; |
| 395 | Ok(Self { proxy: GlobalProxy::Bound(proxy) }) |
| 396 | } |
| 397 | |
| 398 | pub fn get(&self) -> Result<&I, GlobalError> { |
| 399 | self.proxy.get() |
| 400 | } |
| 401 | |
| 402 | pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { |
| 403 | self.proxy.with_min_version(min_version) |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | impl<I: Proxy + Clone, const MAX_VERSION: u32> ProvidesBoundGlobal<I, MAX_VERSION> |
| 408 | for SimpleGlobal<I, MAX_VERSION> |
| 409 | { |
| 410 | fn bound_global(&self) -> Result<I, GlobalError> { |
| 411 | self.proxy.get().cloned() |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | impl<D, I, const MAX_VERSION: u32> Dispatch<I, (), D> for SimpleGlobal<I, MAX_VERSION> |
| 416 | where |
| 417 | D: Dispatch<I, ()>, |
| 418 | I: Proxy, |
| 419 | { |
| 420 | fn event(_: &mut D, _: &I, _: <I as Proxy>::Event, _: &(), _: &Connection, _: &QueueHandle<D>) { |
| 421 | unreachable!("SimpleGlobal is not suitable for {} which has events" , I::interface().name); |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | /// Binds all globals with a given interface. |
| 426 | pub(crate) fn bind_all<I, D, U, F>( |
| 427 | registry: &wl_registry::WlRegistry, |
| 428 | globals: &[Global], |
| 429 | qh: &QueueHandle<D>, |
| 430 | version: std::ops::RangeInclusive<u32>, |
| 431 | mut make_udata: F, |
| 432 | ) -> Result<Vec<I>, BindError> |
| 433 | where |
| 434 | D: Dispatch<I, U> + 'static, |
| 435 | I: Proxy + 'static, |
| 436 | F: FnMut(u32) -> U, |
| 437 | U: Send + Sync + 'static, |
| 438 | { |
| 439 | let iface: &'static Interface = I::interface(); |
| 440 | if *version.end() > iface.version { |
| 441 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
| 442 | panic!("Maximum version ( {}) was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
| 443 | version.end(), iface.version); |
| 444 | } |
| 445 | let mut rv: Vec = Vec::new(); |
| 446 | for global: &Global in globals { |
| 447 | if global.interface != iface.name { |
| 448 | continue; |
| 449 | } |
| 450 | if global.version < *version.start() { |
| 451 | return Err(BindError::UnsupportedVersion); |
| 452 | } |
| 453 | let version: u32 = global.version.min(*version.end()); |
| 454 | let udata: U = make_udata(global.name); |
| 455 | let proxy: I = registry.bind(global.name, version, qh, udata); |
| 456 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
| 457 | |
| 458 | rv.push(proxy); |
| 459 | } |
| 460 | Ok(rv) |
| 461 | } |
| 462 | |
| 463 | /// Binds a global, returning a new object associated with the global. |
| 464 | pub(crate) fn bind_one<I, D, U>( |
| 465 | registry: &wl_registry::WlRegistry, |
| 466 | globals: &[Global], |
| 467 | qh: &QueueHandle<D>, |
| 468 | version: std::ops::RangeInclusive<u32>, |
| 469 | udata: U, |
| 470 | ) -> Result<I, BindError> |
| 471 | where |
| 472 | D: Dispatch<I, U> + 'static, |
| 473 | I: Proxy + 'static, |
| 474 | U: Send + Sync + 'static, |
| 475 | { |
| 476 | let iface = I::interface(); |
| 477 | if *version.end() > iface.version { |
| 478 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
| 479 | panic!("Maximum version ( {}) of {} was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
| 480 | version.end(), iface.name, iface.version); |
| 481 | } |
| 482 | if *version.end() < iface.version { |
| 483 | // This is a reminder to evaluate the new API and bump the maximum in order to be able |
| 484 | // to use new APIs. Actual use of new APIs still needs runtime version checks. |
| 485 | log::trace!(target: "sctk" , "Version {} of {} is available; binding is currently limited to {}" , iface.version, iface.name, version.end()); |
| 486 | } |
| 487 | for global in globals { |
| 488 | if global.interface != iface.name { |
| 489 | continue; |
| 490 | } |
| 491 | if global.version < *version.start() { |
| 492 | return Err(BindError::UnsupportedVersion); |
| 493 | } |
| 494 | let version = global.version.min(*version.end()); |
| 495 | let proxy = registry.bind(global.name, version, qh, udata); |
| 496 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
| 497 | |
| 498 | return Ok(proxy); |
| 499 | } |
| 500 | Err(BindError::NotPresent) |
| 501 | } |
| 502 | |
| 503 | #[macro_export ] |
| 504 | macro_rules! delegate_simple { |
| 505 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty:ty, $iface:ty, $max:expr) => { |
| 506 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $iface: () ] |
| 507 | => $crate::registry::SimpleGlobal<$iface, $max> |
| 508 | ); |
| 509 | }; |
| 510 | } |
| 511 | |
| 512 | /// A helper macro for implementing [`ProvidesRegistryState`]. |
| 513 | /// |
| 514 | /// See [`delegate_registry`] for an example. |
| 515 | #[macro_export ] |
| 516 | macro_rules! registry_handlers { |
| 517 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $($ty:ty),* $(,)?) => { |
| 518 | fn runtime_add_global( |
| 519 | &mut self, |
| 520 | conn: &$crate::reexports::client::Connection, |
| 521 | qh: &$crate::reexports::client::QueueHandle<Self>, |
| 522 | name: u32, |
| 523 | interface: &str, |
| 524 | version: u32, |
| 525 | ) { |
| 526 | $( |
| 527 | <$ty as $crate::registry::RegistryHandler<Self>>::new_global(self, conn, qh, name, interface, version); |
| 528 | )* |
| 529 | } |
| 530 | |
| 531 | fn runtime_remove_global( |
| 532 | &mut self, |
| 533 | conn: &$crate::reexports::client::Connection, |
| 534 | qh: &$crate::reexports::client::QueueHandle<Self>, |
| 535 | name: u32, |
| 536 | interface: &str, |
| 537 | ) { |
| 538 | $( |
| 539 | <$ty as $crate::registry::RegistryHandler<Self>>::remove_global(self, conn, qh, name, interface); |
| 540 | )* |
| 541 | } |
| 542 | } |
| 543 | } |
| 544 | |