// Take a look at the license at the top of the repository in the LICENSE file. use std::mem; use ffi::{self, gboolean, gpointer}; use crate::{source::Priority, translate::*, MainContext, Source, SourceId}; impl MainContext { #[doc(alias = "g_main_context_prepare")] pub fn prepare(&self) -> (bool, i32) { unsafe { let mut priority = mem::MaybeUninit::uninit(); let res = from_glib(ffi::g_main_context_prepare( self.to_glib_none().0, priority.as_mut_ptr(), )); let priority = priority.assume_init(); (res, priority) } } #[doc(alias = "g_main_context_find_source_by_id")] pub fn find_source_by_id(&self, source_id: &SourceId) -> Option { unsafe { from_glib_none(ffi::g_main_context_find_source_by_id( self.to_glib_none().0, source_id.as_raw(), )) } } // rustdoc-stripper-ignore-next /// Invokes `func` on the main context. /// /// If the current thread is the owner of the main context or the main context currently has no /// owner then `func` will be called directly from inside this function. If this behaviour is /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`] /// [`glib::idle_add`](crate::idle_add) instead. #[doc(alias = "g_main_context_invoke")] pub fn invoke(&self, func: F) where F: FnOnce() + Send + 'static, { self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func); } // rustdoc-stripper-ignore-next /// Invokes `func` on the main context with the given priority. /// /// If the current thread is the owner of the main context or the main context currently has no /// owner then `func` will be called directly from inside this function. If this behaviour is /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`] /// [`glib::idle_add`](crate::idle_add) instead. #[doc(alias = "g_main_context_invoke_full")] pub fn invoke_with_priority(&self, priority: Priority, func: F) where F: FnOnce() + Send + 'static, { unsafe { self.invoke_unsafe(priority, func); } } // rustdoc-stripper-ignore-next /// Invokes `func` on the main context. /// /// Different to `invoke()`, this does not require `func` to be /// `Send` but can only be called from the thread that owns the main context. /// /// This function panics if called from a different thread than the one that /// owns the main context. /// /// Note that this effectively means that `func` is called directly from inside this function /// or otherwise panics immediately. If this behaviour is not desired and `func` should always /// be called asynchronously then use [`MainContext::spawn_local`] /// [`glib::idle_add_local`](crate::idle_add_local) instead. pub fn invoke_local(&self, func: F) where F: FnOnce() + 'static, { self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func); } // rustdoc-stripper-ignore-next /// Invokes `func` on the main context with the given priority. /// /// Different to `invoke_with_priority()`, this does not require `func` to be /// `Send` but can only be called from the thread that owns the main context. /// /// This function panics if called from a different thread than the one that /// owns the main context. /// /// Note that this effectively means that `func` is called directly from inside this function /// or otherwise panics immediately. If this behaviour is not desired and `func` should always /// be called asynchronously then use [`MainContext::spawn_local`] /// [`glib::idle_add_local`](crate::idle_add_local) instead. #[allow(clippy::if_same_then_else)] pub fn invoke_local_with_priority(&self, _priority: Priority, func: F) where F: FnOnce() + 'static, { // Checks from `g_main_context_invoke_full()` // FIXME: Combine the first two cases somehow if self.is_owner() { func(); } else if let Ok(_acquire) = self.acquire() { func(); } else { panic!("Must be called from a thread that owns the main context"); } } unsafe fn invoke_unsafe(&self, priority: Priority, func: F) where F: FnOnce() + 'static, { unsafe extern "C" fn trampoline(func: gpointer) -> gboolean { let func: &mut Option = &mut *(func as *mut Option); let func = func .take() .expect("MainContext::invoke() closure called multiple times"); func(); ffi::G_SOURCE_REMOVE } unsafe extern "C" fn destroy_closure(ptr: gpointer) { let _ = Box::>::from_raw(ptr as *mut _); } let func = Box::into_raw(Box::new(Some(func))); ffi::g_main_context_invoke_full( self.to_glib_none().0, priority.into_glib(), Some(trampoline::), func as gpointer, Some(destroy_closure::), ) } // rustdoc-stripper-ignore-next /// Call closure with the main context configured as the thread default one. /// /// The thread default main context is changed in a panic-safe manner before calling `func` and /// released again afterwards regardless of whether closure panicked or not. /// /// This will fail if the main context is owned already by another thread. #[doc(alias = "g_main_context_push_thread_default")] pub fn with_thread_default(&self, func: F) -> Result where F: FnOnce() -> R, { let _acquire = self.acquire()?; let _thread_default = ThreadDefaultContext::new(self); Ok(func()) } // rustdoc-stripper-ignore-next /// Acquire ownership of the main context. /// /// Ownership will automatically be released again once the returned acquire guard is dropped. /// /// This will fail if the main context is owned already by another thread. #[doc(alias = "g_main_context_acquire")] pub fn acquire(&self) -> Result { unsafe { let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0)); if ret { Ok(MainContextAcquireGuard(self)) } else { Err(bool_error!("Failed to acquire ownership of main context, already acquired by another thread")) } } } } #[must_use = "if unused the main context will be released immediately"] pub struct MainContextAcquireGuard<'a>(&'a MainContext); impl<'a> Drop for MainContextAcquireGuard<'a> { #[doc(alias = "g_main_context_release")] #[inline] fn drop(&mut self) { unsafe { ffi::g_main_context_release(self.0.to_glib_none().0); } } } struct ThreadDefaultContext<'a>(&'a MainContext); impl<'a> ThreadDefaultContext<'a> { fn new(ctx: &MainContext) -> ThreadDefaultContext { unsafe { ffi::g_main_context_push_thread_default(ctx.to_glib_none().0); } ThreadDefaultContext(ctx) } } impl<'a> Drop for ThreadDefaultContext<'a> { #[inline] fn drop(&mut self) { unsafe { ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0); } } } #[cfg(test)] mod tests { use std::{panic, ptr, thread}; use super::*; #[test] fn test_invoke() { let c = MainContext::new(); let l = crate::MainLoop::new(Some(&c), false); let l_clone = l.clone(); thread::spawn(move || { c.invoke(move || l_clone.quit()); }); l.run(); } fn is_same_context(a: &MainContext, b: &MainContext) -> bool { ptr::eq(a.to_glib_none().0, b.to_glib_none().0) } #[test] fn test_with_thread_default() { let a = MainContext::new(); let b = MainContext::new(); assert!(!is_same_context(&a, &b)); a.with_thread_default(|| { let t = MainContext::thread_default().unwrap(); assert!(is_same_context(&a, &t)); b.with_thread_default(|| { let t = MainContext::thread_default().unwrap(); assert!(is_same_context(&b, &t)); }) .unwrap(); let t = MainContext::thread_default().unwrap(); assert!(is_same_context(&a, &t)); }) .unwrap(); } #[test] fn test_with_thread_default_is_panic_safe() { let a = MainContext::new(); let b = MainContext::new(); assert!(!is_same_context(&a, &b)); a.with_thread_default(|| { let t = MainContext::thread_default().unwrap(); assert!(is_same_context(&a, &t)); let result = panic::catch_unwind(|| { b.with_thread_default(|| { panic!(); }) .unwrap(); }); assert!(result.is_err()); let t = MainContext::thread_default().unwrap(); assert!(is_same_context(&a, &t)); }) .unwrap(); } }