1use std::{
4 cell::Cell,
5 sync::atomic::{AtomicBool, Ordering},
6};
7
8use crate::ffi;
9use glib::translate::*;
10
11#[cfg(target_os = "macos")]
12unsafe extern "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 unsafe {
95 skip_assert_initialized!();
96 if is_initialized_main_thread() {
97 return;
98 } else if is_initialized() {
99 panic!("Attempted to initialize GTK from two different threads.");
100 } else if !{ from_glib(ffi::gtk_is_initialized()) } {
101 panic!("GTK was not actually initialized");
102 }
103 #[cfg(target_os = "macos")]
106 {
107 assert_ne!(
108 pthread_main_np(),
109 0,
110 "Attempted to initialize GTK on OSX from non-main thread"
111 );
112 }
113 INITIALIZED.store(true, Ordering::Release);
114 IS_MAIN_THREAD.with(|c| c.set(true));
115 }
116}
117
118#[doc(alias = "gtk_init")]
130#[allow(unknown_lints)]
131#[allow(clippy::if_then_panic)]
132pub fn init() -> Result<(), glib::BoolError> {
133 skip_assert_initialized!();
134 if is_initialized_main_thread() {
135 return Ok(());
136 } else if is_initialized() {
137 #[cfg(not(test))]
138 panic!("Attempted to initialize GTK from two different threads.");
139 #[cfg(test)]
140 panic!("Use #[gtk::test] instead of #[test]");
141 }
142
143 unsafe {
144 if from_glib(ffi::gtk_init_check()) {
145 let result: bool = from_glib(glib::ffi::g_main_context_acquire(
148 glib::ffi::g_main_context_default(),
149 ));
150 if !result {
151 return Err(glib::bool_error!("Failed to acquire default main context"));
152 }
153
154 if !from_glib::<glib::ffi::gboolean, bool>(ffi::gtk_is_initialized()) {
155 return Err(glib::bool_error!("GTK was not actually initialized"));
156 }
157
158 set_initialized();
159 Ok(())
160 } else {
161 Err(glib::bool_error!("Failed to initialize GTK"))
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use crate as gtk4;
169
170 #[test]
171 fn init_should_acquire_default_main_context() {
172 let context = glib::MainContext::ref_thread_default();
173 assert!(context.is_owner());
174 }
175}