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