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    #[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 if let Ok(_acquire) = self.acquire() {
109            func();
110        } else {
111            panic!("Must be called from a thread that owns the main context");
112        }
113    }
114
115    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
116    where
117        F: FnOnce() + 'static,
118    {
119        unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
120            let func: &mut Option<F> = &mut *(func as *mut Option<F>);
121            let func = func
122                .take()
123                .expect("MainContext::invoke() closure called multiple times");
124            func();
125            ffi::G_SOURCE_REMOVE
126        }
127        unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
128            let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
129        }
130        let func = Box::into_raw(Box::new(Some(func)));
131        ffi::g_main_context_invoke_full(
132            self.to_glib_none().0,
133            priority.into_glib(),
134            Some(trampoline::<F>),
135            func as gpointer,
136            Some(destroy_closure::<F>),
137        )
138    }
139
140    // rustdoc-stripper-ignore-next
141    /// Call closure with the main context configured as the thread default one.
142    ///
143    /// The thread default main context is changed in a panic-safe manner before calling `func` and
144    /// released again afterwards regardless of whether closure panicked or not.
145    ///
146    /// This will fail if the main context is owned already by another thread.
147    #[doc(alias = "g_main_context_push_thread_default")]
148    pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
149        &self,
150        func: F,
151    ) -> Result<R, crate::BoolError> {
152        let _acquire = self.acquire()?;
153        let _thread_default = ThreadDefaultContext::new(self);
154        Ok(func())
155    }
156
157    // rustdoc-stripper-ignore-next
158    /// Acquire ownership of the main context.
159    ///
160    /// Ownership will automatically be released again once the returned acquire guard is dropped.
161    ///
162    /// This will fail if the main context is owned already by another thread.
163    #[doc(alias = "g_main_context_acquire")]
164    pub fn acquire(&self) -> Result<MainContextAcquireGuard<'_>, crate::BoolError> {
165        unsafe {
166            let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
167            if ret {
168                Ok(MainContextAcquireGuard(self))
169            } else {
170                Err(bool_error!("Failed to acquire ownership of main context, already acquired by another thread"))
171            }
172        }
173    }
174}
175
176#[must_use = "if unused the main context will be released immediately"]
177pub struct MainContextAcquireGuard<'a>(&'a MainContext);
178
179impl Drop for MainContextAcquireGuard<'_> {
180    #[doc(alias = "g_main_context_release")]
181    #[inline]
182    fn drop(&mut self) {
183        unsafe {
184            ffi::g_main_context_release(self.0.to_glib_none().0);
185        }
186    }
187}
188
189struct ThreadDefaultContext<'a>(&'a MainContext);
190
191impl ThreadDefaultContext<'_> {
192    fn new(ctx: &MainContext) -> ThreadDefaultContext<'_> {
193        unsafe {
194            ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
195        }
196        ThreadDefaultContext(ctx)
197    }
198}
199
200impl Drop for ThreadDefaultContext<'_> {
201    #[inline]
202    fn drop(&mut self) {
203        unsafe {
204            ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
205        }
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use std::{panic, ptr, thread};
212
213    use super::*;
214
215    #[test]
216    fn test_invoke() {
217        let c = MainContext::new();
218        let l = crate::MainLoop::new(Some(&c), false);
219
220        let l_clone = l.clone();
221        let join_handle = thread::spawn(move || {
222            c.invoke(move || l_clone.quit());
223        });
224
225        l.run();
226
227        join_handle.join().unwrap();
228    }
229
230    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
231        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
232    }
233
234    #[test]
235    fn test_with_thread_default() {
236        let a = MainContext::new();
237        let b = MainContext::new();
238
239        assert!(!is_same_context(&a, &b));
240
241        a.with_thread_default(|| {
242            let t = MainContext::thread_default().unwrap();
243            assert!(is_same_context(&a, &t));
244
245            b.with_thread_default(|| {
246                let t = MainContext::thread_default().unwrap();
247                assert!(is_same_context(&b, &t));
248            })
249            .unwrap();
250
251            let t = MainContext::thread_default().unwrap();
252            assert!(is_same_context(&a, &t));
253        })
254        .unwrap();
255    }
256
257    #[test]
258    fn test_with_thread_default_is_panic_safe() {
259        let a = MainContext::new();
260        let b = MainContext::new();
261
262        assert!(!is_same_context(&a, &b));
263
264        a.with_thread_default(|| {
265            let t = MainContext::thread_default().unwrap();
266            assert!(is_same_context(&a, &t));
267
268            let result = panic::catch_unwind(|| {
269                b.with_thread_default(|| {
270                    panic!();
271                })
272                .unwrap();
273            });
274            assert!(result.is_err());
275
276            let t = MainContext::thread_default().unwrap();
277            assert!(is_same_context(&a, &t));
278        })
279        .unwrap();
280    }
281}