glib/
main_context.rs

1// Copyright 2015-2016, The Gtk-rs Project Developers.
2// See the COPYRIGHT file at the top-level directory of this distribution.
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5use glib_sys::{self, gboolean, gpointer};
6use source::Priority;
7use std::mem;
8use translate::*;
9use MainContext;
10use Source;
11use SourceId;
12
13impl MainContext {
14    pub fn prepare(&self) -> (bool, i32) {
15        unsafe {
16            let mut priority = mem::uninitialized();
17
18            let res = from_glib(glib_sys::g_main_context_prepare(
19                self.to_glib_none().0,
20                &mut priority,
21            ));
22
23            (res, priority)
24        }
25    }
26
27    pub fn find_source_by_id(&self, source_id: &SourceId) -> Option<Source> {
28        unsafe {
29            from_glib_none(glib_sys::g_main_context_find_source_by_id(
30                self.to_glib_none().0,
31                source_id.to_glib(),
32            ))
33        }
34    }
35
36    /// Invokes `func` on the main context.
37    pub fn invoke<F>(&self, func: F)
38    where
39        F: FnOnce() + Send + 'static,
40    {
41        self.invoke_with_priority(::PRIORITY_DEFAULT_IDLE, func);
42    }
43
44    /// Invokes `func` on the main context with the given priority.
45    pub fn invoke_with_priority<F>(&self, priority: Priority, func: F)
46    where
47        F: FnOnce() + Send + 'static,
48    {
49        unsafe {
50            self.invoke_unsafe(priority, func);
51        }
52    }
53
54    /// Invokes `func` on the main context.
55    ///
56    /// Different to `invoke()`, this does not require `func` to be
57    /// `Send` but can only be called from the thread that owns the main context.
58    ///
59    /// This function panics if called from a different thread than the one that
60    /// owns the main context.
61    pub fn invoke_local<F>(&self, func: F)
62    where
63        F: FnOnce() + 'static,
64    {
65        self.invoke_local_with_priority(::PRIORITY_DEFAULT_IDLE, func);
66    }
67
68    /// Invokes `func` on the main context with the given priority.
69    ///
70    /// Different to `invoke_with_priority()`, this does not require `func` to be
71    /// `Send` but can only be called from the thread that owns the main context.
72    ///
73    /// This function panics if called from a different thread than the one that
74    /// owns the main context.
75    pub fn invoke_local_with_priority<F>(&self, priority: Priority, func: F)
76    where
77        F: FnOnce() + 'static,
78    {
79        unsafe {
80            assert!(self.is_owner());
81            self.invoke_unsafe(priority, func);
82        }
83    }
84
85    unsafe fn invoke_unsafe<F>(&self, priority: Priority, func: F)
86    where
87        F: FnOnce() + 'static,
88    {
89        unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
90            let func: &mut Option<F> = &mut *(func as *mut Option<F>);
91            let func = func
92                .take()
93                .expect("MainContext::invoke() closure called multiple times");
94            func();
95            glib_sys::G_SOURCE_REMOVE
96        }
97        unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
98            Box::<Option<F>>::from_raw(ptr as *mut _);
99        }
100        let func = Box::into_raw(Box::new(Some(func)));
101        glib_sys::g_main_context_invoke_full(
102            self.to_glib_none().0,
103            priority.to_glib(),
104            Some(trampoline::<F>),
105            func as gpointer,
106            Some(destroy_closure::<F>),
107        )
108    }
109
110    /// Calls closure with context configured as the thread default one.
111    ///
112    /// Thread default context is changed in panic-safe manner by calling
113    /// [`push_thread_default`][push_thread_default] before calling closure
114    /// and [`pop_thread_default`][pop_thread_default] afterwards regardless
115    /// of whether closure panicked or not.
116    ///
117    /// [push_thread_default]: struct.MainContext.html#method.push_thread_default
118    /// [pop_thread_default]: struct.MainContext.html#method.pop_thread_default
119    pub fn with_thread_default<R, F: Sized>(&self, func: F) -> R
120    where
121        F: FnOnce() -> R,
122    {
123        let _thread_default = ThreadDefaultContext::new(self);
124        func()
125    }
126}
127
128struct ThreadDefaultContext<'a>(&'a MainContext);
129
130impl<'a> ThreadDefaultContext<'a> {
131    fn new(ctx: &MainContext) -> ThreadDefaultContext {
132        ctx.push_thread_default();
133        ThreadDefaultContext(ctx)
134    }
135}
136
137impl<'a> Drop for ThreadDefaultContext<'a> {
138    fn drop(&mut self) {
139        self.0.pop_thread_default();
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::panic;
147    use std::ptr;
148    use std::thread;
149
150    #[test]
151    fn test_invoke() {
152        let c = MainContext::new();
153        let l = ::MainLoop::new(Some(&c), false);
154
155        let l_clone = l.clone();
156        thread::spawn(move || {
157            c.invoke(move || l_clone.quit());
158        });
159
160        l.run();
161    }
162
163    fn is_same_context(a: &MainContext, b: &MainContext) -> bool {
164        ptr::eq(a.to_glib_none().0, b.to_glib_none().0)
165    }
166
167    #[test]
168    fn test_with_thread_default() {
169        let a = MainContext::new();
170        let b = MainContext::new();
171
172        assert!(!is_same_context(&a, &b));
173
174        a.with_thread_default(|| {
175            let t = MainContext::get_thread_default().unwrap();
176            assert!(is_same_context(&a, &t));
177
178            &b.with_thread_default(|| {
179                let t = MainContext::get_thread_default().unwrap();
180                assert!(is_same_context(&b, &t));
181            });
182
183            let t = MainContext::get_thread_default().unwrap();
184            assert!(is_same_context(&a, &t));
185        });
186    }
187
188    #[test]
189    fn test_with_thread_default_is_panic_safe() {
190        let a = MainContext::new();
191        let b = MainContext::new();
192
193        assert!(!is_same_context(&a, &b));
194
195        a.with_thread_default(|| {
196            let t = MainContext::get_thread_default().unwrap();
197            assert!(is_same_context(&a, &t));
198
199            let result = panic::catch_unwind(|| {
200                &b.with_thread_default(|| {
201                    panic!();
202                });
203            });
204            assert!(result.is_err());
205
206            let t = MainContext::get_thread_default().unwrap();
207            assert!(is_same_context(&a, &t));
208        });
209    }
210}