gtk4/
rt.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use 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
22// rustdoc-stripper-ignore-next
23/// Asserts that this is the main thread and `gtk::init` has been called.
24macro_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
38/// No-op.
39macro_rules! skip_assert_initialized {
40    () => {};
41}
42
43/// Asserts that `gtk::init` has not been called.
44#[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/// Returns `true` if GTK has been initialized.
55#[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/// Returns `true` if GTK has been initialized and this is the main thread.
66#[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/// Informs this crate that GTK has been initialized and the current thread is
77/// the main one.
78///
79/// # Panics
80///
81/// This function will panic if you attempt to initialize GTK from more than
82/// one thread.
83///
84/// # Safety
85///
86/// You must only call this if:
87///
88/// 1. You have initialized the underlying GTK library yourself.
89/// 2. You did 1 on the thread with which you are calling this function
90/// 3. You ensure that this thread is the main thread for the process.
91#[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    //  OS X has its own notion of the main thread and init must be called on that
103    // thread.
104    #[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/// Tries to initialize GTK.
117///
118/// Call either this function or [`Application::new`][new] before using any
119/// other GTK functions.
120///
121/// [new]: struct.Application.html#method.new
122///
123/// Note that this function calls `gtk_init_check()` rather than `gtk_init()`,
124/// so will not cause the program to terminate if GTK could not be initialized.
125/// Instead, an Ok is returned if the windowing system was successfully
126/// initialized otherwise an Err is returned.
127#[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            // See https://github.com/gtk-rs/gtk-rs-core/issues/186 for reasoning behind
144            // acquiring and leaking the main context here.
145            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}