pipewire/
proxy.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use libc::{c_char, c_void};
5use std::fmt;
6use std::mem;
7use std::pin::Pin;
8use std::{ffi::CStr, ptr};
9
10use crate::{types::ObjectType, Error};
11
12pub struct Proxy {
13    ptr: ptr::NonNull<pw_sys::pw_proxy>,
14}
15
16// Wrapper around a proxy pointer
17impl Proxy {
18    pub(crate) fn new(ptr: ptr::NonNull<pw_sys::pw_proxy>) -> Self {
19        Proxy { ptr }
20    }
21
22    pub(crate) fn as_ptr(&self) -> *mut pw_sys::pw_proxy {
23        self.ptr.as_ptr()
24    }
25
26    pub fn add_listener_local(&self) -> ProxyListenerLocalBuilder<'_> {
27        ProxyListenerLocalBuilder {
28            proxy: self,
29            cbs: ListenerLocalCallbacks::default(),
30        }
31    }
32
33    pub fn id(&self) -> u32 {
34        unsafe { pw_sys::pw_proxy_get_id(self.as_ptr()) }
35    }
36
37    /// Get the type of the proxy as well as it's version.
38    pub fn get_type(&self) -> (ObjectType, u32) {
39        unsafe {
40            let mut version = 0;
41            let proxy_type = pw_sys::pw_proxy_get_type(self.as_ptr(), &mut version);
42            let proxy_type = CStr::from_ptr(proxy_type);
43
44            (
45                ObjectType::from_str(proxy_type.to_str().expect("invalid proxy type")),
46                version,
47            )
48        }
49    }
50
51    /// Attempt to downcast the proxy to the provided type.
52    ///
53    /// The downcast will fail if the type that the proxy represents does not match the provided type. \
54    /// In that case, the function returns `(self, Error::WrongProxyType)` so that the proxy is not lost.
55    pub(crate) fn downcast<P: ProxyT>(self) -> Result<P, (Self, Error)> {
56        // Make sure the proxy we got has the type that is requested
57        if P::type_() == self.get_type().0 {
58            unsafe { Ok(P::from_proxy_unchecked(self)) }
59        } else {
60            Err((self, Error::WrongProxyType))
61        }
62    }
63}
64
65impl Drop for Proxy {
66    fn drop(&mut self) {
67        unsafe {
68            pw_sys::pw_proxy_destroy(self.as_ptr());
69        }
70    }
71}
72
73impl fmt::Debug for Proxy {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        let (proxy_type, version) = self.get_type();
76
77        f.debug_struct("Proxy")
78            .field("id", &self.id())
79            .field("type", &proxy_type)
80            .field("version", &version)
81            .finish()
82    }
83}
84
85// Trait implemented by high level proxy wrappers
86pub trait ProxyT {
87    // Add Sized restriction on those methods so it can be used as a
88    // trait object, see E0038
89    fn type_() -> ObjectType
90    where
91        Self: Sized;
92
93    fn upcast(self) -> Proxy;
94    fn upcast_ref(&self) -> &Proxy;
95
96    /// Downcast the provided proxy to `Self` without checking that the type matches.
97    ///
98    /// This function should not be used by applications.
99    /// If you really do need a way to downcast a proxy to it's type, please open an issue.
100    ///
101    /// # Safety
102    /// It must be manually ensured that the provided proxy is actually a proxy representing the created type. \
103    /// Otherwise, undefined behaviour may occur.
104    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
105    where
106        Self: Sized;
107}
108
109// Trait implemented by listener on high level proxy wrappers.
110pub trait Listener {}
111
112pub struct ProxyListener {
113    // Need to stay allocated while the listener is registered
114    #[allow(dead_code)]
115    events: Pin<Box<pw_sys::pw_proxy_events>>,
116    listener: Pin<Box<spa_sys::spa_hook>>,
117    #[allow(dead_code)]
118    data: Box<ListenerLocalCallbacks>,
119}
120
121impl Listener for ProxyListener {}
122
123impl Drop for ProxyListener {
124    fn drop(&mut self) {
125        spa::utils::hook::remove(*self.listener);
126    }
127}
128#[derive(Default)]
129struct ListenerLocalCallbacks {
130    destroy: Option<Box<dyn Fn()>>,
131    bound: Option<Box<dyn Fn(u32)>>,
132    removed: Option<Box<dyn Fn()>>,
133    done: Option<Box<dyn Fn(i32)>>,
134    #[allow(clippy::type_complexity)]
135    error: Option<Box<dyn Fn(i32, i32, &str)>>, // TODO: return a proper Error enum?
136}
137
138pub struct ProxyListenerLocalBuilder<'a> {
139    proxy: &'a Proxy,
140    cbs: ListenerLocalCallbacks,
141}
142
143impl<'a> ProxyListenerLocalBuilder<'a> {
144    #[must_use]
145    pub fn destroy<F>(mut self, destroy: F) -> Self
146    where
147        F: Fn() + 'static,
148    {
149        self.cbs.destroy = Some(Box::new(destroy));
150        self
151    }
152
153    #[must_use]
154    pub fn bound<F>(mut self, bound: F) -> Self
155    where
156        F: Fn(u32) + 'static,
157    {
158        self.cbs.bound = Some(Box::new(bound));
159        self
160    }
161
162    #[must_use]
163    pub fn removed<F>(mut self, removed: F) -> Self
164    where
165        F: Fn() + 'static,
166    {
167        self.cbs.removed = Some(Box::new(removed));
168        self
169    }
170
171    #[must_use]
172    pub fn done<F>(mut self, done: F) -> Self
173    where
174        F: Fn(i32) + 'static,
175    {
176        self.cbs.done = Some(Box::new(done));
177        self
178    }
179
180    #[must_use]
181    pub fn error<F>(mut self, error: F) -> Self
182    where
183        F: Fn(i32, i32, &str) + 'static,
184    {
185        self.cbs.error = Some(Box::new(error));
186        self
187    }
188
189    #[must_use]
190    pub fn register(self) -> ProxyListener {
191        unsafe extern "C" fn proxy_destroy(data: *mut c_void) {
192            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
193            callbacks.destroy.as_ref().unwrap()();
194        }
195
196        unsafe extern "C" fn proxy_bound(data: *mut c_void, global_id: u32) {
197            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
198            callbacks.bound.as_ref().unwrap()(global_id);
199        }
200
201        unsafe extern "C" fn proxy_removed(data: *mut c_void) {
202            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
203            callbacks.removed.as_ref().unwrap()();
204        }
205
206        unsafe extern "C" fn proxy_done(data: *mut c_void, seq: i32) {
207            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
208            callbacks.done.as_ref().unwrap()(seq);
209        }
210
211        unsafe extern "C" fn proxy_error(
212            data: *mut c_void,
213            seq: i32,
214            res: i32,
215            message: *const c_char,
216        ) {
217            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
218            let message = CStr::from_ptr(message).to_str().unwrap();
219            callbacks.error.as_ref().unwrap()(seq, res, message);
220        }
221
222        let e = unsafe {
223            let mut e: Pin<Box<pw_sys::pw_proxy_events>> = Box::pin(mem::zeroed());
224            e.version = pw_sys::PW_VERSION_PROXY_EVENTS;
225
226            if self.cbs.destroy.is_some() {
227                e.destroy = Some(proxy_destroy);
228            }
229
230            if self.cbs.bound.is_some() {
231                e.bound = Some(proxy_bound);
232            }
233
234            if self.cbs.removed.is_some() {
235                e.removed = Some(proxy_removed);
236            }
237
238            if self.cbs.done.is_some() {
239                e.done = Some(proxy_done);
240            }
241
242            if self.cbs.error.is_some() {
243                e.error = Some(proxy_error);
244            }
245
246            e
247        };
248
249        let (listener, data) = unsafe {
250            let proxy = &self.proxy.as_ptr();
251
252            let data = Box::into_raw(Box::new(self.cbs));
253            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
254            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
255            let funcs: *const pw_sys::pw_proxy_events = e.as_ref().get_ref();
256
257            pw_sys::pw_proxy_add_listener(
258                proxy.cast(),
259                listener_ptr.cast(),
260                funcs.cast(),
261                data as *mut _,
262            );
263
264            (listener, Box::from_raw(data))
265        };
266
267        ProxyListener {
268            events: e,
269            listener,
270            data,
271        }
272    }
273}