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::{MainContext, Source, SourceId, source::Priority, translate::*};
8
9impl MainContext {
10 /// Prepares to poll sources within a main loop.
11 ///
12 /// The resulting information
13 /// for polling is determined by calling `GLib::MainContext::query()`.
14 ///
15 /// You must have successfully acquired the context with
16 /// [`acquire()`][Self::acquire()] before you may call this function.
17 ///
18 /// # Returns
19 ///
20 /// true if some source is ready to be dispatched prior to polling,
21 /// false otherwise
22 ///
23 /// ## `priority`
24 /// location to store priority of highest priority
25 /// source already ready
26 #[doc(alias = "g_main_context_prepare")]
27 pub fn prepare(&self) -> (bool, i32) {
28 unsafe {
29 let mut priority = mem::MaybeUninit::uninit();
30
31 let res = from_glib(ffi::g_main_context_prepare(
32 self.to_glib_none().0,
33 priority.as_mut_ptr(),
34 ));
35 let priority = priority.assume_init();
36 (res, priority)
37 }
38 }
39
40 /// Finds a [`Source`][crate::Source] given a pair of context and ID.
41 ///
42 /// It is a programmer error to attempt to look up a non-existent source.
43 ///
44 /// More specifically: source IDs can be reissued after a source has been
45 /// destroyed and therefore it is never valid to use this function with a
46 /// source ID which may have already been removed. An example is when
47 /// scheduling an idle to run in another thread with `idle_add()`: the
48 /// idle may already have run and been removed by the time this function
49 /// is called on its (now invalid) source ID. This source ID may have
50 /// been reissued, leading to the operation being performed against the
51 /// wrong source.
52 /// ## `source_id`
53 /// the source ID, as returned by `GLib::Source::get_id()`
54 ///
55 /// # Returns
56 ///
57 /// the source
58 #[doc(alias = "g_main_context_find_source_by_id")]
59 pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
60 unsafe {
61 from_glib_none(ffi::g_main_context_find_source_by_id(
62 self.to_glib_none().0,
63 source_id.as_raw(),
64 ))
65 }
66 }
67
68 // rustdoc-stripper-ignore-next
69 /// Invokes `func` on the main context.
70 ///
71 /// If the current thread is the owner of the main context or the main context currently has no
72 /// owner then `func` will be called directly from inside this function. If this behaviour is
73 /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
74 /// [`glib::idle_add`](crate::idle_add) instead.
75 // rustdoc-stripper-ignore-next-stop
76 /// Invokes a function in such a way that @self is owned during the
77 /// invocation of @function.
78 ///
79 /// If @self is `NULL` then the global-default main context — as
80 /// returned by [`default()`][Self::default()] — is used.
81 ///
82 /// If @self is owned by the current thread, @function is called
83 /// directly. Otherwise, if @self is the thread-default main context
84 /// of the current thread and [`acquire()`][Self::acquire()] succeeds,
85 /// then @function is called and [`release()`][Self::release()] is called
86 /// afterwards.
87 ///
88 /// In any other case, an idle source is created to call @function and
89 /// that source is attached to @self (presumably to be run in another
90 /// thread). The idle source is attached with `GLib::PRIORITY_DEFAULT`
91 /// priority. If you want a different priority, use
92 /// [`invoke_full()`][Self::invoke_full()].
93 ///
94 /// Note that, as with normal idle functions, @function should probably return
95 /// `GLib::SOURCE_REMOVE`. If it returns `GLib::SOURCE_CONTINUE`, it
96 /// will be continuously run in a loop (and may prevent this call from returning).
97 /// ## `function`
98 /// function to call
99 #[doc(alias = "g_main_context_invoke")]
100 pub fn invoke<F>(&self, func: F)
101 where
102 F: FnOnce() + Send + 'static,
103 {
104 self.invoke_with_priority(crate::Priority::DEFAULT_IDLE, func);
105 }
106
107 // rustdoc-stripper-ignore-next
108 /// Invokes `func` on the main context with the given priority.
109 ///
110 /// If the current thread is the owner of the main context or the main context currently has no
111 /// owner then `func` will be called directly from inside this function. If this behaviour is
112 /// not desired and `func` should always be called asynchronously then use [`MainContext::spawn`]
113 /// [`glib::idle_add`](crate::idle_add) instead.
114 #[doc(alias = "g_main_context_invoke_full")]
115 pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
116 where
117 F: FnOnce() + Send + 'static,
118 {
119 unsafe {
120 self.invoke_unsafe(priority, func);
121 }
122 }
123
124 // rustdoc-stripper-ignore-next
125 /// Invokes `func` on the main context.
126 ///
127 /// Different to `invoke()`, this does not require `func` to be
128 /// `Send` but can only be called from the thread that owns the main context.
129 ///
130 /// This function panics if called from a different thread than the one that
131 /// owns the main context.
132 ///
133 /// Note that this effectively means that `func` is called directly from inside this function
134 /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
135 /// be called asynchronously then use [`MainContext::spawn_local`]
136 /// [`glib::idle_add_local`](crate::idle_add_local) instead.
137 pub fn invoke_local<F>(&self, func: F)
138 where
139 F: FnOnce() + 'static,
140 {
141 self.invoke_local_with_priority(crate::Priority::DEFAULT_IDLE, func);
142 }
143
144 // rustdoc-stripper-ignore-next
145 /// Invokes `func` on the main context with the given priority.
146 ///
147 /// Different to `invoke_with_priority()`, this does not require `func` to be
148 /// `Send` but can only be called from the thread that owns the main context.
149 ///
150 /// This function panics if called from a different thread than the one that
151 /// owns the main context.
152 ///
153 /// Note that this effectively means that `func` is called directly from inside this function
154 /// or otherwise panics immediately. If this behaviour is not desired and `func` should always
155 /// be called asynchronously then use [`MainContext::spawn_local`]
156 /// [`glib::idle_add_local`](crate::idle_add_local) instead.
157 #[allow(clippy::if_same_then_else)]
158 pub fn invoke_local_with_priority<F>(&self, _priority: Priority, func: F)
159 where
160 F: FnOnce() + 'static,
161 {
162 // Checks from `g_main_context_invoke_full()`
163 // FIXME: Combine the first two cases somehow
164 if self.is_owner() {
165 func();
166 } else {
167 match self.acquire() {
168 Ok(_acquire) => {
169 func();
170 }
171 _ => {
172 panic!("Must be called from a thread that owns the main context");
173 }
174 }
175 }
176 }
177
178 unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
179 where
180 F: FnOnce() + 'static,
181 {
182 unsafe {
183 unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
184 unsafe {
185 let func: &mut Option<F> = &mut *(func as *mut Option<F>);
186 let func = func
187 .take()
188 .expect("MainContext::invoke() closure called multiple times");
189 func();
190 ffi::G_SOURCE_REMOVE
191 }
192 }
193 unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
194 unsafe {
195 let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
196 }
197 }
198 let func = Box::into_raw(Box::new(Some(func)));
199 ffi::g_main_context_invoke_full(
200 self.to_glib_none().0,
201 priority.into_glib(),
202 Some(trampoline::<F>),
203 func as gpointer,
204 Some(destroy_closure::<F>),
205 )
206 }
207 }
208
209 // rustdoc-stripper-ignore-next
210 /// Call closure with the main context configured as the thread default one.
211 ///
212 /// The thread default main context is changed in a panic-safe manner before calling `func` and
213 /// released again afterwards regardless of whether closure panicked or not.
214 ///
215 /// This will fail if the main context is owned already by another thread.
216 #[doc(alias = "g_main_context_push_thread_default")]
217 pub fn with_thread_default<R, F: FnOnce() -> R + Sized>(
218 &self,
219 func: F,
220 ) -> Result<R, crate::BoolError> {
221 let _acquire = self.acquire()?;
222 let _thread_default = ThreadDefaultContext::new(self);
223 Ok(func())
224 }
225
226 // rustdoc-stripper-ignore-next
227 /// Acquire ownership of the main context.
228 ///
229 /// Ownership will automatically be released again once the returned acquire guard is dropped.
230 ///
231 /// This will fail if the main context is owned already by another thread.
232 // rustdoc-stripper-ignore-next-stop
233 /// Tries to become the owner of the specified context.
234 ///
235 /// If some other thread is the owner of the context,
236 /// returns false immediately. Ownership is properly
237 /// recursive: the owner can require ownership again
238 /// and will release ownership when [`release()`][Self::release()]
239 /// is called as many times as [`acquire()`][Self::acquire()].
240 ///
241 /// You must be the owner of a context before you
242 /// can call [`prepare()`][Self::prepare()], `GLib::MainContext::query()`,
243 /// `GLib::MainContext::check()`, [`dispatch()`][Self::dispatch()],
244 /// [`release()`][Self::release()].
245 ///
246 /// Since 2.76 @self can be `NULL` to use the global-default
247 /// main context.
248 ///
249 /// # Returns
250 ///
251 /// true if this thread is now the owner of @self, false otherwise
252 #[doc(alias = "g_main_context_acquire")]
253 pub fn acquire(&self) -> Result<MainContextAcquireGuard<'_>, crate::BoolError> {
254 unsafe {
255 let ret: bool = from_glib(ffi::g_main_context_acquire(self.to_glib_none().0));
256 if ret {
257 Ok(MainContextAcquireGuard(self))
258 } else {
259 Err(bool_error!(
260 "Failed to acquire ownership of main context, already acquired by another thread"
261 ))
262 }
263 }
264 }
265}
266
267#[must_use = "if unused the main context will be released immediately"]
268pub struct MainContextAcquireGuard<'a>(&'a MainContext);
269
270impl Drop for MainContextAcquireGuard<'_> {
271 #[doc(alias = "g_main_context_release")]
272 #[inline]
273 fn drop(&mut self) {
274 unsafe {
275 ffi::g_main_context_release(self.0.to_glib_none().0);
276 }
277 }
278}
279
280struct ThreadDefaultContext<'a>(&'a MainContext);
281
282impl ThreadDefaultContext<'_> {
283 fn new(ctx: &MainContext) -> ThreadDefaultContext<'_> {
284 unsafe {
285 ffi::g_main_context_push_thread_default(ctx.to_glib_none().0);
286 }
287 ThreadDefaultContext(ctx)
288 }
289}
290
291impl Drop for ThreadDefaultContext<'_> {
292 #[inline]
293 fn drop(&mut self) {
294 unsafe {
295 ffi::g_main_context_pop_thread_default(self.0.to_glib_none().0);
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use std::{panic, ptr, thread};
303
304 use super::*;
305
306 #[test]
307 fn test_invoke() {
308 let c = MainContext::new();
309 let l = crate::MainLoop::new(Some(&c), false);
310
311 let l_clone = l.clone();
312 let join_handle = thread::spawn(move || {
313 c.invoke(move || l_clone.quit());
314 });
315
316 l.run();
317
318 join_handle.join().unwrap();
319 }
320
321 fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
322 ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
323 }
324
325 #[test]
326 fn test_with_thread_default() {
327 let a = MainContext::new();
328 let b = MainContext::new();
329
330 assert!(!is_same_context(&a, &b));
331
332 a.with_thread_default(|| {
333 let t = MainContext::thread_default().unwrap();
334 assert!(is_same_context(&a, &t));
335
336 b.with_thread_default(|| {
337 let t = MainContext::thread_default().unwrap();
338 assert!(is_same_context(&b, &t));
339 })
340 .unwrap();
341
342 let t = MainContext::thread_default().unwrap();
343 assert!(is_same_context(&a, &t));
344 })
345 .unwrap();
346 }
347
348 #[test]
349 fn test_with_thread_default_is_panic_safe() {
350 let a = MainContext::new();
351 let b = MainContext::new();
352
353 assert!(!is_same_context(&a, &b));
354
355 a.with_thread_default(|| {
356 let t = MainContext::thread_default().unwrap();
357 assert!(is_same_context(&a, &t));
358
359 let result = panic::catch_unwind(|| {
360 b.with_thread_default(|| {
361 panic!();
362 })
363 .unwrap();
364 });
365 assert!(result.is_err());
366
367 let t = MainContext::thread_default().unwrap();
368 assert!(is_same_context(&a, &t));
369 })
370 .unwrap();
371 }
372}