1 // Take a look at the license at the top of the repository in the LICENSE file.
2 
3 use glib::translate::*;
4 use std::cell::Cell;
5 use std::sync::atomic::{AtomicBool, Ordering};
6 
7 #[cfg(target_os = "macos")]
8 extern "C" {
pthread_main_np() -> i329     fn pthread_main_np() -> i32;
10 }
11 
12 thread_local! {
13     static IS_MAIN_THREAD: Cell<bool> = Cell::new(false)
14 }
15 
16 static INITIALIZED: AtomicBool = AtomicBool::new(false);
17 
18 /// Asserts that this is the main thread and `gtk::init` has been called.
19 macro_rules! assert_initialized_main_thread {
20     () => {
21         if !crate::rt::is_initialized_main_thread() {
22             if crate::rt::is_initialized() {
23                 panic!("GTK may only be used from the main thread.");
24             } else {
25                 panic!("GTK has not been initialized. Call `gtk::init` first.");
26             }
27         }
28     };
29 }
30 
31 /// No-op.
32 macro_rules! skip_assert_initialized {
33     () => {};
34 }
35 
36 /// Asserts that `gtk::init` has not been called.
37 #[allow(unused_macros)]
38 macro_rules! assert_not_initialized {
39     () => {
40         if crate::rt::is_initialized() {
41             panic!("This function has to be called before `gtk::init`.");
42         }
43     };
44 }
45 
46 /// Returns `true` if GTK has been initialized.
47 #[inline]
is_initialized() -> bool48 pub fn is_initialized() -> bool {
49     skip_assert_initialized!();
50     INITIALIZED.load(Ordering::Acquire)
51 }
52 
53 /// Returns `true` if GTK has been initialized and this is the main thread.
54 #[inline]
is_initialized_main_thread() -> bool55 pub fn is_initialized_main_thread() -> bool {
56     skip_assert_initialized!();
57     IS_MAIN_THREAD.with(|c| c.get())
58 }
59 
60 /// Informs this crate that GTK has been initialized and the current thread is the main one.
61 ///
62 /// # Panics
63 ///
64 /// This function will panic if you attempt to initialise GTK from more than
65 /// one thread.
66 ///
67 /// # Safety
68 ///
69 /// You must only call this if:
70 ///
71 /// 1. You have initialised the underlying GTK library yourself.
72 /// 2. You did 1 on the thread with which you are calling this function
73 /// 3. You ensure that this thread is the main thread for the process.
set_initialized()74 pub unsafe fn set_initialized() {
75     skip_assert_initialized!();
76     if is_initialized_main_thread() {
77         return;
78     } else if is_initialized() {
79         panic!("Attempted to initialize GTK from two different threads.");
80     }
81 
82     //  OS X has its own notion of the main thread and init must be called on that thread.
83     #[cfg(target_os = "macos")]
84     {
85         if pthread_main_np() == 0 {
86             panic!("Attempted to initialize GTK on OSX from non-main thread");
87         }
88     }
89 
90     gdk::set_initialized();
91     INITIALIZED.store(true, Ordering::Release);
92     IS_MAIN_THREAD.with(|c| c.set(true));
93 }
94 
95 /// Tries to initialize GTK+.
96 ///
97 /// Call either this function or [`Application::new`][new] before using any
98 /// other GTK+ functions.
99 ///
100 /// [new]: struct.Application.html#method.new
101 ///
102 /// Note that this function calls `gtk_init_check()` rather than `gtk_init()`,
103 /// so will not cause the program to terminate if GTK could not be initialized.
104 /// Instead, an Ok is returned if the windowing system was successfully
105 /// initialized otherwise an Err is returned.
106 #[doc(alias = "gtk_init")]
init() -> Result<(), glib::BoolError>107 pub fn init() -> Result<(), glib::BoolError> {
108     skip_assert_initialized!();
109     if is_initialized_main_thread() {
110         return Ok(());
111     } else if is_initialized() {
112         panic!("Attempted to initialize GTK from two different threads.");
113     }
114     unsafe {
115         // We just want to keep the program's name since more arguments could lead to unwanted
116         // behaviors...
117         let argv = ::std::env::args().take(1).collect::<Vec<_>>();
118 
119         if from_glib(ffi::gtk_init_check(&mut 1, &mut argv.to_glib_none().0)) {
120             // See https://github.com/gtk-rs/gtk-rs-core/issues/186 for reasoning behind
121             // acquiring and leaking the main context here.
122             let result: bool = from_glib(glib::ffi::g_main_context_acquire(
123                 glib::ffi::g_main_context_default(),
124             ));
125             if !result {
126                 return Err(glib::bool_error!("Failed to acquire default main context"));
127             }
128             set_initialized();
129             Ok(())
130         } else {
131             Err(glib::bool_error!("Failed to initialize GTK"))
132         }
133     }
134 }
135 
136 #[doc(alias = "gtk_main_quit")]
main_quit()137 pub fn main_quit() {
138     assert_initialized_main_thread!();
139     unsafe {
140         if ffi::gtk_main_level() > 0 {
141             ffi::gtk_main_quit();
142         } else if cfg!(debug_assertions) {
143             panic!("Attempted to quit a GTK main loop when none is running.");
144         }
145     }
146 }
147