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