pipewire/
link.rs

1use std::{
2    ffi::{c_void, CStr},
3    fmt, mem,
4    ops::Deref,
5    pin::Pin,
6    ptr,
7};
8
9use bitflags::bitflags;
10use spa::spa_interface_call_method;
11
12use crate::{
13    proxy::{Listener, Proxy, ProxyT},
14    types::ObjectType,
15};
16
17#[derive(Debug)]
18pub struct Link {
19    proxy: Proxy,
20}
21
22impl ProxyT for Link {
23    fn type_() -> ObjectType {
24        ObjectType::Link
25    }
26
27    fn upcast(self) -> Proxy {
28        self.proxy
29    }
30
31    fn upcast_ref(&self) -> &Proxy {
32        &self.proxy
33    }
34
35    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
36    where
37        Self: Sized,
38    {
39        Self { proxy }
40    }
41}
42
43impl Link {
44    #[must_use]
45    pub fn add_listener_local(&self) -> LinkListenerLocalBuilder<'_> {
46        LinkListenerLocalBuilder {
47            link: self,
48            cbs: ListenerLocalCallbacks::default(),
49        }
50    }
51}
52
53pub struct LinkListener {
54    // Need to stay allocated while the listener is registered
55    #[allow(dead_code)]
56    events: Pin<Box<pw_sys::pw_link_events>>,
57    listener: Pin<Box<spa_sys::spa_hook>>,
58    #[allow(dead_code)]
59    data: Box<ListenerLocalCallbacks>,
60}
61
62impl Listener for LinkListener {}
63
64impl Drop for LinkListener {
65    fn drop(&mut self) {
66        spa::utils::hook::remove(*self.listener);
67    }
68}
69
70#[derive(Default)]
71struct ListenerLocalCallbacks {
72    #[allow(clippy::type_complexity)]
73    info: Option<Box<dyn Fn(&LinkInfoRef)>>,
74}
75
76pub struct LinkListenerLocalBuilder<'link> {
77    link: &'link Link,
78    cbs: ListenerLocalCallbacks,
79}
80
81impl<'a> LinkListenerLocalBuilder<'a> {
82    #[must_use]
83    pub fn info<F>(mut self, info: F) -> Self
84    where
85        F: Fn(&LinkInfoRef) + 'static,
86    {
87        self.cbs.info = Some(Box::new(info));
88        self
89    }
90
91    #[must_use]
92    pub fn register(self) -> LinkListener {
93        unsafe extern "C" fn link_events_info(
94            data: *mut c_void,
95            info: *const pw_sys::pw_link_info,
96        ) {
97            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
98            let info = ptr::NonNull::new(info as *mut pw_sys::pw_link_info).expect("info is NULL");
99            let info = info.cast::<LinkInfoRef>().as_ref();
100            callbacks.info.as_ref().unwrap()(info);
101        }
102
103        let e = unsafe {
104            let mut e: Pin<Box<pw_sys::pw_link_events>> = Box::pin(mem::zeroed());
105            e.version = pw_sys::PW_VERSION_LINK_EVENTS;
106
107            if self.cbs.info.is_some() {
108                e.info = Some(link_events_info);
109            }
110
111            e
112        };
113
114        let (listener, data) = unsafe {
115            let link = &self.link.proxy.as_ptr();
116
117            let data = Box::into_raw(Box::new(self.cbs));
118            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
119            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
120
121            spa_interface_call_method!(
122                link,
123                pw_sys::pw_link_methods,
124                add_listener,
125                listener_ptr.cast(),
126                e.as_ref().get_ref(),
127                data as *mut _
128            );
129
130            (listener, Box::from_raw(data))
131        };
132
133        LinkListener {
134            events: e,
135            listener,
136            data,
137        }
138    }
139}
140
141#[repr(transparent)]
142pub struct LinkInfoRef(pw_sys::pw_link_info);
143
144impl LinkInfoRef {
145    pub fn as_raw(&self) -> &pw_sys::pw_link_info {
146        &self.0
147    }
148
149    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_link_info {
150        std::ptr::addr_of!(self.0).cast_mut()
151    }
152
153    pub fn id(&self) -> u32 {
154        self.0.id
155    }
156
157    pub fn output_node_id(&self) -> u32 {
158        self.0.output_node_id
159    }
160
161    pub fn output_port_id(&self) -> u32 {
162        self.0.output_port_id
163    }
164
165    pub fn input_node_id(&self) -> u32 {
166        self.0.input_node_id
167    }
168
169    pub fn input_port_id(&self) -> u32 {
170        self.0.input_port_id
171    }
172
173    pub fn state(&self) -> LinkState<'_> {
174        let raw_state = self.0.state;
175        match raw_state {
176            pw_sys::pw_link_state_PW_LINK_STATE_ERROR => {
177                let error = unsafe { CStr::from_ptr(self.0.error).to_str().unwrap() };
178                LinkState::Error(error)
179            }
180            pw_sys::pw_link_state_PW_LINK_STATE_UNLINKED => LinkState::Unlinked,
181            pw_sys::pw_link_state_PW_LINK_STATE_INIT => LinkState::Init,
182            pw_sys::pw_link_state_PW_LINK_STATE_NEGOTIATING => LinkState::Negotiating,
183            pw_sys::pw_link_state_PW_LINK_STATE_ALLOCATING => LinkState::Allocating,
184            pw_sys::pw_link_state_PW_LINK_STATE_PAUSED => LinkState::Paused,
185            pw_sys::pw_link_state_PW_LINK_STATE_ACTIVE => LinkState::Active,
186            _ => panic!("Invalid link state: {raw_state}"),
187        }
188    }
189
190    pub fn change_mask(&self) -> LinkChangeMask {
191        LinkChangeMask::from_bits_retain(self.0.change_mask)
192    }
193
194    pub fn format(&self) -> Option<&spa::pod::Pod> {
195        let format = self.0.format;
196        if format.is_null() {
197            None
198        } else {
199            Some(unsafe { spa::pod::Pod::from_raw(format) })
200        }
201    }
202
203    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
204        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
205        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
206    }
207}
208
209impl fmt::Debug for LinkInfoRef {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        f.debug_struct("LinkInfoRef")
212            .field("id", &self.id())
213            .field("output_node_id", &self.output_node_id())
214            .field("output_port_id", &self.output_port_id())
215            .field("input_node_id", &self.input_node_id())
216            .field("input_port_id", &self.input_port_id())
217            .field("change-mask", &self.change_mask())
218            .field("state", &self.state())
219            .field("props", &self.props())
220            // TODO: .field("format", &self.format())
221            .finish()
222    }
223}
224
225pub struct LinkInfo {
226    ptr: ptr::NonNull<pw_sys::pw_link_info>,
227}
228
229impl LinkInfo {
230    pub fn new(ptr: ptr::NonNull<pw_sys::pw_link_info>) -> Self {
231        Self { ptr }
232    }
233
234    pub fn from_raw(raw: *mut pw_sys::pw_link_info) -> Self {
235        Self {
236            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
237        }
238    }
239
240    pub fn into_raw(self) -> *mut pw_sys::pw_link_info {
241        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
242    }
243}
244
245impl Drop for LinkInfo {
246    fn drop(&mut self) {
247        unsafe { pw_sys::pw_link_info_free(self.ptr.as_ptr()) }
248    }
249}
250
251impl std::ops::Deref for LinkInfo {
252    type Target = LinkInfoRef;
253
254    fn deref(&self) -> &Self::Target {
255        unsafe { self.ptr.cast::<LinkInfoRef>().as_ref() }
256    }
257}
258
259impl AsRef<LinkInfoRef> for LinkInfo {
260    fn as_ref(&self) -> &LinkInfoRef {
261        self.deref()
262    }
263}
264
265bitflags! {
266    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
267    pub struct LinkChangeMask: u64 {
268        const STATE = pw_sys::PW_LINK_CHANGE_MASK_STATE as u64;
269        const FORMAT = pw_sys::PW_LINK_CHANGE_MASK_FORMAT as u64;
270        const PROPS = pw_sys::PW_LINK_CHANGE_MASK_PROPS as u64;
271    }
272}
273
274impl fmt::Debug for LinkInfo {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        f.debug_struct("LinkInfo")
277            .field("id", &self.id())
278            .field("output_node_id", &self.output_node_id())
279            .field("output_port_id", &self.output_port_id())
280            .field("input_node_id", &self.input_node_id())
281            .field("input_port_id", &self.input_port_id())
282            .field("change-mask", &self.change_mask())
283            .field("state", &self.state())
284            .field("props", &self.props())
285            // TODO: .field("format", &self.format())
286            .finish()
287    }
288}
289
290#[derive(Debug)]
291pub enum LinkState<'a> {
292    Error(&'a str),
293    Unlinked,
294    Init,
295    Negotiating,
296    Allocating,
297    Paused,
298    Active,
299}