pipewire/
link.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Links connect two [ports](crate::port) of opposite direction, making media flow from the output port to the input port.
5//!
6//! A link negotiates a format and buffers between ports. A port can be linked to many other ports and PipeWire will manage mixing and duplicating the buffers.
7//!
8//! This module contains wrappers for [`pw_link`](pw_sys::pw_link) and related items.
9
10use std::{
11    ffi::{c_void, CStr},
12    fmt, mem,
13    ops::Deref,
14    pin::Pin,
15    ptr,
16};
17
18use bitflags::bitflags;
19use spa::spa_interface_call_method;
20
21use crate::{
22    proxy::{Listener, Proxy, ProxyT},
23    types::ObjectType,
24};
25
26/// A [proxy][Proxy] to a [link](self).
27#[derive(Debug)]
28pub struct Link {
29    proxy: Proxy,
30}
31
32impl ProxyT for Link {
33    fn type_() -> ObjectType {
34        ObjectType::Link
35    }
36
37    fn upcast(self) -> Proxy {
38        self.proxy
39    }
40
41    fn upcast_ref(&self) -> &Proxy {
42        &self.proxy
43    }
44
45    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
46    where
47        Self: Sized,
48    {
49        Self { proxy }
50    }
51}
52
53impl Link {
54    #[must_use = "Use the builder to register event callbacks"]
55    pub fn add_listener_local(&self) -> LinkListenerLocalBuilder<'_> {
56        LinkListenerLocalBuilder {
57            link: self,
58            cbs: ListenerLocalCallbacks::default(),
59        }
60    }
61}
62
63/// An owned listener for link events.
64///
65/// This is created by [`LinkListenerLocalBuilder`] and will receive events as long as it is alive.
66/// When this gets dropped, the listener gets unregistered and no events will be received by it.
67#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
68pub struct LinkListener {
69    // Need to stay allocated while the listener is registered
70    #[allow(dead_code)]
71    events: Pin<Box<pw_sys::pw_link_events>>,
72    listener: Pin<Box<spa_sys::spa_hook>>,
73    #[allow(dead_code)]
74    data: Box<ListenerLocalCallbacks>,
75}
76
77impl Listener for LinkListener {}
78
79impl Drop for LinkListener {
80    fn drop(&mut self) {
81        spa::utils::hook::remove(*self.listener);
82    }
83}
84
85#[derive(Default)]
86struct ListenerLocalCallbacks {
87    #[allow(clippy::type_complexity)]
88    info: Option<Box<dyn Fn(&LinkInfoRef)>>,
89}
90
91/// A builder for registering link event callbacks.
92///
93/// Use [`Link::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
94/// After adding callbacks, use [`register`](Self::register) to get back a [`LinkListener`].
95///
96/// # Examples
97/// ```
98/// # use pipewire::link::Link;
99/// # fn example(link: Link) {
100/// let link_listener = link.add_listener_local()
101///     .info(|info| println!("New link info: {info:?}"))
102///     .register();
103/// # }
104/// ```
105pub struct LinkListenerLocalBuilder<'link> {
106    link: &'link Link,
107    cbs: ListenerLocalCallbacks,
108}
109
110impl<'a> LinkListenerLocalBuilder<'a> {
111    /// Set the link `info` event callback of the listener.
112    ///
113    /// # Callback parameters
114    /// `info`: Info about the link.
115    ///
116    /// # Examples
117    /// ```
118    /// # use pipewire::link::Link;
119    /// # fn example(link: Link) {
120    /// let link_listener = link.add_listener_local()
121    ///     .info(|info| println!("New link info: {info:?}"))
122    ///     .register();
123    /// # }
124    /// ```
125    #[must_use = "Call `.register()` to start receiving events"]
126    pub fn info<F>(mut self, info: F) -> Self
127    where
128        F: Fn(&LinkInfoRef) + 'static,
129    {
130        self.cbs.info = Some(Box::new(info));
131        self
132    }
133
134    /// Subscribe to events and register any provided callbacks.
135    pub fn register(self) -> LinkListener {
136        unsafe extern "C" fn link_events_info(
137            data: *mut c_void,
138            info: *const pw_sys::pw_link_info,
139        ) {
140            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
141            let info = ptr::NonNull::new(info as *mut pw_sys::pw_link_info).expect("info is NULL");
142            let info = info.cast::<LinkInfoRef>().as_ref();
143            callbacks.info.as_ref().unwrap()(info);
144        }
145
146        let e = unsafe {
147            let mut e: Pin<Box<pw_sys::pw_link_events>> = Box::pin(mem::zeroed());
148            e.version = pw_sys::PW_VERSION_LINK_EVENTS;
149
150            if self.cbs.info.is_some() {
151                e.info = Some(link_events_info);
152            }
153
154            e
155        };
156
157        let (listener, data) = unsafe {
158            let link = &self.link.proxy.as_ptr();
159
160            let data = Box::into_raw(Box::new(self.cbs));
161            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
162            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
163
164            spa_interface_call_method!(
165                link,
166                pw_sys::pw_link_methods,
167                add_listener,
168                listener_ptr.cast(),
169                e.as_ref().get_ref(),
170                data as *mut _
171            );
172
173            (listener, Box::from_raw(data))
174        };
175
176        LinkListener {
177            events: e,
178            listener,
179            data,
180        }
181    }
182}
183
184#[repr(transparent)]
185pub struct LinkInfoRef(pw_sys::pw_link_info);
186
187impl LinkInfoRef {
188    pub fn as_raw(&self) -> &pw_sys::pw_link_info {
189        &self.0
190    }
191
192    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_link_info {
193        std::ptr::addr_of!(self.0).cast_mut()
194    }
195
196    pub fn id(&self) -> u32 {
197        self.0.id
198    }
199
200    pub fn output_node_id(&self) -> u32 {
201        self.0.output_node_id
202    }
203
204    pub fn output_port_id(&self) -> u32 {
205        self.0.output_port_id
206    }
207
208    pub fn input_node_id(&self) -> u32 {
209        self.0.input_node_id
210    }
211
212    pub fn input_port_id(&self) -> u32 {
213        self.0.input_port_id
214    }
215
216    pub fn state(&self) -> LinkState<'_> {
217        let raw_state = self.0.state;
218        match raw_state {
219            pw_sys::pw_link_state_PW_LINK_STATE_ERROR => {
220                let error = unsafe { CStr::from_ptr(self.0.error).to_str().unwrap() };
221                LinkState::Error(error)
222            }
223            pw_sys::pw_link_state_PW_LINK_STATE_UNLINKED => LinkState::Unlinked,
224            pw_sys::pw_link_state_PW_LINK_STATE_INIT => LinkState::Init,
225            pw_sys::pw_link_state_PW_LINK_STATE_NEGOTIATING => LinkState::Negotiating,
226            pw_sys::pw_link_state_PW_LINK_STATE_ALLOCATING => LinkState::Allocating,
227            pw_sys::pw_link_state_PW_LINK_STATE_PAUSED => LinkState::Paused,
228            pw_sys::pw_link_state_PW_LINK_STATE_ACTIVE => LinkState::Active,
229            _ => panic!("Invalid link state: {raw_state}"),
230        }
231    }
232
233    pub fn change_mask(&self) -> LinkChangeMask {
234        LinkChangeMask::from_bits_retain(self.0.change_mask)
235    }
236
237    pub fn format(&self) -> Option<&spa::pod::Pod> {
238        let format = self.0.format;
239        if format.is_null() {
240            None
241        } else {
242            Some(unsafe { spa::pod::Pod::from_raw(format) })
243        }
244    }
245
246    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
247        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
248        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
249    }
250}
251
252impl fmt::Debug for LinkInfoRef {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        f.debug_struct("LinkInfoRef")
255            .field("id", &self.id())
256            .field("output_node_id", &self.output_node_id())
257            .field("output_port_id", &self.output_port_id())
258            .field("input_node_id", &self.input_node_id())
259            .field("input_port_id", &self.input_port_id())
260            .field("change-mask", &self.change_mask())
261            .field("state", &self.state())
262            .field("props", &self.props())
263            // TODO: .field("format", &self.format())
264            .finish()
265    }
266}
267
268pub struct LinkInfo {
269    ptr: ptr::NonNull<pw_sys::pw_link_info>,
270}
271
272impl LinkInfo {
273    pub fn new(ptr: ptr::NonNull<pw_sys::pw_link_info>) -> Self {
274        Self { ptr }
275    }
276
277    pub fn from_raw(raw: *mut pw_sys::pw_link_info) -> Self {
278        Self {
279            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
280        }
281    }
282
283    pub fn into_raw(self) -> *mut pw_sys::pw_link_info {
284        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
285    }
286}
287
288impl Drop for LinkInfo {
289    fn drop(&mut self) {
290        unsafe { pw_sys::pw_link_info_free(self.ptr.as_ptr()) }
291    }
292}
293
294impl std::ops::Deref for LinkInfo {
295    type Target = LinkInfoRef;
296
297    fn deref(&self) -> &Self::Target {
298        unsafe { self.ptr.cast::<LinkInfoRef>().as_ref() }
299    }
300}
301
302impl AsRef<LinkInfoRef> for LinkInfo {
303    fn as_ref(&self) -> &LinkInfoRef {
304        self.deref()
305    }
306}
307
308bitflags! {
309    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
310    pub struct LinkChangeMask: u64 {
311        const STATE = pw_sys::PW_LINK_CHANGE_MASK_STATE as u64;
312        const FORMAT = pw_sys::PW_LINK_CHANGE_MASK_FORMAT as u64;
313        const PROPS = pw_sys::PW_LINK_CHANGE_MASK_PROPS as u64;
314    }
315}
316
317impl fmt::Debug for LinkInfo {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        f.debug_struct("LinkInfo")
320            .field("id", &self.id())
321            .field("output_node_id", &self.output_node_id())
322            .field("output_port_id", &self.output_port_id())
323            .field("input_node_id", &self.input_node_id())
324            .field("input_port_id", &self.input_port_id())
325            .field("change-mask", &self.change_mask())
326            .field("state", &self.state())
327            .field("props", &self.props())
328            // TODO: .field("format", &self.format())
329            .finish()
330    }
331}
332
333#[derive(Debug)]
334pub enum LinkState<'a> {
335    Error(&'a str),
336    Unlinked,
337    Init,
338    Negotiating,
339    Allocating,
340    Paused,
341    Active,
342}