Skip to main content

gpui_platform/
gpui_platform.rs

1//! Convenience crate that re-exports GPUI's platform traits and the
2//! `current_platform` constructor so consumers don't need `#[cfg]` gating.
3
4pub use gpui::Platform;
5
6#[cfg(target_family = "wasm")]
7pub use gpui_web::WebBackendPreference;
8
9use std::rc::Rc;
10
11/// Returns a background executor for the current platform.
12pub fn background_executor() -> gpui::BackgroundExecutor {
13    current_platform(true).background_executor()
14}
15
16pub fn application() -> gpui::Application {
17    gpui::Application::with_platform(current_platform(false))
18}
19
20pub fn headless() -> gpui::Application {
21    gpui::Application::with_platform(current_platform(true))
22}
23
24/// Returns a Linux app that may switch among `allowed_modes`.
25///
26/// It starts windowed in the process's own environment, or headless if that names no allowed
27/// display server. Set another initial mode with [`gpui::Application::with_windowing`], and
28/// switch later with [`gpui::App::request_windowing`].
29#[cfg(all(
30    any(target_os = "linux", target_os = "freebsd"),
31    not(target_env = "ohos")
32))]
33pub fn linux(allowed_modes: gpui::WindowingModes) -> gpui::Application {
34    gpui::Application::with_platform(gpui_linux::linux_platform(allowed_modes))
35}
36
37#[cfg(target_family = "wasm")]
38pub fn application_with_web_backend(backend_preference: WebBackendPreference) -> gpui::Application {
39    let platform = Rc::new(gpui_web::WebPlatform::new_with_backend(
40        true,
41        backend_preference,
42    ));
43    gpui::Application::with_platform(platform)
44}
45
46/// Unlike `application`, this function returns a single-threaded web application.
47#[cfg(target_family = "wasm")]
48pub fn single_threaded_web() -> gpui::Application {
49    let platform = Rc::new(gpui_web::WebPlatform::new(false));
50    gpui::Application::with_platform(platform)
51}
52
53/// Initializes panic hooks and logging for the web platform.
54/// Call this before running the application in a wasm_bindgen entrypoint.
55#[cfg(target_family = "wasm")]
56pub fn web_init() {
57    console_error_panic_hook::set_once();
58    gpui_web::init_logging();
59}
60
61/// Returns the default [`Platform`] for the current OS.
62pub fn current_platform(headless: bool) -> Rc<dyn Platform> {
63    cfg_select! {
64        target_os = "ios" => gpui_ios::current_platform(headless),
65        target_os = "macos" => Rc::new(gpui_macos::MacPlatform::new(headless)),
66        target_os = "windows" => Rc::new(
67            gpui_windows::WindowsPlatform::new(headless)
68                .expect("failed to initialize Windows platform"),
69        ),
70        all(
71            any(target_os = "linux", target_os = "freebsd"),
72            not(target_env = "ohos")
73        ) => gpui_linux::current_platform(headless),
74        target_family = "wasm" => {
75            let _ = headless;
76            Rc::new(gpui_web::WebPlatform::new_with_backend(
77                true,
78                gpui_web::WebBackendPreference::Auto,
79            ))
80        },
81    }
82}
83
84/// Returns the current platform's text system for benchmark contexts, built once per
85/// thread and shared by every context on it.
86///
87/// `#[gpui::bench]` creates a context for every Criterion routine call. Building a whole
88/// platform each time only to take its text system leaks the platform's threads on Linux
89/// (a 20-worker dispatcher and a timer thread per call), which slows every later
90/// benchmark in the process.
91#[cfg(feature = "bench-support")]
92pub fn bench_text_system() -> std::sync::Arc<dyn gpui::PlatformTextSystem> {
93    thread_local! {
94        static TEXT_SYSTEM: std::sync::Arc<dyn gpui::PlatformTextSystem> =
95            current_platform(true).text_system();
96    }
97    TEXT_SYSTEM.with(|text_system| text_system.clone())
98}
99
100/// Returns a new [`HeadlessRenderer`] for the current platform, if available.
101#[cfg(any(feature = "bench-support", feature = "test-support"))]
102pub fn current_headless_renderer() -> anyhow::Result<Option<Box<dyn gpui::PlatformHeadlessRenderer>>>
103{
104    #[cfg(target_os = "macos")]
105    {
106        Ok(Some(Box::new(
107            gpui_macos::metal_renderer::MetalHeadlessRenderer::new()?,
108        )))
109    }
110
111    #[cfg(target_os = "linux")]
112    {
113        gpui_wgpu::WgpuHeadlessRenderer::new()
114            .map(|renderer| Some(Box::new(renderer) as Box<dyn gpui::PlatformHeadlessRenderer>))
115    }
116
117    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
118    {
119        Ok(None)
120    }
121}
122
123#[cfg(all(test, target_os = "macos"))]
124mod tests {
125    use super::*;
126    use gpui::{AppContext, Empty, VisualTestAppContext};
127    use std::cell::RefCell;
128    use std::time::Duration;
129
130    // Note: All VisualTestAppContext tests are ignored by default because they require
131    // the macOS main thread. Standard Rust tests run on worker threads, which causes
132    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
133    //
134    // To run these tests, use:
135    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
136
137    #[test]
138    #[ignore] // Requires macOS main thread
139    fn test_foreground_tasks_run_with_run_until_parked() {
140        let mut cx = VisualTestAppContext::new(current_platform(false));
141
142        let task_ran = Rc::new(RefCell::new(false));
143
144        // Spawn a foreground task via the App's spawn method
145        // This should use our TestDispatcher, not the MacDispatcher
146        {
147            let task_ran = task_ran.clone();
148            cx.update(|cx| {
149                cx.spawn(async move |_| {
150                    *task_ran.borrow_mut() = true;
151                })
152                .detach();
153            });
154        }
155
156        // The task should not have run yet
157        assert!(!*task_ran.borrow());
158
159        // Run until parked should execute the foreground task
160        cx.run_until_parked();
161
162        // Now the task should have run
163        assert!(*task_ran.borrow());
164    }
165
166    #[test]
167    #[ignore] // Requires macOS main thread
168    fn test_advance_clock_triggers_delayed_tasks() {
169        let mut cx = VisualTestAppContext::new(current_platform(false));
170
171        let task_ran = Rc::new(RefCell::new(false));
172
173        // Spawn a task that waits for a timer
174        {
175            let task_ran = task_ran.clone();
176            let executor = cx.background_executor.clone();
177            cx.update(|cx| {
178                cx.spawn(async move |_| {
179                    executor.timer(Duration::from_millis(500)).await;
180                    *task_ran.borrow_mut() = true;
181                })
182                .detach();
183            });
184        }
185
186        // Run until parked - the task should be waiting on the timer
187        cx.run_until_parked();
188        assert!(!*task_ran.borrow());
189
190        // Advance clock past the timer duration
191        cx.advance_clock(Duration::from_millis(600));
192
193        // Now the task should have completed
194        assert!(*task_ran.borrow());
195    }
196
197    #[test]
198    #[ignore] // Requires macOS main thread - window creation fails on test threads
199    fn test_window_spawn_uses_test_dispatcher() {
200        let mut cx = VisualTestAppContext::new(current_platform(false));
201
202        let task_ran = Rc::new(RefCell::new(false));
203
204        let window = cx
205            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
206            .expect("Failed to open window");
207
208        // Spawn a task via window.spawn - this is the critical test case
209        // for tooltip behavior, as tooltips use window.spawn for delayed show
210        {
211            let task_ran = task_ran.clone();
212            cx.update_window(window.into(), |_, window, cx| {
213                window
214                    .spawn(cx, async move |_| {
215                        *task_ran.borrow_mut() = true;
216                    })
217                    .detach();
218            })
219            .ok();
220        }
221
222        // The task should not have run yet
223        assert!(!*task_ran.borrow());
224
225        // Run until parked should execute the foreground task spawned via window
226        cx.run_until_parked();
227
228        // Now the task should have run
229        assert!(*task_ran.borrow());
230    }
231}