pipewire/loop_/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use std::{convert::TryInto, os::unix::prelude::*, ptr, time::Duration};
5
6use libc::{c_int, c_void};
7pub use nix::sys::signal::Signal;
8use spa::{spa_interface_call_method, support::system::IoFlags, utils::result::SpaResult};
9
10use crate::utils::assert_main_thread;
11
12mod box_;
13pub use box_::*;
14mod rc;
15pub use rc::*;
16
17/// A transparent wrapper around a raw [`pw_loop`](`pw_sys::pw_loop`).
18/// It is usually only seen in a reference (`&Loop`), and does not own the `pw_loop`.
19///
20/// Owned versions, [`LoopRc`] for shared ownership and [`LoopBox`] for unique ownership, are available,
21/// which lets you create and own a [`pw_loop`](`pw_sys::pw_loop`).
22///
23/// Other objects, such as [`MainLoop`](`crate::main_loop::MainLoop`), can also contain loops.
24#[repr(transparent)]
25pub struct Loop(pw_sys::pw_loop);
26
27impl Loop {
28    pub fn as_raw(&self) -> &pw_sys::pw_loop {
29        &self.0
30    }
31
32    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_loop {
33        std::ptr::addr_of!(self.0).cast_mut()
34    }
35
36    /// Get the file descriptor backing this loop.
37    pub fn fd(&self) -> BorrowedFd<'_> {
38        unsafe {
39            let mut iface = self.as_raw().control.as_ref().unwrap().iface;
40
41            let raw_fd = spa_interface_call_method!(
42                &mut iface as *mut spa_sys::spa_interface,
43                spa_sys::spa_loop_control_methods,
44                get_fd,
45            );
46
47            BorrowedFd::borrow_raw(raw_fd)
48        }
49    }
50
51    /// Enter a loop
52    ///
53    /// Start an iteration of the loop. This function should be called
54    /// before calling iterate and is typically used to capture the thread
55    /// that this loop will run in.
56    ///
57    /// # Safety
58    /// Each call of `enter` must be paired with a call of `leave`.
59    pub unsafe fn enter(&self) {
60        let mut iface = self.as_raw().control.as_ref().unwrap().iface;
61
62        spa_interface_call_method!(
63            &mut iface as *mut spa_sys::spa_interface,
64            spa_sys::spa_loop_control_methods,
65            enter,
66        )
67    }
68
69    /// Leave a loop
70    ///
71    /// Ends the iteration of a loop. This should be called after calling
72    /// iterate.
73    ///
74    /// # Safety
75    /// Each call of `leave` must be paired with a call of `enter`.
76    pub unsafe fn leave(&self) {
77        let mut iface = self.as_raw().control.as_ref().unwrap().iface;
78
79        spa_interface_call_method!(
80            &mut iface as *mut spa_sys::spa_interface,
81            spa_sys::spa_loop_control_methods,
82            leave,
83        )
84    }
85
86    /// Perform one iteration of the loop.
87    ///
88    /// An optional timeout can be provided.
89    /// 0 for no timeout, -1 for infinite timeout.
90    ///
91    /// This function will block
92    /// up to the provided timeout and then dispatch the fds with activity.
93    /// The number of dispatched fds is returned.
94    ///
95    /// This will automatically call [`Self::enter()`] on the loop before iterating, and [`Self::leave()`] afterwards.
96    ///
97    /// # Panics
98    /// This function will panic if the provided timeout as milliseconds does not fit inside a
99    /// `c_int` integer.
100    pub fn iterate(&self, timeout: std::time::Duration) -> i32 {
101        unsafe {
102            self.enter();
103            let res = self.iterate_unguarded(timeout);
104            self.leave();
105
106            res
107        }
108    }
109
110    /// A variant of [`iterate()`](`Self::iterate()`) that does not call [`Self::enter()`]  and [`Self::leave()`] on the loop.
111    ///
112    /// # Safety
113    /// Before calling this, [`Self::enter()`] must be called, and [`Self::leave()`] must be called afterwards.
114    pub unsafe fn iterate_unguarded(&self, timeout: std::time::Duration) -> i32 {
115        let mut iface = self.as_raw().control.as_ref().unwrap().iface;
116
117        let timeout: c_int = timeout
118            .as_millis()
119            .try_into()
120            .expect("Provided timeout does not fit in a c_int");
121
122        spa_interface_call_method!(
123            &mut iface as *mut spa_sys::spa_interface,
124            spa_sys::spa_loop_control_methods,
125            iterate,
126            timeout
127        )
128    }
129
130    /// Register some type of IO object with a callback that is called when reading/writing on the IO object
131    /// is available.
132    ///
133    /// The specified `event_mask` determines whether to trigger when either input, output, or any of the two is available.
134    ///
135    /// The returned IoSource needs to take ownership of the IO object, but will provide a reference to the callback when called.
136    #[must_use]
137    pub fn add_io<I, F>(&self, io: I, event_mask: IoFlags, callback: F) -> IoSource<'_, I>
138    where
139        I: AsRawFd,
140        F: Fn(&mut I) + 'static,
141        Self: Sized,
142    {
143        unsafe extern "C" fn call_closure<I>(data: *mut c_void, _fd: RawFd, _mask: u32)
144        where
145            I: AsRawFd,
146        {
147            let (io, callback) = (data as *mut IoSourceData<I>).as_mut().unwrap();
148            callback(io);
149        }
150
151        let fd = io.as_raw_fd();
152        let data = Box::into_raw(Box::new((io, Box::new(callback) as Box<dyn Fn(&mut I)>)));
153
154        let (source, data) = unsafe {
155            let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
156
157            let source = spa_interface_call_method!(
158                &mut iface as *mut spa_sys::spa_interface,
159                spa_sys::spa_loop_utils_methods,
160                add_io,
161                fd,
162                event_mask.bits(),
163                // Never let the loop close the fd, this should be handled via `Drop` implementations.
164                false,
165                Some(call_closure::<I>),
166                data as *mut _
167            );
168
169            (source, Box::from_raw(data))
170        };
171
172        let ptr = ptr::NonNull::new(source).expect("source is NULL");
173
174        IoSource {
175            ptr,
176            loop_: self,
177            _data: data,
178        }
179    }
180
181    /// Register a callback to be called whenever the loop is idle.
182    ///
183    /// This can be enabled and disabled as needed with the `enabled` parameter,
184    /// and also with the `enable` method on the returned source.
185    #[must_use]
186    pub fn add_idle<F>(&self, enabled: bool, callback: F) -> IdleSource<'_>
187    where
188        F: Fn() + 'static,
189    {
190        unsafe extern "C" fn call_closure<F>(data: *mut c_void)
191        where
192            F: Fn(),
193        {
194            let callback = (data as *mut F).as_ref().unwrap();
195            callback();
196        }
197
198        let data = Box::into_raw(Box::new(callback));
199
200        let (source, data) = unsafe {
201            let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
202
203            let source = spa_interface_call_method!(
204                &mut iface as *mut spa_sys::spa_interface,
205                spa_sys::spa_loop_utils_methods,
206                add_idle,
207                enabled,
208                Some(call_closure::<F>),
209                data as *mut _
210            );
211
212            (source, Box::from_raw(data))
213        };
214
215        let ptr = ptr::NonNull::new(source).expect("source is NULL");
216
217        IdleSource {
218            ptr,
219            loop_: self,
220            _data: data,
221        }
222    }
223
224    /// Register a signal with a callback that is called when the signal is sent.
225    ///
226    /// For example, this can be used to quit the loop when the process receives the `SIGTERM` signal.
227    #[must_use]
228    pub fn add_signal_local<F>(&self, signal: Signal, callback: F) -> SignalSource<'_>
229    where
230        F: Fn() + 'static,
231        Self: Sized,
232    {
233        assert_main_thread();
234
235        unsafe extern "C" fn call_closure<F>(data: *mut c_void, _signal: c_int)
236        where
237            F: Fn(),
238        {
239            let callback = (data as *mut F).as_ref().unwrap();
240            callback();
241        }
242
243        let data = Box::into_raw(Box::new(callback));
244
245        let (source, data) = unsafe {
246            let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
247
248            let source = spa_interface_call_method!(
249                &mut iface as *mut spa_sys::spa_interface,
250                spa_sys::spa_loop_utils_methods,
251                add_signal,
252                signal as c_int,
253                Some(call_closure::<F>),
254                data as *mut _
255            );
256
257            (source, Box::from_raw(data))
258        };
259
260        let ptr = ptr::NonNull::new(source).expect("source is NULL");
261
262        SignalSource {
263            ptr,
264            loop_: self,
265            _data: data,
266        }
267    }
268
269    /// Register a new event with a callback that is called when the event happens.
270    ///
271    /// The returned [`EventSource`] can be used to trigger the event.
272    #[must_use]
273    pub fn add_event<F>(&self, callback: F) -> EventSource<'_>
274    where
275        F: Fn() + 'static,
276        Self: Sized,
277    {
278        unsafe extern "C" fn call_closure<F>(data: *mut c_void, _count: u64)
279        where
280            F: Fn(),
281        {
282            let callback = (data as *mut F).as_ref().unwrap();
283            callback();
284        }
285
286        let data = Box::into_raw(Box::new(callback));
287
288        let (source, data) = unsafe {
289            let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
290
291            let source = spa_interface_call_method!(
292                &mut iface as *mut spa_sys::spa_interface,
293                spa_sys::spa_loop_utils_methods,
294                add_event,
295                Some(call_closure::<F>),
296                data as *mut _
297            );
298            (source, Box::from_raw(data))
299        };
300
301        let ptr = ptr::NonNull::new(source).expect("source is NULL");
302
303        EventSource {
304            ptr,
305            loop_: self,
306            _data: data,
307        }
308    }
309
310    /// Register a timer with the loop with a callback that is called after the timer expired.
311    ///
312    /// The timer will start out inactive, and the returned [`TimerSource`] can be used to arm the timer, or disarm it again.
313    ///
314    /// The callback will be provided with the number of timer expirations since the callback was last called.
315    #[must_use]
316    pub fn add_timer<F>(&self, callback: F) -> TimerSource<'_>
317    where
318        F: Fn(u64) + 'static,
319        Self: Sized,
320    {
321        unsafe extern "C" fn call_closure<F>(data: *mut c_void, expirations: u64)
322        where
323            F: Fn(u64),
324        {
325            let callback = (data as *mut F).as_ref().unwrap();
326            callback(expirations);
327        }
328
329        let data = Box::into_raw(Box::new(callback));
330
331        let (source, data) = unsafe {
332            let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
333
334            let source = spa_interface_call_method!(
335                &mut iface as *mut spa_sys::spa_interface,
336                spa_sys::spa_loop_utils_methods,
337                add_timer,
338                Some(call_closure::<F>),
339                data as *mut _
340            );
341            (source, Box::from_raw(data))
342        };
343
344        let ptr = ptr::NonNull::new(source).expect("source is NULL");
345
346        TimerSource {
347            ptr,
348            loop_: self,
349            _data: data,
350        }
351    }
352
353    /// Destroy a source that belongs to this loop.
354    ///
355    /// # Safety
356    /// The provided source must belong to this loop.
357    unsafe fn destroy_source<S>(&self, source: &S)
358    where
359        S: IsSource,
360        Self: Sized,
361    {
362        let mut iface = self.as_raw().utils.as_ref().unwrap().iface;
363
364        spa_interface_call_method!(
365            &mut iface as *mut spa_sys::spa_interface,
366            spa_sys::spa_loop_utils_methods,
367            destroy_source,
368            source.as_ptr()
369        )
370    }
371}
372
373pub trait IsSource {
374    /// Return a valid pointer to a raw `spa_source`.
375    fn as_ptr(&self) -> *mut spa_sys::spa_source;
376}
377
378type IoSourceData<I> = (I, Box<dyn Fn(&mut I) + 'static>);
379
380/// A source that can be used to react to IO events.
381///
382/// This source can be obtained by calling [`add_io`](`Loop::add_io`) on a loop, registering a callback to it.
383pub struct IoSource<'l, I>
384where
385    I: AsRawFd,
386{
387    ptr: ptr::NonNull<spa_sys::spa_source>,
388    loop_: &'l Loop,
389    // Store data wrapper to prevent leak
390    _data: Box<IoSourceData<I>>,
391}
392
393impl<'l, I> IsSource for IoSource<'l, I>
394where
395    I: AsRawFd,
396{
397    fn as_ptr(&self) -> *mut spa_sys::spa_source {
398        self.ptr.as_ptr()
399    }
400}
401
402impl<'l, I> Drop for IoSource<'l, I>
403where
404    I: AsRawFd,
405{
406    fn drop(&mut self) {
407        unsafe { self.loop_.destroy_source(self) }
408    }
409}
410
411/// A source that can be used to have a callback called when the loop is idle.
412///
413/// This source can be obtained by calling [`add_idle`](`Loop::add_idle`) on a loop, registering a callback to it.
414pub struct IdleSource<'l> {
415    ptr: ptr::NonNull<spa_sys::spa_source>,
416    loop_: &'l Loop,
417    // Store data wrapper to prevent leak
418    _data: Box<dyn Fn() + 'static>,
419}
420
421impl<'l> IdleSource<'l> {
422    /// Set the source as enabled or disabled, allowing or preventing the callback from being called.
423    pub fn enable(&self, enable: bool) {
424        unsafe {
425            let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface;
426
427            spa_interface_call_method!(
428                &mut iface as *mut spa_sys::spa_interface,
429                spa_sys::spa_loop_utils_methods,
430                enable_idle,
431                self.as_ptr(),
432                enable
433            );
434        }
435    }
436}
437
438impl<'l> IsSource for IdleSource<'l> {
439    fn as_ptr(&self) -> *mut spa_sys::spa_source {
440        self.ptr.as_ptr()
441    }
442}
443
444impl<'l> Drop for IdleSource<'l> {
445    fn drop(&mut self) {
446        unsafe { self.loop_.destroy_source(self) }
447    }
448}
449
450/// A source that can be used to react to signals.
451///
452/// This source can be obtained by calling [`add_signal_local`](`Loop::add_signal_local`) on a loop, registering a callback to it.
453pub struct SignalSource<'l> {
454    ptr: ptr::NonNull<spa_sys::spa_source>,
455    loop_: &'l Loop,
456    // Store data wrapper to prevent leak
457    _data: Box<dyn Fn() + 'static>,
458}
459
460impl<'l> IsSource for SignalSource<'l> {
461    fn as_ptr(&self) -> *mut spa_sys::spa_source {
462        self.ptr.as_ptr()
463    }
464}
465
466impl<'l> Drop for SignalSource<'l> {
467    fn drop(&mut self) {
468        unsafe { self.loop_.destroy_source(self) }
469    }
470}
471
472/// A source that can be used to signal to a loop that an event has occurred.
473///
474/// This source can be obtained by calling [`add_event`](`Loop::add_event`) on a loop, registering a callback to it.
475///
476/// By calling [`signal`](`EventSource::signal`) on the `EventSource`, the loop is signaled that the event has occurred.
477/// It will then call the callback at the next possible occasion.
478pub struct EventSource<'l> {
479    ptr: ptr::NonNull<spa_sys::spa_source>,
480    loop_: &'l Loop,
481    // Store data wrapper to prevent leak
482    _data: Box<dyn Fn() + 'static>,
483}
484
485impl<'l> IsSource for EventSource<'l> {
486    fn as_ptr(&self) -> *mut spa_sys::spa_source {
487        self.ptr.as_ptr()
488    }
489}
490
491impl<'l> EventSource<'l> {
492    /// Signal the loop associated with this source that the event has occurred,
493    /// to make the loop call the callback at the next possible occasion.
494    pub fn signal(&self) -> SpaResult {
495        let res = unsafe {
496            let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface;
497
498            spa_interface_call_method!(
499                &mut iface as *mut spa_sys::spa_interface,
500                spa_sys::spa_loop_utils_methods,
501                signal_event,
502                self.as_ptr()
503            )
504        };
505
506        SpaResult::from_c(res)
507    }
508}
509
510impl<'l> Drop for EventSource<'l> {
511    fn drop(&mut self) {
512        unsafe { self.loop_.destroy_source(self) }
513    }
514}
515
516/// A source that can be used to have a callback called on a timer.
517///
518/// This source can be obtained by calling [`add_timer`](`Loop::add_timer`) on a loop, registering a callback to it.
519///
520/// The timer starts out inactive.
521/// You can arm or disarm the timer by calling [`update_timer`](`Self::update_timer`).
522pub struct TimerSource<'l> {
523    ptr: ptr::NonNull<spa_sys::spa_source>,
524    loop_: &'l Loop,
525    // Store data wrapper to prevent leak
526    _data: Box<dyn Fn(u64) + 'static>,
527}
528
529impl<'l> TimerSource<'l> {
530    /// Arm or disarm the timer.
531    ///
532    /// The timer will be called the next time after the provided `value` duration.
533    /// After that, the timer will be repeatedly called again at the the specified `interval`.
534    ///
535    /// If `interval` is `None` or zero, the timer will only be called once. \
536    /// If `value` is `None` or zero, the timer will be disabled.
537    ///
538    /// # Panics
539    /// The provided durations seconds must fit in an i64. Otherwise, this function will panic.
540    pub fn update_timer(&self, value: Option<Duration>, interval: Option<Duration>) -> SpaResult {
541        fn duration_to_timespec(duration: Duration) -> spa_sys::timespec {
542            spa_sys::timespec {
543                tv_sec: duration.as_secs().try_into().expect("Duration too long"),
544                // `Into` is only implemented on some platforms for these types,
545                // so use a fallible conversion.
546                // As there are a limited amount of nanoseconds in a second, this shouldn't fail
547                #[allow(clippy::unnecessary_fallible_conversions)]
548                tv_nsec: duration
549                    .subsec_nanos()
550                    .try_into()
551                    .expect("Nanoseconds should fit into timespec"),
552            }
553        }
554
555        let value = duration_to_timespec(value.unwrap_or_default());
556        let interval = duration_to_timespec(interval.unwrap_or_default());
557
558        let res = unsafe {
559            let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface;
560
561            spa_interface_call_method!(
562                &mut iface as *mut spa_sys::spa_interface,
563                spa_sys::spa_loop_utils_methods,
564                update_timer,
565                self.as_ptr(),
566                &value as *const _ as *mut _,
567                &interval as *const _ as *mut _,
568                false
569            )
570        };
571
572        SpaResult::from_c(res)
573    }
574}
575
576impl<'l> IsSource for TimerSource<'l> {
577    fn as_ptr(&self) -> *mut spa_sys::spa_source {
578        self.ptr.as_ptr()
579    }
580}
581
582impl<'l> Drop for TimerSource<'l> {
583    fn drop(&mut self) {
584        unsafe { self.loop_.destroy_source(self) }
585    }
586}