glib/
main_context.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem;
4
5use crate::ffi::{self, gboolean, gpointer};
6
7use crate::{source::Priority, translate::*, MainContext, Source, SourceId};
8
9impl MainContext {
10    /// Prepares to poll sources within a main loop. The resulting information
11    /// for polling is determined by calling `GLib::MainContext::query()`.
12    ///
13    /// You must have successfully acquired the context with
14    /// [`acquire()`][Self::acquire()] before you may call this function.
15    ///
16    /// # Returns
17    ///
18    /// [`true`] if some source is ready to be dispatched
19    ///               prior to polling.
20    ///
21    /// ## `priority`
22    /// location to store priority of highest priority
23    ///            source already ready.
24    #[doc(alias = "g_main_context_prepare")]
25    pub fn prepare(&self) -> (bool, i32) {
26        unsafe {
27            let mut priority = mem::MaybeUninit::uninit();
28
29            let res = from_glib(ffi::g_main_context_prepare(
30                self.to_glib_none().0,
31                priority.as_mut_ptr(),
32            ));
33            let priority = priority.assume_init();
34            (res, priority)
35        }
36    }
37
38    /// Finds a #GSource given a pair of context and ID.
39    ///
40    /// It is a programmer error to attempt to look up a non-existent source.
41    ///
42    /// More specifically: source IDs can be reissued after a source has been
43    /// destroyed and therefore it is never valid to use this function with a
44    /// source ID which may have already been removed.  An example is when
45    /// scheduling an idle to run in another thread with `idle_add()`: the
46    /// idle may already have run and been removed by the time this function
47    /// is called on its (now invalid) source ID.  This source ID may have
48    /// been reissued, leading to the operation being performed against the
49    /// wrong source.
50    /// ## `source_id`
51    /// the source ID, as returned by `GLib::Source::get_id()`.
52    ///
53    /// # Returns
54    ///
55    /// the #GSource
56    #[doc(alias = "g_main_context_find_source_by_id")]
57    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
58        unsafe {
59            from_glib_none(ffi::g_main_context_find_source_by_id(
60                self.to_glib_none().0,
61                source_id.as_raw(),
62            ))
63        }
64    }
65
66    // rustdoc-stripper-ignore-next
67    /// Invokes `func` on the main context.
68    ///
69    /// If the current thread is the owner of the main context or the main context currently has no
70    /// owner then `func` will be called directly from inside this function. If this behaviour is
71    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
72    /// [`glib::idle_add`](crate::idle_add) instead.
73    // rustdoc-stripper-ignore-next-stop
74    /// Invokes a function in such a way that @self is owned during the
75    /// invocation of @function.
76    ///
77    /// If @self is [`None`] then the global-default main context — as
78    /// returned by [`default()`][Self::default()] — is used.
79    ///
80    /// If @self is owned by the current thread, @function is called
81    /// directly.  Otherwise, if @self is the thread-default main context
82    /// of the current thread and [`acquire()`][Self::acquire()] succeeds, then
83    /// @function is called and [`release()`][Self::release()] is called
84    /// afterwards.
85    ///
86    /// In any other case, an idle source is created to call @function and
87    /// that source is attached to @self (presumably to be run in another
88    /// thread).  The idle source is attached with `GLib::PRIORITY_DEFAULT`
89    /// priority.  If you want a different priority, use
90    /// [`invoke_full()`][Self::invoke_full()].
91    ///
92    /// Note that, as with normal idle functions, @function should probably
93    /// return [`false`].  If it returns [`true`], it will be continuously run in a
94    /// loop (and may prevent this call from returning).
95    /// ## `function`
96    /// function to call
97    #[doc(alias = "g_main_context_invoke")]
98    pub fn invoke<F>(&self, func: F)
99    where
100        F: FnOnce() + Send + 'static,
101    {
102        self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func);
103    }
104
105    // rustdoc-stripper-ignore-next
106    /// Invokes `func` on the main context with the given priority.
107    ///
108    /// If the current thread is the owner of the main context or the main context currently has no
109    /// owner then `func` will be called directly from inside this function. If this behaviour is
110    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
111    /// [`glib::idle_add`](crate::idle_add) instead.
112    #[doc(alias = "g_main_context_invoke_full")]
113    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
114    where
115        F: FnOnce() + Send + 'static,
116    {
117        unsafe {
118            self.invoke_unsafe(priority, func);
119        }
120    }
121
122    // rustdoc-stripper-ignore-next
123    /// Invokes `func` on the main context.
124    ///
125    /// Different to `invoke()`, this does not require `func` to be
126    /// `Send` but can only be called from the thread that owns the main context.
127    ///
128    /// This function panics if called from a different thread than the one that
129    /// owns the main context.
130    ///
131    /// Note that this effectively means that `func` is called directly from inside this function
132    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
133    /// be called asynchronously then use [`MainContext::spawn_local`]
134    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
135    pub fn invoke_local<F>(&self, func: F)
136    where
137        F: FnOnce() + 'static,
138    {
139        self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func);
140    }
141
142    // rustdoc-stripper-ignore-next
143    /// Invokes `func` on the main context with the given priority.
144    ///
145    /// Different to `invoke_with_priority()`, this does not require `func` to be
146    /// `Send` but can only be called from the thread that owns the main context.
147    ///
148    /// This function panics if called from a different thread than the one that
149    /// owns the main context.
150    ///
151    /// Note that this effectively means that `func` is called directly from inside this function
152    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
153    /// be called asynchronously then use [`MainContext::spawn_local`]
154    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
155    #[allow(clippy::if_same_then_else)]
156    pub fn invoke_local_with_priority<F>(&self, _priority: Priority, func: F)
157    where
158        F: FnOnce() + 'static,
159    {
160        // Checks from `g_main_context_invoke_full()`
161        // FIXME: Combine the first two cases somehow
162        if self.is_owner() {
163            func();
164        } else if let Ok(_acquire) = self.acquire() {
165            func();
166        } else {
167            panic!("Must be called from a thread that owns the main context");
168        }
169    }
170
171    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
172    where
173        F: FnOnce() + 'static,
174    {
175        unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
176            let func: &mut Option<F> = &mut *(func as *mut Option<F>);
177            let func = func
178                .take()
179                .expect("MainContext::invoke() closure called multiple times");
180            func();
181            ffi::G_SOURCE_REMOVE
182        }
183        unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
184            let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
185        }
186        let func = Box::into_raw(Box::new(Some(func)));
187        ffi::g_main_context_invoke_full(
188            self.to_glib_none().0,
189            priority.into_glib(),
190            Some(trampoline::<F>),
191            func as gpointer,
192            Some(destroy_closure::<F>),
193        )
194    }
195
196    // rustdoc-stripper-ignore-next
197    /// Call closure with the main context configured as the thread default one.
198    ///
199    /// The thread default main context is changed in a panic-safe manner before calling `func` and
200    /// released again afterwards regardless of whether closure panicked or not.
201    ///
202    /// This will fail if the main context is owned already by another thread.
203    #[doc(alias = "g_main_context_push_thread_default")]
204    pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
205        &self,
206        func: F,
207    ) -> Result<R, crate::BoolError> {
208        let _acquire = self.acquire()?;
209        let _thread_default = ThreadDefaultContext::new(self);
210        Ok(func())
211    }
212
213    // rustdoc-stripper-ignore-next
214    /// Acquire ownership of the main context.
215    ///
216    /// Ownership will automatically be released again once the returned acquire guard is dropped.
217    ///
218    /// This will fail if the main context is owned already by another thread.
219    // rustdoc-stripper-ignore-next-stop
220    /// Tries to become the owner of the specified context.
221    /// If some other thread is the owner of the context,
222    /// returns [`false`] immediately. Ownership is properly
223    /// recursive: the owner can require ownership again
224    /// and will release ownership when [`release()`][Self::release()]
225    /// is called as many times as [`acquire()`][Self::acquire()].
226    ///
227    /// You must be the owner of a context before you
228    /// can call [`prepare()`][Self::prepare()], `GLib::MainContext::query()`,
229    /// `GLib::MainContext::check()`, [`dispatch()`][Self::dispatch()],
230    /// [`release()`][Self::release()].
231    ///
232    /// Since 2.76 @self can be [`None`] to use the global-default
233    /// main context.
234    ///
235    /// # Returns
236    ///
237    /// [`true`] if the operation succeeded, and
238    ///   this thread is now the owner of @self.
239    #[doc(alias = "g_main_context_acquire")]
240    pub fn acquire(&self) -> Result<MainContextAcquireGuard, crate::BoolError> {
241        unsafe {
242            let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
243            if ret {
244                Ok(MainContextAcquireGuard(self))
245            } else {
246                Err(bool_error!("Failed to acquire ownership of main context, already acquired by another thread"))
247            }
248        }
249    }
250}
251
252#[must_use = "if unused the main context will be released immediately"]
253pub struct MainContextAcquireGuard<'a>(&'a MainContext);
254
255impl Drop for MainContextAcquireGuard<'_> {
256    #[doc(alias = "g_main_context_release")]
257    #[inline]
258    fn drop(&mut self) {
259        unsafe {
260            ffi::g_main_context_release(self.0.to_glib_none().0);
261        }
262    }
263}
264
265struct ThreadDefaultContext<'a>(&'a MainContext);
266
267impl ThreadDefaultContext<'_> {
268    fn new(ctx: &MainContext) -> ThreadDefaultContext {
269        unsafe {
270            ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
271        }
272        ThreadDefaultContext(ctx)
273    }
274}
275
276impl Drop for ThreadDefaultContext<'_> {
277    #[inline]
278    fn drop(&mut self) {
279        unsafe {
280            ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use std::{panic, ptr, thread};
288
289    use super::*;
290
291    #[test]
292    fn test_invoke() {
293        let c = MainContext::new();
294        let l = crate::MainLoop::new(Some(&c), false);
295
296        let l_clone = l.clone();
297        let join_handle = thread::spawn(move || {
298            c.invoke(move || l_clone.quit());
299        });
300
301        l.run();
302
303        join_handle.join().unwrap();
304    }
305
306    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
307        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
308    }
309
310    #[test]
311    fn test_with_thread_default() {
312        let a = MainContext::new();
313        let b = MainContext::new();
314
315        assert!(!is_same_context(&a, &b));
316
317        a.with_thread_default(|| {
318            let t = MainContext::thread_default().unwrap();
319            assert!(is_same_context(&a, &t));
320
321            b.with_thread_default(|| {
322                let t = MainContext::thread_default().unwrap();
323                assert!(is_same_context(&b, &t));
324            })
325            .unwrap();
326
327            let t = MainContext::thread_default().unwrap();
328            assert!(is_same_context(&a, &t));
329        })
330        .unwrap();
331    }
332
333    #[test]
334    fn test_with_thread_default_is_panic_safe() {
335        let a = MainContext::new();
336        let b = MainContext::new();
337
338        assert!(!is_same_context(&a, &b));
339
340        a.with_thread_default(|| {
341            let t = MainContext::thread_default().unwrap();
342            assert!(is_same_context(&a, &t));
343
344            let result = panic::catch_unwind(|| {
345                b.with_thread_default(|| {
346                    panic!();
347                })
348                .unwrap();
349            });
350            assert!(result.is_err());
351
352            let t = MainContext::thread_default().unwrap();
353            assert!(is_same_context(&a, &t));
354        })
355        .unwrap();
356    }
357}