pipewire/
client.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Clients represent an open connection of a client process with the server.
5//!
6//! This module contains wrappers for [`pw_client`](pw_sys::pw_client) and related items.
7
8use bitflags::bitflags;
9use libc::c_void;
10use std::ops::Deref;
11use std::pin::Pin;
12use std::{
13    ffi::{CStr, CString},
14    ptr,
15};
16use std::{fmt, mem};
17
18use crate::{
19    permissions::Permission,
20    proxy::{Listener, Proxy, ProxyT},
21    types::ObjectType,
22};
23use spa::spa_interface_call_method;
24
25/// A [proxy][Proxy] to a [client](self).
26#[derive(Debug)]
27pub struct Client {
28    proxy: Proxy,
29}
30
31impl ProxyT for Client {
32    fn type_() -> ObjectType {
33        ObjectType::Client
34    }
35
36    fn upcast(self) -> Proxy {
37        self.proxy
38    }
39
40    fn upcast_ref(&self) -> &Proxy {
41        &self.proxy
42    }
43
44    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
45    where
46        Self: Sized,
47    {
48        Self { proxy }
49    }
50}
51
52impl Client {
53    // TODO: add non-local version when we'll bind pw_thread_loop_start()
54    #[must_use = "Use the builder to register event callbacks"]
55    pub fn add_listener_local(&self) -> ClientListenerLocalBuilder<'_> {
56        ClientListenerLocalBuilder {
57            client: self,
58            cbs: ListenerLocalCallbacks::default(),
59        }
60    }
61
62    /// Send an error to the client.
63    ///
64    /// # Permissions
65    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
66    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
67    ///
68    /// # Parameters
69    /// `id`: The global id to report the error on  
70    /// `res`: An errno style error code
71    /// `message`: An error string
72    pub fn error(&self, id: u32, res: i32, message: &str) {
73        let message = CString::new(message).expect("Null byte in message parameter");
74        let message_cstr = message.as_c_str();
75        Client::error_cstr(self, id, res, message_cstr)
76    }
77
78    /// Send an error to the client.
79    ///
80    /// # Permissions
81    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
82    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
83    ///
84    /// # Parameters
85    /// `id`: The global id to report the error on  
86    /// `res`: An errno style error code
87    /// `message`: An error string
88    pub fn error_cstr(&self, id: u32, res: i32, message: &CStr) {
89        unsafe {
90            spa_interface_call_method!(
91                self.proxy.as_ptr(),
92                pw_sys::pw_client_methods,
93                error,
94                id,
95                res,
96                message.as_ptr() as *const _
97            );
98        };
99    }
100
101    /// Update the client's properties.
102    ///
103    /// # Permissions
104    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
105    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
106    pub fn update_properties(&self, properties: &spa::utils::dict::DictRef) {
107        unsafe {
108            spa_interface_call_method!(
109                self.proxy.as_ptr(),
110                pw_sys::pw_client_methods,
111                update_properties,
112                properties.as_raw_ptr()
113            );
114        }
115    }
116
117    /// Get the client's permissions.
118    ///
119    /// A [`permissions`](ClientListenerLocalBuilder::permissions) event will be emitted.
120    ///
121    /// # Permissions
122    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
123    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
124    ///
125    /// # Parameters
126    /// `index`: The first index to query, `0` for first  
127    /// `num`: The maximum number of items to get
128    pub fn get_permissions(&self, index: u32, num: u32) {
129        unsafe {
130            spa_interface_call_method!(
131                self.proxy.as_ptr(),
132                pw_sys::pw_client_methods,
133                get_permissions,
134                index,
135                num
136            );
137        }
138    }
139
140    /// Manage the permissions of the global objects for this client.
141    ///
142    /// Update the permissions of the global objects using the provided slice.
143    ///
144    /// Globals can use the default permissions or can have specific permissions assigned to them.
145    ///
146    /// # Permissions
147    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
148    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
149    pub fn update_permissions(&self, permissions: &[Permission]) {
150        unsafe {
151            spa_interface_call_method!(
152                self.proxy.as_ptr(),
153                pw_sys::pw_client_methods,
154                update_permissions,
155                permissions.len() as u32,
156                permissions.as_ptr().cast()
157            );
158        }
159    }
160}
161
162#[derive(Default)]
163struct ListenerLocalCallbacks {
164    #[allow(clippy::type_complexity)]
165    info: Option<Box<dyn Fn(&ClientInfoRef)>>,
166    #[allow(clippy::type_complexity)]
167    permissions: Option<Box<dyn Fn(u32, &[Permission])>>,
168}
169
170/// A builder for registering client event callbacks.
171///
172/// Use [`Client::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
173/// After adding callbacks, use [`register`](Self::register) to get back a [`ClientListener`].
174///
175/// # Examples
176/// ```
177/// # use pipewire::client::Client;
178/// # fn example(client: Client) {
179/// let client_listener = client.add_listener_local()
180///     .info(|info| println!("New client info: {info:?}"))
181///     .permissions(|index, permissions| {
182///         println!("New client permissions: index {index}, permissions {permissions:?}");
183///     })
184///     .register();
185/// # }
186/// ```
187pub struct ClientListenerLocalBuilder<'a> {
188    client: &'a Client,
189    cbs: ListenerLocalCallbacks,
190}
191
192#[repr(transparent)]
193pub struct ClientInfoRef(pw_sys::pw_client_info);
194
195impl ClientInfoRef {
196    pub fn as_raw(&self) -> &pw_sys::pw_client_info {
197        &self.0
198    }
199
200    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_client_info {
201        std::ptr::addr_of!(self.0).cast_mut()
202    }
203
204    pub fn id(&self) -> u32 {
205        self.0.id
206    }
207
208    pub fn change_mask(&self) -> ClientChangeMask {
209        ClientChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask")
210    }
211
212    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
213        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
214        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
215    }
216}
217
218impl fmt::Debug for ClientInfoRef {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        f.debug_struct("ClientInfoRef")
221            .field("id", &self.id())
222            .field("change-mask", &self.change_mask())
223            .field("props", &self.props())
224            .finish()
225    }
226}
227
228pub struct ClientInfo {
229    ptr: ptr::NonNull<pw_sys::pw_client_info>,
230}
231
232impl ClientInfo {
233    pub fn new(ptr: ptr::NonNull<pw_sys::pw_client_info>) -> Self {
234        Self { ptr }
235    }
236
237    pub fn from_raw(raw: *mut pw_sys::pw_client_info) -> Self {
238        Self {
239            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
240        }
241    }
242
243    pub fn into_raw(self) -> *mut pw_sys::pw_client_info {
244        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
245    }
246}
247
248impl Drop for ClientInfo {
249    fn drop(&mut self) {
250        unsafe { pw_sys::pw_client_info_free(self.ptr.as_ptr()) }
251    }
252}
253
254impl std::ops::Deref for ClientInfo {
255    type Target = ClientInfoRef;
256
257    fn deref(&self) -> &Self::Target {
258        unsafe { self.ptr.cast::<ClientInfoRef>().as_ref() }
259    }
260}
261
262impl AsRef<ClientInfoRef> for ClientInfo {
263    fn as_ref(&self) -> &ClientInfoRef {
264        self.deref()
265    }
266}
267
268bitflags! {
269    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
270    pub struct ClientChangeMask: u64 {
271        const PROPS = pw_sys::PW_CLIENT_CHANGE_MASK_PROPS as u64;
272    }
273}
274
275impl fmt::Debug for ClientInfo {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        f.debug_struct("ClientInfo")
278            .field("id", &self.id())
279            .field("change-mask", &self.change_mask())
280            .field("props", &self.props())
281            .finish()
282    }
283}
284
285/// An owned listener for client events.
286///
287/// This is created by [`ClientListenerLocalBuilder`] and will receive events as long as it is alive.
288/// When this gets dropped, the listener gets unregistered and no events will be received by it.
289#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
290pub struct ClientListener {
291    // Need to stay allocated while the listener is registered
292    #[allow(dead_code)]
293    events: Pin<Box<pw_sys::pw_client_events>>,
294    listener: Pin<Box<spa_sys::spa_hook>>,
295    #[allow(dead_code)]
296    data: Box<ListenerLocalCallbacks>,
297}
298
299impl Listener for ClientListener {}
300
301impl Drop for ClientListener {
302    fn drop(&mut self) {
303        spa::utils::hook::remove(*self.listener);
304    }
305}
306
307impl<'a> ClientListenerLocalBuilder<'a> {
308    /// Set the client `info` event callback of the listener.
309    ///
310    /// # Callback parameters
311    /// `info`: Info about the client
312    /// # Examples
313    /// ```
314    /// # use pipewire::client::Client;
315    /// # fn example(client: Client) {
316    /// let client_listener = client.add_listener_local()
317    ///     .info(|info| println!("New client info: {info:?}"))
318    ///     .register();
319    /// # }
320    /// ```
321    #[must_use = "Call `.register()` to start receiving events"]
322    pub fn info<F>(mut self, info: F) -> Self
323    where
324        F: Fn(&ClientInfoRef) + 'static,
325    {
326        self.cbs.info = Some(Box::new(info));
327        self
328    }
329
330    /// Set the client `permissions` event callback of the listener.
331    ///
332    /// This event is emitted as a result of [`get_permissions`](Client::get_permissions).
333    ///
334    /// # Callback parameters
335    /// `index`: Index of the first permission entry  
336    /// `permissions`: The permissions
337    ///
338    /// # Examples
339    /// ```
340    /// # use pipewire::client::Client;
341    /// # fn example(client: Client) {
342    /// let client_listener = client.add_listener_local()
343    ///     .permissions(|index, permissions| {
344    ///         println!("New client permissions: index {index}, permissions {permissions:?}");
345    ///     })
346    ///     .register();
347    /// # }
348    /// ```
349    #[must_use = "Call `.register()` to start receiving events"]
350    pub fn permissions<F>(mut self, permissions: F) -> Self
351    where
352        F: Fn(u32, &[Permission]) + 'static,
353    {
354        self.cbs.permissions = Some(Box::new(permissions));
355        self
356    }
357
358    /// Subscribe to events and register any provided callbacks.
359    pub fn register(self) -> ClientListener {
360        unsafe extern "C" fn client_events_info(
361            data: *mut c_void,
362            info: *const pw_sys::pw_client_info,
363        ) {
364            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
365            let info =
366                ptr::NonNull::new(info as *mut pw_sys::pw_client_info).expect("info is NULL");
367            let info = info.cast::<ClientInfoRef>().as_ref();
368            callbacks.info.as_ref().unwrap()(info);
369        }
370
371        unsafe extern "C" fn client_events_permissions(
372            data: *mut c_void,
373            index: u32,
374            n_permissions: u32,
375            permissions: *const pw_sys::pw_permission,
376        ) {
377            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
378            let permissions =
379                std::slice::from_raw_parts(permissions.cast(), n_permissions as usize);
380
381            callbacks.permissions.as_ref().unwrap()(index, permissions);
382        }
383
384        let e = unsafe {
385            let mut e: Pin<Box<pw_sys::pw_client_events>> = Box::pin(mem::zeroed());
386            e.version = pw_sys::PW_VERSION_CLIENT_EVENTS;
387
388            if self.cbs.info.is_some() {
389                e.info = Some(client_events_info);
390            }
391            if self.cbs.permissions.is_some() {
392                e.permissions = Some(client_events_permissions);
393            }
394
395            e
396        };
397
398        let (listener, data) = unsafe {
399            let client = &self.client.proxy.as_ptr();
400
401            let data = Box::into_raw(Box::new(self.cbs));
402            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
403            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
404
405            spa_interface_call_method!(
406                client,
407                pw_sys::pw_client_methods,
408                add_listener,
409                listener_ptr.cast(),
410                e.as_ref().get_ref(),
411                data as *mut _
412            );
413
414            (listener, Box::from_raw(data))
415        };
416
417        ClientListener {
418            events: e,
419            listener,
420            data,
421        }
422    }
423}