1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem;
4
5use 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: 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"]
177pub struct MainContextAcquireGuard<'a>(&'a MainContext);
178
179impl<'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
189struct ThreadDefaultContext<'a>(&'a MainContext);
190
191impl<'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
200impl<'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)]
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 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