| 1 | // Take a look at the license at the top of the repository in the LICENSE file. |
| 2 | |
| 3 | use std::mem; |
| 4 | |
| 5 | use crate::ffi::{self, gboolean, gpointer}; |
| 6 | |
| 7 | use crate::{source::Priority, translate::*, MainContext, Source, SourceId}; |
| 8 | |
| 9 | impl 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" ] |
| 177 | pub struct MainContextAcquireGuard<'a>(&'a MainContext); |
| 178 | |
| 179 | impl 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 | |
| 189 | struct ThreadDefaultContext<'a>(&'a MainContext); |
| 190 | |
| 191 | impl ThreadDefaultContext<'_> { |
| 192 | fn new(ctx: &MainContext) -> ThreadDefaultContext { |
| 193 | unsafe { |
| 194 | ffi::g_main_context_push_thread_default(context:ctx.to_glib_none().0); |
| 195 | } |
| 196 | ThreadDefaultContext(ctx) |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | impl 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)] |
| 210 | mod 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 | } |
| 282 | |