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