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 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: Sized>(&self, func: F) -> Result<R, crate::BoolError> |
149 | where |
150 | F: FnOnce() -> R, |
151 | { |
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<'a> Drop for MainContextAcquireGuard<'a> { |
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<'a> ThreadDefaultContext<'a> { |
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<'a> Drop for ThreadDefaultContext<'a> { |
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 | thread::spawn(move || { |
222 | c.invoke(move || l_clone.quit()); |
223 | }); |
224 | |
225 | l.run(); |
226 | } |
227 | |
228 | fn is_same_context(a: &MainContext, b: &MainContext) -> bool { |
229 | ptr::eq(a.to_glib_none().0, b.to_glib_none().0) |
230 | } |
231 | |
232 | #[test ] |
233 | fn test_with_thread_default() { |
234 | let a = MainContext::new(); |
235 | let b = MainContext::new(); |
236 | |
237 | assert!(!is_same_context(&a, &b)); |
238 | |
239 | a.with_thread_default(|| { |
240 | let t = MainContext::thread_default().unwrap(); |
241 | assert!(is_same_context(&a, &t)); |
242 | |
243 | b.with_thread_default(|| { |
244 | let t = MainContext::thread_default().unwrap(); |
245 | assert!(is_same_context(&b, &t)); |
246 | }) |
247 | .unwrap(); |
248 | |
249 | let t = MainContext::thread_default().unwrap(); |
250 | assert!(is_same_context(&a, &t)); |
251 | }) |
252 | .unwrap(); |
253 | } |
254 | |
255 | #[test ] |
256 | fn test_with_thread_default_is_panic_safe() { |
257 | let a = MainContext::new(); |
258 | let b = MainContext::new(); |
259 | |
260 | assert!(!is_same_context(&a, &b)); |
261 | |
262 | a.with_thread_default(|| { |
263 | let t = MainContext::thread_default().unwrap(); |
264 | assert!(is_same_context(&a, &t)); |
265 | |
266 | let result = panic::catch_unwind(|| { |
267 | b.with_thread_default(|| { |
268 | panic!(); |
269 | }) |
270 | .unwrap(); |
271 | }); |
272 | assert!(result.is_err()); |
273 | |
274 | let t = MainContext::thread_default().unwrap(); |
275 | assert!(is_same_context(&a, &t)); |
276 | }) |
277 | .unwrap(); |
278 | } |
279 | } |
280 | |