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::{MainContext, Source, SourceId, source::Priority, translate::*};
8
9impl MainContext {
10    #[doc(alias = "g_main_context_prepare")]
11    pub fn prepare(&self) -> (bool, i32) {
12        unsafe {
13            let mut priority = mem::MaybeUninit::uninit();
14
15            let res = from_glib(ffi::g_main_context_prepare(
16                self.to_glib_none().0,
17                priority.as_mut_ptr(),
18            ));
19            let priority = priority.assume_init();
20            (res, priority)
21        }
22    }
23
24    #[doc(alias = "g_main_context_find_source_by_id")]
25    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
26        unsafe {
27            from_glib_none(ffi::g_main_context_find_source_by_id(
28                self.to_glib_none().0,
29                source_id.as_raw(),
30            ))
31        }
32    }
33
34    // rustdoc-stripper-ignore-next
35    /// Invokes `func` on the main context.
36    ///
37    /// If the current thread is the owner of the main context or the main context currently has no
38    /// owner then `func` will be called directly from inside this function. If this behaviour is
39    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
40    /// [`glib::idle_add`](crate::idle_add) instead.
41    #[doc(alias = "g_main_context_invoke")]
42    pub fn invoke<F>(&self, func: F)
43    where
44        F: FnOnce() + Send + 'static,
45    {
46        self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func);
47    }
48
49    // rustdoc-stripper-ignore-next
50    /// Invokes `func` on the main context with the given priority.
51    ///
52    /// If the current thread is the owner of the main context or the main context currently has no
53    /// owner then `func` will be called directly from inside this function. If this behaviour is
54    /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
55    /// [`glib::idle_add`](crate::idle_add) instead.
56    #[doc(alias = "g_main_context_invoke_full")]
57    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
58    where
59        F: FnOnce() + Send + 'static,
60    {
61        unsafe {
62            self.invoke_unsafe(priority, func);
63        }
64    }
65
66    // rustdoc-stripper-ignore-next
67    /// Invokes `func` on the main context.
68    ///
69    /// Different to `invoke()`, this does not require `func` to be
70    /// `Send` but can only be called from the thread that owns the main context.
71    ///
72    /// This function panics if called from a different thread than the one that
73    /// owns the main context.
74    ///
75    /// Note that this effectively means that `func` is called directly from inside this function
76    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
77    /// be called asynchronously then use [`MainContext::spawn_local`]
78    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
79    pub fn invoke_local<F>(&self, func: F)
80    where
81        F: FnOnce() + 'static,
82    {
83        self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func);
84    }
85
86    // rustdoc-stripper-ignore-next
87    /// Invokes `func` on the main context with the given priority.
88    ///
89    /// Different to `invoke_with_priority()`, this does not require `func` to be
90    /// `Send` but can only be called from the thread that owns the main context.
91    ///
92    /// This function panics if called from a different thread than the one that
93    /// owns the main context.
94    ///
95    /// Note that this effectively means that `func` is called directly from inside this function
96    /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
97    /// be called asynchronously then use [`MainContext::spawn_local`]
98    /// [`glib::idle_add_local`](crate::idle_add_local) instead.
99    #[allow(clippy::if_same_then_else)]
100    pub fn invoke_local_with_priority<F>(&self, _priority: Priority, func: F)
101    where
102        F: FnOnce() + 'static,
103    {
104        // Checks from `g_main_context_invoke_full()`
105        // FIXME: Combine the first two cases somehow
106        if self.is_owner() {
107            func();
108        } else {
109            match self.acquire() {
110                Ok(_acquire) => {
111                    func();
112                }
113                _ => {
114                    panic!("Must be called from a thread that owns the main context");
115                }
116            }
117        }
118    }
119
120    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
121    where
122        F: FnOnce() + 'static,
123    {
124        unsafe {
125            unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
126                unsafe {
127                    let func: &mut Option<F> = &mut *(func as *mut Option<F>);
128                    let func = func
129                        .take()
130                        .expect("MainContext::invoke() closure called multiple times");
131                    func();
132                    ffi::G_SOURCE_REMOVE
133                }
134            }
135            unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
136                unsafe {
137                    let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
138                }
139            }
140            let func = Box::into_raw(Box::new(Some(func)));
141            ffi::g_main_context_invoke_full(
142                self.to_glib_none().0,
143                priority.into_glib(),
144                Some(trampoline::<F>),
145                func as gpointer,
146                Some(destroy_closure::<F>),
147            )
148        }
149    }
150
151    // rustdoc-stripper-ignore-next
152    /// Call closure with the main context configured as the thread default one.
153    ///
154    /// The thread default main context is changed in a panic-safe manner before calling `func` and
155    /// released again afterwards regardless of whether closure panicked or not.
156    ///
157    /// This will fail if the main context is owned already by another thread.
158    #[doc(alias = "g_main_context_push_thread_default")]
159    pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
160        &self,
161        func: F,
162    ) -> Result<R, crate::BoolError> {
163        let _acquire = self.acquire()?;
164        let _thread_default = ThreadDefaultContext::new(self);
165        Ok(func())
166    }
167
168    // rustdoc-stripper-ignore-next
169    /// Acquire ownership of the main context.
170    ///
171    /// Ownership will automatically be released again once the returned acquire guard is dropped.
172    ///
173    /// This will fail if the main context is owned already by another thread.
174    #[doc(alias = "g_main_context_acquire")]
175    pub fn acquire(&self) -> Result<MainContextAcquireGuard<'_>, crate::BoolError> {
176        unsafe {
177            let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
178            if ret {
179                Ok(MainContextAcquireGuard(self))
180            } else {
181                Err(bool_error!(
182                    "Failed to acquire ownership of main context, already acquired by another thread"
183                ))
184            }
185        }
186    }
187}
188
189#[must_use = "if unused the main context will be released immediately"]
190pub struct MainContextAcquireGuard<'a>(&'a MainContext);
191
192impl Drop for MainContextAcquireGuard<'_> {
193    #[doc(alias = "g_main_context_release")]
194    #[inline]
195    fn drop(&mut self) {
196        unsafe {
197            ffi::g_main_context_release(self.0.to_glib_none().0);
198        }
199    }
200}
201
202struct ThreadDefaultContext<'a>(&'a MainContext);
203
204impl ThreadDefaultContext<'_> {
205    fn new(ctx: &MainContext) -> ThreadDefaultContext<'_> {
206        unsafe {
207            ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
208        }
209        ThreadDefaultContext(ctx)
210    }
211}
212
213impl Drop for ThreadDefaultContext<'_> {
214    #[inline]
215    fn drop(&mut self) {
216        unsafe {
217            ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use std::{panic, ptr, thread};
225
226    use super::*;
227
228    #[test]
229    fn test_invoke() {
230        let c = MainContext::new();
231        let l = crate::MainLoop::new(Some(&c), false);
232
233        let l_clone = l.clone();
234        let join_handle = thread::spawn(move || {
235            c.invoke(move || l_clone.quit());
236        });
237
238        l.run();
239
240        join_handle.join().unwrap();
241    }
242
243    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
244        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
245    }
246
247    #[test]
248    fn test_with_thread_default() {
249        let a = MainContext::new();
250        let b = MainContext::new();
251
252        assert!(!is_same_context(&a, &b));
253
254        a.with_thread_default(|| {
255            let t = MainContext::thread_default().unwrap();
256            assert!(is_same_context(&a, &t));
257
258            b.with_thread_default(|| {
259                let t = MainContext::thread_default().unwrap();
260                assert!(is_same_context(&b, &t));
261            })
262            .unwrap();
263
264            let t = MainContext::thread_default().unwrap();
265            assert!(is_same_context(&a, &t));
266        })
267        .unwrap();
268    }
269
270    #[test]
271    fn test_with_thread_default_is_panic_safe() {
272        let a = MainContext::new();
273        let b = MainContext::new();
274
275        assert!(!is_same_context(&a, &b));
276
277        a.with_thread_default(|| {
278            let t = MainContext::thread_default().unwrap();
279            assert!(is_same_context(&a, &t));
280
281            let result = panic::catch_unwind(|| {
282                b.with_thread_default(|| {
283                    panic!();
284                })
285                .unwrap();
286            });
287            assert!(result.is_err());
288
289            let t = MainContext::thread_default().unwrap();
290            assert!(is_same_context(&a, &t));
291        })
292        .unwrap();
293    }
294}