Skip to main content

pipewire_native/proxy/
mod.rs

1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: Copyright (c) 2025 Asymptotic Inc.
3// SPDX-FileCopyrightText: Copyright (c) 2025 Arun Raghavan
4
5use std::any::Any;
6use std::sync::{Arc, Mutex, RwLock};
7
8use pipewire_native_spa as spa;
9
10use crate::HookId;
11use crate::{new_refcounted, properties::Properties, refcounted, Refcounted};
12
13use crate::{types::ObjectType, Id};
14
15/// A proxy representing client objects.
16pub mod client;
17/// A proxy representing device objects.
18pub mod device;
19/// A proxy for representing factory objects.
20pub mod factory;
21/// A proxy representing a link between two ports.
22pub mod link;
23/// A proxy for global metadata objects.
24pub mod metadata;
25/// A proxy representing modules loaded in the server.
26pub mod module;
27/// A proxy representing nodes.
28pub mod node;
29/// A proxy representing ports on nodes.
30pub mod port;
31/// A proxy representing the profiler.
32pub mod profiler;
33/// A proxy representing the registry.
34pub mod registry;
35
36refcounted! {
37    /// Proxies are a central concept to how clients interact with a PipeWire server. The server
38    /// has a list of objects (either instantiated locally, or by other clients). A number of these
39    /// objects are exported to other clients to enumerate, interact with (call _methods_ on) and
40    /// be notified when they change (via _events_).
41    ///
42    /// Clients discover exported objects using a [Registry](registry::Registry) (itself also a
43    /// proxy), which can be created using [Core::registry()](super::core::Core::registry()). The
44    /// [RegistryEvents::global](registry::RegistryEvents::global) event is triggered for each
45    /// existing exported object (and subsequenty when new objects are created).
46    ///
47    /// If an object is of interest, clients can "bind" to that object using
48    /// [Registry::bind()](registry::Registry::bind()). This provides an object on which methods
49    /// may be called, and  event notifications may be received.
50    ///
51    /// The object itself will be a specific type (such as [Client](client::Client), but will also
52    /// have an associated [Proxy\<T\>](Proxy) type (in this example, [Proxy\<Client\>](Proxy)).
53    /// The specific type provides methods and events that are specific to the object itself. In
54    /// addition, the [Proxy\<T\>](Proxy) type provides more general proxy-related methods and
55    /// events.
56    ///
57    /// Specific types implement the [HasProxy] trait, which allows maintaining a generic
58    /// collection of these objects, while having the ability to downcast to the specific type. The
59    /// [HasProxy::proxy()] method can be used to get the corresponding [Proxy] object.
60    ///
61    /// Note that there are two IDs associated with a proxy. One is a "local" ID, which represents
62    /// the client's view of the object. The other is a "global" ID, which is the server's view of
63    /// the object. Due to the asynchronous nature of the protocol, it is possible that the client
64    /// has a view of server objects that is not current. Global IDs might be reused, and this
65    /// mechanism allows the server to keep track of what each client's view of the server state is
66    /// and avoid calling methods on objects that have gone away.
67    pub struct Proxy<T: HasProxy + Refcounted> {
68        object: T::WeakRef,
69        id: Id,
70        bound_id: RwLock<Option<Id>>,
71        hooks: Arc<Mutex<spa::hook::HookList<ProxyEvents>>>,
72    }
73}
74
75/// Events that might be emitted by a proxy.
76#[allow(clippy::type_complexity)]
77#[derive(Default)]
78pub struct ProxyEvents {
79    /// The proxy is about to be destroyed (either because it went away, or because we are
80    /// disconnecting).
81    pub destroy: Option<Box<dyn FnMut() + Send>>,
82    /// The proxy was bound to.
83    pub bound: Option<Box<dyn FnMut(Id) + Send>>,
84    /// The proxy was removed (either on the server-side, or we are disconnecting).
85    pub removed: Option<Box<dyn FnMut() + Send>>,
86    /// An asynchronous operation on the proxy was completed.
87    pub done: Option<Box<dyn FnMut(u32) + Send>>,
88    /// An error occured on the object.
89    pub error: Option<Box<dyn FnMut(u32, u32, &str) + Send>>,
90    /// The proxy was bound to (supercedes [Self::bound]).
91    pub bound_props: Option<Box<dyn FnMut(u32, &Properties) + Send>>,
92}
93
94impl<T: HasProxy + Refcounted> Proxy<T> {
95    pub(crate) fn new(id: Id, object: &T) -> Self {
96        Self {
97            inner: new_refcounted(InnerProxy::<T>::new(id, object.downgrade())),
98        }
99    }
100
101    /// The "local" ID for this object.
102    pub fn id(&self) -> Id {
103        self.inner.id
104    }
105
106    /// Retrieves the specific object corresponding to this [Proxy]. Because the proxy holds a weak
107    /// reference to the object, the returned value is an [Option].
108    pub fn object(&self) -> Option<T> {
109        Refcounted::upgrade(&self.inner.object)
110    }
111
112    /// The "global" ID for this object.
113    pub fn bound_id(&self) -> Option<Id> {
114        *self.inner.bound_id.read().unwrap()
115    }
116
117    pub(crate) fn set_bound_id(&self, id: Id) {
118        *self.inner.bound_id.write().unwrap() = Some(id);
119        spa::emit_hook!(self.inner.hooks, bound, id);
120    }
121
122    pub(crate) fn set_bound_props(&self, id: Id, props: &Properties) {
123        *self.inner.bound_id.write().unwrap() = Some(id);
124        spa::emit_hook!(self.inner.hooks, bound_props, id, props);
125    }
126
127    /// Register a listener for proxy events.
128    pub fn add_listener(&self, events: ProxyEvents) -> HookId {
129        self.inner.hooks.lock().unwrap().append(events)
130    }
131
132    /// Remove a set of event listeners.
133    pub fn remove_listener(&self, hook_id: HookId) {
134        self.inner.hooks.lock().unwrap().remove(hook_id);
135    }
136
137    pub(crate) fn events(&self) -> Arc<Mutex<spa::hook::HookList<ProxyEvents>>> {
138        self.inner.hooks.clone()
139    }
140}
141
142impl<T: HasProxy + Refcounted> InnerProxy<T> {
143    fn new(id: Id, object: T::WeakRef) -> Self {
144        Self {
145            object,
146            id,
147            bound_id: RwLock::new(None),
148            hooks: spa::hook::HookList::new(),
149        }
150    }
151}
152
153/// This trait is implemented by all specific types of proxies. See the [Proxy] documentation for
154/// more details.
155pub trait HasProxy: Any + Send + Sync {
156    // See the invoke! and notify! macros below
157    // type Methods;
158    // type Events;
159
160    /// The interface type of the proxy object.
161    fn type_(&self) -> ObjectType;
162
163    /// The interface version of the proxy object.
164    fn version(&self) -> u32;
165
166    /// Get a [Proxy\<T\>](Proxy) for this object.
167    fn proxy(&self) -> Proxy<Self>
168    where
169        Self: Refcounted;
170}
171
172impl dyn HasProxy {
173    /// Downcast from a `dyn HasProxy` to the specific type.
174    pub fn downcast<T: HasProxy + Refcounted>(&self) -> Option<T> {
175        (self as &dyn Any).downcast_ref::<T>().cloned()
176    }
177
178    /// Downcast from a `dyn HasProxy` to the corresponding [Proxy] type.
179    pub fn downcast_proxy<T: HasProxy + Refcounted>(&self) -> Option<Proxy<T>> {
180        (self as &dyn Any).downcast_ref::<T>().map(|o| o.proxy())
181    }
182}
183
184// We expect each proxy object to have a set of associated methods (which can be invoked on the
185// object) and/or events (which notify listeners via hooks). Unfortunately, expressing this via the
186// type system makes things complicated. Notably, the `proxies` list on `Core` can no longer be a
187// container of `dyn HasProxy`, as the associated types all need to be specified.
188//
189// As a compromise, we provide these two macros that assume types that implement `HasProxy` also
190// implement either or both functions, methods() and events(). These return a struct of their
191// respective types, on which an invocation or notification can be triggered.
192
193#[doc(hidden)]
194#[macro_export]
195macro_rules! proxy_object_invoke {
196    ($proxy:ident, $method:ident $(, $($args:tt)*)?) => {
197        ($proxy.object().unwrap().methods().lock().unwrap().$method)(&$proxy $(, $($args)*)?)
198    };
199}
200
201#[doc(hidden)]
202#[macro_export]
203macro_rules! proxy_object_notify {
204    ($proxy:ident, $event:ident $(, $($args:tt)*)?) => {
205        if let Some(_object) = $proxy.object() {
206            spa::emit_hook!(_object.events(), $event $(, $($args)*)?);
207        }
208    };
209}
210
211// To go from an object in dyn HasProxy form to the actual proxy itself, we need to do some dyn Any
212// shenanigans, so let's hide that away in a macro as well.
213#[doc(hidden)]
214#[macro_export]
215macro_rules! hasproxy_method_call_internal {
216    ($object:expr, $unlock:block, $method:ident $(, $($args:tt),*)?) => {
217        {
218            if $object.type_() == $crate::types::interface::CORE {
219                let _proxy = $object.downcast_proxy::<$crate::core::Core>().unwrap();
220                $unlock
221                _proxy.$method($($($args),*)?)
222            } else if $object.type_() == $crate::types::interface::CLIENT {
223                let _proxy = $object.downcast_proxy::<$crate::proxy::client::Client>().unwrap();
224                $unlock
225                _proxy.$method($($($args),*)?)
226            } else if $object.type_() == $crate::types::interface::DEVICE {
227                let _proxy = $object.downcast_proxy::<$crate::proxy::device::Device>().unwrap();
228                $unlock
229                _proxy.$method($($($args),*)?)
230            } else if $object.type_() == $crate::types::interface::FACTORY {
231                let _proxy = $object.downcast_proxy::<$crate::proxy::factory::Factory>().unwrap();
232                $unlock
233                _proxy.$method($($($args),*)?)
234            } else if $object.type_() == $crate::types::interface::LINK {
235                let _proxy = $object.downcast_proxy::<$crate::proxy::link::Link>().unwrap();
236                $unlock
237                _proxy.$method($($($args),*)?)
238            } else if $object.type_() == $crate::types::interface::METADATA {
239                let _proxy = $object.downcast_proxy::<$crate::proxy::metadata::Metadata>().unwrap();
240                $unlock
241                _proxy.$method($($($args),*)?)
242            } else if $object.type_() == $crate::types::interface::MODULE {
243                let _proxy = $object.downcast_proxy::<$crate::proxy::module::Module>().unwrap();
244                $unlock
245                _proxy.$method($($($args),*)?)
246            } else if $object.type_() == $crate::types::interface::NODE {
247                let _proxy = $object.downcast_proxy::<$crate::proxy::node::Node>().unwrap();
248                $unlock
249                _proxy.$method($($($args),*)?)
250            } else if $object.type_() == $crate::types::interface::PORT {
251                let _proxy = $object.downcast_proxy::<$crate::proxy::port::Port>().unwrap();
252                $unlock
253                _proxy.$method($($($args),*)?)
254            } else if $object.type_() == $crate::types::interface::PROFILER {
255                let _proxy = $object.downcast_proxy::<$crate::proxy::profiler::Profiler>().unwrap();
256                $unlock
257                _proxy.$method($($($args),*)?)
258            } else if $object.type_() == $crate::types::interface::REGISTRY {
259                let _proxy = $object.downcast_proxy::<$crate::proxy::registry::Registry>().unwrap();
260                $unlock
261                _proxy.$method($($($args),*)?)
262            } else {
263                unreachable!("got unexpected proxy type {}", $object.type_())
264            }
265        }
266    };
267}
268
269#[doc(hidden)]
270#[macro_export]
271macro_rules! hasproxy_method_call {
272    ($object:expr, $method:ident $(, $($args:tt),*)?) => {
273        $crate::hasproxy_method_call_internal!($object, {}, $method $(, $($args),*)?)
274    };
275}
276
277#[doc(hidden)]
278#[macro_export]
279macro_rules! hasproxy_method_call_unlocked {
280    ($object:expr, $lock: ident, $method:ident $(, $($args:tt),*)?) => {
281        $crate::hasproxy_method_call_internal!($object, { drop($lock); }, $method $(, $($args),*)?)
282    };
283}
284
285#[doc(hidden)]
286#[macro_export]
287macro_rules! hasproxy_notify_internal {
288    ($object:ident, $unlock:block, $event:ident $(, $($args:tt),*)?) => {
289        if $object.type_() == $crate::types::interface::CORE {
290            let _proxy = $object.downcast_proxy::<$crate::core::Core>().unwrap();
291            $unlock
292            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
293        } else if $object.type_() == $crate::types::interface::CLIENT {
294            let _proxy = $object.downcast_proxy::<$crate::proxy::client::Client>().unwrap();
295            $unlock
296            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
297        } else if $object.type_() == $crate::types::interface::DEVICE {
298            let _proxy = $object.downcast_proxy::<$crate::proxy::device::Device>().unwrap();
299            $unlock
300            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
301        } else if $object.type_() == $crate::types::interface::FACTORY {
302            let _proxy = $object.downcast_proxy::<$crate::proxy::factory::Factory>().unwrap();
303            $unlock
304            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
305        } else if $object.type_() == $crate::types::interface::LINK {
306            let _proxy = $object.downcast_proxy::<$crate::proxy::link::Link>().unwrap();
307            $unlock
308            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
309        } else if $object.type_() == $crate::types::interface::METADATA {
310            let _proxy = $object.downcast_proxy::<$crate::proxy::metadata::Metadata>().unwrap();
311            $unlock
312            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
313        } else if $object.type_() == $crate::types::interface::MODULE {
314            let _proxy = $object.downcast_proxy::<$crate::proxy::module::Module>().unwrap();
315            $unlock
316            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
317        } else if $object.type_() == $crate::types::interface::NODE {
318            let _proxy = $object.downcast_proxy::<$crate::proxy::node::Node>().unwrap();
319            $unlock
320            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
321        } else if $object.type_() == $crate::types::interface::PORT {
322            let _proxy = $object.downcast_proxy::<$crate::proxy::port::Port>().unwrap();
323            $unlock
324            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
325        } else if $object.type_() == $crate::types::interface::PROFILER {
326            let _proxy = $object.downcast_proxy::<$crate::proxy::profiler::Profiler>().unwrap();
327            $unlock
328            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
329        } else if $object.type_() == $crate::types::interface::REGISTRY {
330            let _proxy = $object.downcast_proxy::<$crate::proxy::registry::Registry>().unwrap();
331            $unlock
332            spa::emit_hook!(_proxy.events(), $event $(, $($args),*)?)
333        } else {
334            unreachable!("got unexpected proxy type {}", $object.type_())
335        }
336    };
337}
338
339#[doc(hidden)]
340#[macro_export]
341macro_rules! hasproxy_notify {
342    ($object:ident, $event:ident $(, $($args:tt),*)?) => {
343        $crate::hasproxy_notify_internal!($object, {}, $event $(, $($args),*)?)
344    };
345}
346
347#[doc(hidden)]
348#[macro_export]
349macro_rules! hasproxy_notify_unlocked {
350    ($object:ident, $lock:ident, $event:ident $(, $($args:tt),*)?) => {
351        $crate::hasproxy_notify_internal!($object, { drop($lock); }, $event $(, $($args),*)?)
352    };
353}
354
355#[doc(hidden)]
356#[macro_export]
357macro_rules! proxy_notify {
358    ($object:ident, $event:ident $(, $($args:tt),*)?) => {
359        spa::emit_hook!($object.proxy().events(), $event $(, $($args),*)?)
360    };
361}