1use std::{
4 cell::Cell,
5 sync::atomic::{AtomicBool, Ordering},
6};
7
8use crate::ffi;
9use glib::translate::*;
10
11#[cfg(target_os = "macos")]
12extern "C" {
13 fn pthread_main_np() -> i32;
14}
15
16thread_local! {
17 static IS_MAIN_THREAD: Cell<bool> = const{Cell::new(false)}
18}
19
20static INITIALIZED: AtomicBool = AtomicBool::new(false);
21
22macro_rules! assert_initialized_main_thread {
25 () => {
26 #[allow(unknown_lints)]
27 #[allow(clippy::if_then_panic)]
28 if !crate::rt::is_initialized_main_thread() {
29 if crate::rt::is_initialized() {
30 panic!("GTK may only be used from the main thread.");
31 } else {
32 panic!("GTK has not been initialized. Call `gtk::init` first.");
33 }
34 }
35 };
36}
37
38macro_rules! skip_assert_initialized {
40 () => {};
41}
42
43#[allow(unused_macros)]
45macro_rules! assert_not_initialized {
46 () => {
47 assert!(
48 !crate::rt::is_initialized(),
49 "This function has to be called before `gtk::init`."
50 );
51 };
52}
53
54#[inline]
56pub fn is_initialized() -> bool {
57 skip_assert_initialized!();
58 if cfg!(not(feature = "unsafe-assume-initialized")) {
59 INITIALIZED.load(Ordering::Acquire)
60 } else {
61 true
62 }
63}
64
65#[inline]
67pub fn is_initialized_main_thread() -> bool {
68 skip_assert_initialized!();
69 if cfg!(not(feature = "unsafe-assume-initialized")) {
70 IS_MAIN_THREAD.with(|c| c.get())
71 } else {
72 true
73 }
74}
75
76#[allow(unknown_lints)]
92#[allow(clippy::if_then_panic)]
93pub unsafe fn set_initialized() {
94 skip_assert_initialized!();
95 if is_initialized_main_thread() {
96 return;
97 } else if is_initialized() {
98 panic!("Attempted to initialize GTK from two different threads.");
99 } else if !{ from_glib(ffi::gtk_is_initialized()) } {
100 panic!("GTK was not actually initialized");
101 }
102 #[cfg(target_os = "macos")]
105 {
106 assert_ne!(
107 pthread_main_np(),
108 0,
109 "Attempted to initialize GTK on OSX from non-main thread"
110 );
111 }
112 INITIALIZED.store(true, Ordering::Release);
113 IS_MAIN_THREAD.with(|c| c.set(true));
114}
115
116#[doc(alias = "gtk_init")]
128#[allow(unknown_lints)]
129#[allow(clippy::if_then_panic)]
130pub fn init() -> Result<(), glib::BoolError> {
131 skip_assert_initialized!();
132 if is_initialized_main_thread() {
133 return Ok(());
134 } else if is_initialized() {
135 #[cfg(not(test))]
136 panic!("Attempted to initialize GTK from two different threads.");
137 #[cfg(test)]
138 panic!("Use #[gtk::test] instead of #[test]");
139 }
140
141 unsafe {
142 if from_glib(ffi::gtk_init_check()) {
143 let result: bool = from_glib(glib::ffi::g_main_context_acquire(
146 glib::ffi::g_main_context_default(),
147 ));
148 if !result {
149 return Err(glib::bool_error!("Failed to acquire default main context"));
150 }
151
152 if !from_glib::<glib::ffi::gboolean, bool>(ffi::gtk_is_initialized()) {
153 return Err(glib::bool_error!("GTK was not actually initialized"));
154 }
155
156 set_initialized();
157 Ok(())
158 } else {
159 Err(glib::bool_error!("Failed to initialize GTK"))
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use crate as gtk4;
167
168 #[test]
169 fn init_should_acquire_default_main_context() {
170 let context = glib::MainContext::ref_thread_default();
171 assert!(context.is_owner());
172 }
173}