Skip to main content

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")]
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
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    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        //  OS X has its own notion of the main thread and init must be called on that
104        // thread.
105        #[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/// Tries to initialize GTK.
119///
120/// Call either this function or [`Application::new`][new] before using any
121/// other GTK functions.
122///
123/// [new]: struct.Application.html#method.new
124///
125/// Note that this function calls `gtk_init_check()` rather than `gtk_init()`,
126/// so will not cause the program to terminate if GTK could not be initialized.
127/// Instead, an Ok is returned if the windowing system was successfully
128/// initialized otherwise an Err is returned.
129#[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            // See https://github.com/gtk-rs/gtk-rs-core/issues/186 for reasoning behind
146            // acquiring and leaking the main context here.
147            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}