Skip to main content

gstreamer/
bus.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem::transmute;
4
5use glib::{
6    ControlFlow,
7    ffi::{gboolean, gpointer},
8    source::Priority,
9    translate::*,
10};
11
12use crate::{Bus, BusSyncReply, Message, MessageType, ffi};
13
14#[cfg(feature = "futures")]
15pub use crate::bus_futures::BusStream;
16#[cfg(feature = "futures")]
17use futures_util::{StreamExt, stream::FusedStream};
18
19unsafe extern "C" fn trampoline_watch<F: FnMut(&Bus, &Message) -> ControlFlow + Send + 'static>(
20    bus: *mut ffi::GstBus,
21    msg: *mut ffi::GstMessage,
22    func: gpointer,
23) -> gboolean {
24    unsafe {
25        let func: &mut F = &mut *(func as *mut F);
26        func(&from_glib_borrow(bus), &Message::from_glib_borrow(msg)).into_glib()
27    }
28}
29
30unsafe extern "C" fn destroy_closure_watch<
31    F: FnMut(&Bus, &Message) -> ControlFlow + Send + 'static,
32>(
33    ptr: gpointer,
34) {
35    unsafe {
36        let _ = Box::<F>::from_raw(ptr as *mut _);
37    }
38}
39
40fn into_raw_watch<F: FnMut(&Bus, &Message) -> ControlFlow + Send + 'static>(func: F) -> gpointer {
41    #[allow(clippy::type_complexity)]
42    let func: Box<F> = Box::new(func);
43    Box::into_raw(func) as gpointer
44}
45
46unsafe extern "C" fn trampoline_watch_local<F: FnMut(&Bus, &Message) -> ControlFlow + 'static>(
47    bus: *mut ffi::GstBus,
48    msg: *mut ffi::GstMessage,
49    func: gpointer,
50) -> gboolean {
51    unsafe {
52        let func: &mut glib::thread_guard::ThreadGuard<F> =
53            &mut *(func as *mut glib::thread_guard::ThreadGuard<F>);
54        (func.get_mut())(&from_glib_borrow(bus), &Message::from_glib_borrow(msg)).into_glib()
55    }
56}
57
58unsafe extern "C" fn destroy_closure_watch_local<
59    F: FnMut(&Bus, &Message) -> ControlFlow + 'static,
60>(
61    ptr: gpointer,
62) {
63    unsafe {
64        let _ = Box::<glib::thread_guard::ThreadGuard<F>>::from_raw(ptr as *mut _);
65    }
66}
67
68fn into_raw_watch_local<F: FnMut(&Bus, &Message) -> ControlFlow + 'static>(func: F) -> gpointer {
69    #[allow(clippy::type_complexity)]
70    let func: Box<glib::thread_guard::ThreadGuard<F>> =
71        Box::new(glib::thread_guard::ThreadGuard::new(func));
72    Box::into_raw(func) as gpointer
73}
74
75unsafe extern "C" fn trampoline_sync<
76    F: Fn(&Bus, &Message) -> BusSyncReply + Send + Sync + 'static,
77>(
78    bus: *mut ffi::GstBus,
79    msg: *mut ffi::GstMessage,
80    func: gpointer,
81) -> ffi::GstBusSyncReply {
82    unsafe {
83        let f: &F = &*(func as *const F);
84        let res = f(&from_glib_borrow(bus), &Message::from_glib_borrow(msg)).into_glib();
85
86        if res == ffi::GST_BUS_DROP {
87            ffi::gst_mini_object_unref(msg as *mut _);
88        }
89
90        res
91    }
92}
93
94unsafe extern "C" fn destroy_closure_sync<
95    F: Fn(&Bus, &Message) -> BusSyncReply + Send + Sync + 'static,
96>(
97    ptr: gpointer,
98) {
99    unsafe {
100        let _ = Box::<F>::from_raw(ptr as *mut _);
101    }
102}
103
104fn into_raw_sync<F: Fn(&Bus, &Message) -> BusSyncReply + Send + Sync + 'static>(
105    func: F,
106) -> gpointer {
107    let func: Box<F> = Box::new(func);
108    Box::into_raw(func) as gpointer
109}
110
111impl Bus {
112    /// Adds a bus signal watch to the default main context with the given `priority`
113    /// (e.g. `G_PRIORITY_DEFAULT`). It is also possible to use a non-default main
114    /// context set up using [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()]
115    /// (before one had to create a bus watch source and attach it to the desired
116    /// main context 'manually').
117    ///
118    /// After calling this statement, the bus will emit the "message" signal for each
119    /// message posted on the bus when the `GMainLoop` is running.
120    ///
121    /// This function may be called multiple times. To clean up, the caller is
122    /// responsible for calling [`remove_signal_watch()`][Self::remove_signal_watch()] as many times as this
123    /// function is called.
124    ///
125    /// There can only be a single bus watch per bus, you must remove any signal
126    /// watch before you can set another type of watch.
127    /// ## `priority`
128    /// The priority of the watch.
129    #[doc(alias = "gst_bus_add_signal_watch")]
130    #[doc(alias = "gst_bus_add_signal_watch_full")]
131    pub fn add_signal_watch_full(&self, priority: Priority) {
132        unsafe {
133            ffi::gst_bus_add_signal_watch_full(self.to_glib_none().0, priority.into_glib());
134        }
135    }
136
137    /// Create watch for this bus. The [`glib::Source`][crate::glib::Source] will be dispatched whenever
138    /// a message is on the bus. After the GSource is dispatched, the
139    /// message is popped off the bus and unreffed.
140    ///
141    /// As with other watches, there can only be one watch on the bus, including
142    /// any signal watch added with `gst_bus_add_signal_watch`.
143    ///
144    /// # Returns
145    ///
146    /// a [`glib::Source`][crate::glib::Source] that can be added to a `GMainLoop`.
147    #[doc(alias = "gst_bus_create_watch")]
148    pub fn create_watch<F>(&self, name: Option<&str>, priority: Priority, func: F) -> glib::Source
149    where
150        F: FnMut(&Bus, &Message) -> ControlFlow + Send + 'static,
151    {
152        skip_assert_initialized!();
153        unsafe {
154            let source = ffi::gst_bus_create_watch(self.to_glib_none().0);
155            glib::ffi::g_source_set_callback(
156                source,
157                Some(transmute::<
158                    *mut (),
159                    unsafe extern "C" fn(glib::ffi::gpointer) -> i32,
160                >(trampoline_watch::<F> as *mut ())),
161                into_raw_watch(func),
162                Some(destroy_closure_watch::<F>),
163            );
164            glib::ffi::g_source_set_priority(source, priority.into_glib());
165
166            if let Some(name) = name {
167                glib::ffi::g_source_set_name(source, name.to_glib_none().0);
168            }
169
170            from_glib_full(source)
171        }
172    }
173
174    /// Adds a bus watch to the default main context with the default priority
175    /// ( `G_PRIORITY_DEFAULT` ). It is also possible to use a non-default main
176    /// context set up using [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()] (before
177    /// one had to create a bus watch source and attach it to the desired main
178    /// context 'manually').
179    ///
180    /// This function is used to receive asynchronous messages in the main loop.
181    /// There can only be a single bus watch per bus, you must remove it before you
182    /// can set a new one.
183    ///
184    /// The bus watch will only work if a `GMainLoop` is being run.
185    ///
186    /// The watch can be removed using [`remove_watch()`][Self::remove_watch()] or by returning [`false`]
187    /// from `func`. If the watch was added to the default main context it is also
188    /// possible to remove the watch using [`glib::Source::remove()`][crate::glib::Source::remove()].
189    ///
190    /// The bus watch will take its own reference to the `self`, so it is safe to unref
191    /// `self` using `gst_object_unref()` after setting the bus watch.
192    /// ## `func`
193    /// A function to call when a message is received.
194    ///
195    /// # Returns
196    ///
197    /// The event source id or 0 if `self` already got an event source.
198    #[doc(alias = "gst_bus_add_watch")]
199    #[doc(alias = "gst_bus_add_watch_full")]
200    pub fn add_watch<F>(&self, func: F) -> Result<BusWatchGuard, glib::BoolError>
201    where
202        F: FnMut(&Bus, &Message) -> ControlFlow + Send + 'static,
203    {
204        unsafe {
205            let res = ffi::gst_bus_add_watch_full(
206                self.to_glib_none().0,
207                glib::ffi::G_PRIORITY_DEFAULT,
208                Some(trampoline_watch::<F>),
209                into_raw_watch(func),
210                Some(destroy_closure_watch::<F>),
211            );
212
213            if res == 0 {
214                Err(glib::bool_error!("Bus already has a watch"))
215            } else {
216                Ok(BusWatchGuard { bus: self.clone() })
217            }
218        }
219    }
220
221    #[doc(alias = "gst_bus_add_watch")]
222    #[doc(alias = "gst_bus_add_watch_full")]
223    pub fn add_watch_local<F>(&self, func: F) -> Result<BusWatchGuard, glib::BoolError>
224    where
225        F: FnMut(&Bus, &Message) -> ControlFlow + 'static,
226    {
227        unsafe {
228            let ctx = glib::MainContext::ref_thread_default();
229            let _acquire = ctx
230                .acquire()
231                .expect("thread default main context already acquired by another thread");
232
233            let res = ffi::gst_bus_add_watch_full(
234                self.to_glib_none().0,
235                glib::ffi::G_PRIORITY_DEFAULT,
236                Some(trampoline_watch_local::<F>),
237                into_raw_watch_local(func),
238                Some(destroy_closure_watch_local::<F>),
239            );
240
241            if res == 0 {
242                Err(glib::bool_error!("Bus already has a watch"))
243            } else {
244                Ok(BusWatchGuard { bus: self.clone() })
245            }
246        }
247    }
248
249    /// Sets the synchronous handler on the bus. The function will be called
250    /// every time a new message is posted on the bus. Note that the function
251    /// will be called in the same thread context as the posting object. This
252    /// function is usually only called by the creator of the bus. Applications
253    /// should handle messages asynchronously using the gst_bus watch and poll
254    /// functions.
255    ///
256    /// Before 1.16.3 it was not possible to replace an existing handler and
257    /// clearing an existing handler with [`None`] was not thread-safe.
258    /// ## `func`
259    /// The handler function to install
260    /// ## `notify`
261    /// called when `user_data` becomes unused
262    #[doc(alias = "gst_bus_set_sync_handler")]
263    pub fn set_sync_handler<F>(&self, func: F)
264    where
265        F: Fn(&Bus, &Message) -> BusSyncReply + Send + Sync + 'static,
266    {
267        unsafe {
268            let bus = self.to_glib_none().0;
269
270            #[allow(clippy::manual_dangling_ptr)]
271            #[cfg(not(feature = "v1_18"))]
272            {
273                static SET_ONCE_QUARK: std::sync::OnceLock<glib::Quark> =
274                    std::sync::OnceLock::new();
275
276                let set_once_quark = SET_ONCE_QUARK
277                    .get_or_init(|| glib::Quark::from_str("gstreamer-rs-sync-handler"));
278
279                // This is not thread-safe before 1.16.3, see
280                // https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/merge_requests/416
281                if crate::version() < (1, 16, 3, 0) {
282                    if !glib::gobject_ffi::g_object_get_qdata(
283                        bus as *mut _,
284                        set_once_quark.into_glib(),
285                    )
286                    .is_null()
287                    {
288                        panic!("Bus sync handler can only be set once");
289                    }
290
291                    glib::gobject_ffi::g_object_set_qdata(
292                        bus as *mut _,
293                        set_once_quark.into_glib(),
294                        1 as *mut _,
295                    );
296                }
297            }
298
299            ffi::gst_bus_set_sync_handler(
300                bus,
301                Some(trampoline_sync::<F>),
302                into_raw_sync(func),
303                Some(destroy_closure_sync::<F>),
304            )
305        }
306    }
307
308    pub fn unset_sync_handler(&self) {
309        #[cfg(not(feature = "v1_18"))]
310        {
311            // This is not thread-safe before 1.16.3, see
312            // https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/merge_requests/416
313            if crate::version() < (1, 16, 3, 0) {
314                return;
315            }
316        }
317
318        unsafe {
319            use std::ptr;
320
321            ffi::gst_bus_set_sync_handler(self.to_glib_none().0, None, ptr::null_mut(), None)
322        }
323    }
324
325    #[doc(alias = "gst_bus_pop")]
326    pub fn iter(&self) -> Iter<'_> {
327        self.iter_timed(Some(crate::ClockTime::ZERO))
328    }
329
330    #[doc(alias = "gst_bus_timed_pop")]
331    pub fn iter_timed(&self, timeout: impl Into<Option<crate::ClockTime>>) -> Iter<'_> {
332        Iter {
333            bus: self,
334            timeout: timeout.into(),
335        }
336    }
337
338    #[doc(alias = "gst_bus_pop_filtered")]
339    pub fn iter_filtered<'a>(
340        &'a self,
341        msg_types: &'a [MessageType],
342    ) -> impl Iterator<Item = Message> + 'a {
343        self.iter_timed_filtered(Some(crate::ClockTime::ZERO), msg_types)
344    }
345
346    #[doc(alias = "gst_bus_timed_pop_filtered")]
347    pub fn iter_timed_filtered<'a>(
348        &'a self,
349        timeout: impl Into<Option<crate::ClockTime>>,
350        msg_types: &'a [MessageType],
351    ) -> impl Iterator<Item = Message> + 'a {
352        self.iter_timed(timeout)
353            .filter(move |msg| msg_types.contains(&msg.type_()))
354    }
355
356    /// Gets a message from the bus whose type matches the message type mask `types`,
357    /// waiting up to the specified timeout (and discarding any messages that do not
358    /// match the mask provided).
359    ///
360    /// If `timeout` is 0, this function behaves like [`pop_filtered()`][Self::pop_filtered()]. If
361    /// `timeout` is `GST_CLOCK_TIME_NONE`, this function will block forever until a
362    /// matching message was posted on the bus.
363    /// ## `timeout`
364    /// a timeout in nanoseconds, or `GST_CLOCK_TIME_NONE` to wait forever
365    /// ## `types`
366    /// message types to take into account, `GST_MESSAGE_ANY` for any type
367    ///
368    /// # Returns
369    ///
370    /// a [`Message`][crate::Message] matching the
371    ///  filter in `types`, or [`None`] if no matching message was found on
372    ///  the bus until the timeout expired.
373    #[doc(alias = "gst_bus_timed_pop_filtered")]
374    pub fn timed_pop_filtered(
375        &self,
376        timeout: impl Into<Option<crate::ClockTime>>,
377        msg_types: &[MessageType],
378    ) -> Option<Message> {
379        // Infinite wait: just loop forever
380        let Some(timeout) = timeout.into() else {
381            loop {
382                let msg = self.timed_pop(None)?;
383                if msg_types.contains(&msg.type_()) {
384                    return Some(msg);
385                }
386            }
387        };
388
389        // Finite timeout
390        let total = timeout;
391        let start = std::time::Instant::now();
392
393        loop {
394            let elapsed = crate::ClockTime::from_nseconds(start.elapsed().as_nanos() as u64);
395
396            // If timeout budget is exhausted, return None
397            let remaining = total.checked_sub(elapsed)?;
398
399            let msg = self.timed_pop(Some(remaining))?;
400
401            if msg_types.contains(&msg.type_()) {
402                return Some(msg);
403            }
404
405            // Discard non-matching messages without restarting the timeout
406        }
407    }
408
409    /// Gets a message matching `type_` from the bus. Will discard all messages on
410    /// the bus that do not match `type_` and that have been posted before the first
411    /// message that does match `type_`. If there is no message matching `type_` on
412    /// the bus, all messages will be discarded. It is not possible to use message
413    /// enums beyond `GST_MESSAGE_EXTENDED` in the `events` mask.
414    /// ## `types`
415    /// message types to take into account
416    ///
417    /// # Returns
418    ///
419    /// the next [`Message`][crate::Message] matching
420    ///  `type_` that is on the bus, or [`None`] if the bus is empty or there
421    ///  is no message matching `type_`.
422    #[doc(alias = "gst_bus_pop_filtered")]
423    pub fn pop_filtered(&self, msg_types: &[MessageType]) -> Option<Message> {
424        loop {
425            let msg = self.pop()?;
426            if msg_types.contains(&msg.type_()) {
427                return Some(msg);
428            }
429        }
430    }
431
432    #[cfg(feature = "futures")]
433    pub fn stream(&self) -> BusStream {
434        BusStream::new(self)
435    }
436
437    #[cfg(feature = "futures")]
438    pub fn stream_filtered<'a>(
439        &self,
440        message_types: &'a [MessageType],
441    ) -> impl FusedStream<Item = Message> + Unpin + Send + 'a + use<'a> {
442        self.stream().filter(move |message| {
443            let message_type = message.type_();
444
445            std::future::ready(message_types.contains(&message_type))
446        })
447    }
448}
449
450#[must_use = "iterators are lazy and do nothing unless consumed"]
451#[derive(Debug)]
452pub struct Iter<'a> {
453    bus: &'a Bus,
454    timeout: Option<crate::ClockTime>,
455}
456
457impl Iterator for Iter<'_> {
458    type Item = Message;
459
460    fn next(&mut self) -> Option<Message> {
461        self.bus.timed_pop(self.timeout)
462    }
463}
464
465// rustdoc-stripper-ignore-next
466/// Manages ownership of the bus watch added to a bus with [`Bus::add_watch`] or [`Bus::add_watch_local`]
467///
468/// When dropped the bus watch is removed from the bus.
469#[derive(Debug)]
470#[must_use = "if unused the bus watch will immediately be removed"]
471pub struct BusWatchGuard {
472    bus: Bus,
473}
474
475impl Drop for BusWatchGuard {
476    fn drop(&mut self) {
477        let _ = self.bus.remove_watch();
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use std::sync::{Arc, Mutex};
484
485    use super::*;
486
487    #[test]
488    fn test_sync_handler() {
489        crate::init().unwrap();
490
491        let bus = Bus::new();
492        let msgs = Arc::new(Mutex::new(Vec::new()));
493        let msgs_clone = msgs.clone();
494        bus.set_sync_handler(move |_, msg| {
495            msgs_clone.lock().unwrap().push(msg.clone());
496            BusSyncReply::Pass
497        });
498
499        bus.post(crate::message::Eos::new()).unwrap();
500
501        let msgs = msgs.lock().unwrap();
502        assert_eq!(msgs.len(), 1);
503        match msgs[0].view() {
504            crate::MessageView::Eos(_) => (),
505            _ => unreachable!(),
506        }
507    }
508}