pipewire/
client.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use bitflags::bitflags;
5use libc::c_void;
6use std::ops::Deref;
7use std::pin::Pin;
8use std::{
9    ffi::{CStr, CString},
10    ptr,
11};
12use std::{fmt, mem};
13
14use crate::{
15    permissions::Permission,
16    proxy::{Listener, Proxy, ProxyT},
17    types::ObjectType,
18};
19use spa::spa_interface_call_method;
20
21#[derive(Debug)]
22pub struct Client {
23    proxy: Proxy,
24}
25
26impl ProxyT for Client {
27    fn type_() -> ObjectType {
28        ObjectType::Client
29    }
30
31    fn upcast(self) -> Proxy {
32        self.proxy
33    }
34
35    fn upcast_ref(&self) -> &Proxy {
36        &self.proxy
37    }
38
39    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
40    where
41        Self: Sized,
42    {
43        Self { proxy }
44    }
45}
46
47impl Client {
48    // TODO: add non-local version when we'll bind pw_thread_loop_start()
49    #[must_use]
50    pub fn add_listener_local(&self) -> ClientListenerLocalBuilder<'_> {
51        ClientListenerLocalBuilder {
52            client: self,
53            cbs: ListenerLocalCallbacks::default(),
54        }
55    }
56
57    pub fn error(&self, id: u32, res: i32, message: &str) {
58        let message = CString::new(message).expect("Null byte in message parameter");
59        let message_cstr = message.as_c_str();
60        Client::error_cstr(self, id, res, message_cstr)
61    }
62
63    pub fn error_cstr(&self, id: u32, res: i32, message: &CStr) {
64        unsafe {
65            spa_interface_call_method!(
66                self.proxy.as_ptr(),
67                pw_sys::pw_client_methods,
68                error,
69                id,
70                res,
71                message.as_ptr() as *const _
72            );
73        };
74    }
75
76    pub fn update_properties(&self, properties: &spa::utils::dict::DictRef) {
77        unsafe {
78            spa_interface_call_method!(
79                self.proxy.as_ptr(),
80                pw_sys::pw_client_methods,
81                update_properties,
82                properties.as_raw_ptr()
83            );
84        }
85    }
86
87    pub fn get_permissions(&self, index: u32, num: u32) {
88        unsafe {
89            spa_interface_call_method!(
90                self.proxy.as_ptr(),
91                pw_sys::pw_client_methods,
92                get_permissions,
93                index,
94                num
95            );
96        }
97    }
98
99    pub fn update_permissions(&self, permissions: &[Permission]) {
100        unsafe {
101            spa_interface_call_method!(
102                self.proxy.as_ptr(),
103                pw_sys::pw_client_methods,
104                update_permissions,
105                permissions.len() as u32,
106                permissions.as_ptr().cast()
107            );
108        }
109    }
110}
111
112#[derive(Default)]
113struct ListenerLocalCallbacks {
114    #[allow(clippy::type_complexity)]
115    info: Option<Box<dyn Fn(&ClientInfoRef)>>,
116    #[allow(clippy::type_complexity)]
117    permissions: Option<Box<dyn Fn(u32, &[Permission])>>,
118}
119
120pub struct ClientListenerLocalBuilder<'a> {
121    client: &'a Client,
122    cbs: ListenerLocalCallbacks,
123}
124
125#[repr(transparent)]
126pub struct ClientInfoRef(pw_sys::pw_client_info);
127
128impl ClientInfoRef {
129    pub fn as_raw(&self) -> &pw_sys::pw_client_info {
130        &self.0
131    }
132
133    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_client_info {
134        std::ptr::addr_of!(self.0).cast_mut()
135    }
136
137    pub fn id(&self) -> u32 {
138        self.0.id
139    }
140
141    pub fn change_mask(&self) -> ClientChangeMask {
142        ClientChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask")
143    }
144
145    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
146        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
147        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
148    }
149}
150
151impl fmt::Debug for ClientInfoRef {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.debug_struct("ClientInfoRef")
154            .field("id", &self.id())
155            .field("change-mask", &self.change_mask())
156            .field("props", &self.props())
157            .finish()
158    }
159}
160
161pub struct ClientInfo {
162    ptr: ptr::NonNull<pw_sys::pw_client_info>,
163}
164
165impl ClientInfo {
166    pub fn new(ptr: ptr::NonNull<pw_sys::pw_client_info>) -> Self {
167        Self { ptr }
168    }
169
170    pub fn from_raw(raw: *mut pw_sys::pw_client_info) -> Self {
171        Self {
172            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
173        }
174    }
175
176    pub fn into_raw(self) -> *mut pw_sys::pw_client_info {
177        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
178    }
179}
180
181impl Drop for ClientInfo {
182    fn drop(&mut self) {
183        unsafe { pw_sys::pw_client_info_free(self.ptr.as_ptr()) }
184    }
185}
186
187impl std::ops::Deref for ClientInfo {
188    type Target = ClientInfoRef;
189
190    fn deref(&self) -> &Self::Target {
191        unsafe { self.ptr.cast::<ClientInfoRef>().as_ref() }
192    }
193}
194
195impl AsRef<ClientInfoRef> for ClientInfo {
196    fn as_ref(&self) -> &ClientInfoRef {
197        self.deref()
198    }
199}
200
201bitflags! {
202    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
203    pub struct ClientChangeMask: u64 {
204        const PROPS = pw_sys::PW_CLIENT_CHANGE_MASK_PROPS as u64;
205    }
206}
207
208impl fmt::Debug for ClientInfo {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.debug_struct("ClientInfo")
211            .field("id", &self.id())
212            .field("change-mask", &self.change_mask())
213            .field("props", &self.props())
214            .finish()
215    }
216}
217
218pub struct ClientListener {
219    // Need to stay allocated while the listener is registered
220    #[allow(dead_code)]
221    events: Pin<Box<pw_sys::pw_client_events>>,
222    listener: Pin<Box<spa_sys::spa_hook>>,
223    #[allow(dead_code)]
224    data: Box<ListenerLocalCallbacks>,
225}
226
227impl Listener for ClientListener {}
228
229impl Drop for ClientListener {
230    fn drop(&mut self) {
231        spa::utils::hook::remove(*self.listener);
232    }
233}
234
235impl<'a> ClientListenerLocalBuilder<'a> {
236    #[must_use]
237    pub fn info<F>(mut self, info: F) -> Self
238    where
239        F: Fn(&ClientInfoRef) + 'static,
240    {
241        self.cbs.info = Some(Box::new(info));
242        self
243    }
244
245    pub fn permissions<F>(mut self, permissions: F) -> Self
246    where
247        F: Fn(u32, &[Permission]) + 'static,
248    {
249        self.cbs.permissions = Some(Box::new(permissions));
250        self
251    }
252
253    #[must_use]
254    pub fn register(self) -> ClientListener {
255        unsafe extern "C" fn client_events_info(
256            data: *mut c_void,
257            info: *const pw_sys::pw_client_info,
258        ) {
259            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
260            let info =
261                ptr::NonNull::new(info as *mut pw_sys::pw_client_info).expect("info is NULL");
262            let info = info.cast::<ClientInfoRef>().as_ref();
263            callbacks.info.as_ref().unwrap()(info);
264        }
265
266        unsafe extern "C" fn client_events_permissions(
267            data: *mut c_void,
268            index: u32,
269            n_permissions: u32,
270            permissions: *const pw_sys::pw_permission,
271        ) {
272            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
273            let permissions =
274                std::slice::from_raw_parts(permissions.cast(), n_permissions as usize);
275
276            callbacks.permissions.as_ref().unwrap()(index, permissions);
277        }
278
279        let e = unsafe {
280            let mut e: Pin<Box<pw_sys::pw_client_events>> = Box::pin(mem::zeroed());
281            e.version = pw_sys::PW_VERSION_CLIENT_EVENTS;
282
283            if self.cbs.info.is_some() {
284                e.info = Some(client_events_info);
285            }
286            if self.cbs.permissions.is_some() {
287                e.permissions = Some(client_events_permissions);
288            }
289
290            e
291        };
292
293        let (listener, data) = unsafe {
294            let client = &self.client.proxy.as_ptr();
295
296            let data = Box::into_raw(Box::new(self.cbs));
297            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
298            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
299
300            spa_interface_call_method!(
301                client,
302                pw_sys::pw_client_methods,
303                add_listener,
304                listener_ptr.cast(),
305                e.as_ref().get_ref(),
306                data as *mut _
307            );
308
309            (listener, Box::from_raw(data))
310        };
311
312        ClientListener {
313            events: e,
314            listener,
315            data,
316        }
317    }
318}