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