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