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