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