Skip to main content

gpui/
platform_entry.rs

1//! Platform entrypoints: construct the default [`crate::Platform`] for the
2//! current operating system and turn it into an [`crate::Application`].
3//!
4//! These live behind the `platform` feature, which pulls in the
5//! backend for the target OS.
6
7use crate::{Application, BackgroundExecutor, Platform};
8use std::rc::Rc;
9
10/// Returns a background executor for the current platform.
11pub fn background_executor() -> BackgroundExecutor {
12    current_platform(true).background_executor()
13}
14
15/// Returns an [`Application`] backed by the default platform for this OS.
16pub fn application() -> Application {
17    #[cfg(target_family = "wasm")]
18    {
19        application_with_web_backend(gpui_web::WebBackendPreference::Auto)
20    }
21
22    #[cfg(not(target_family = "wasm"))]
23    Application::with_platform(current_platform(false))
24}
25
26/// Returns a headless [`Application`] backed by the default platform for this OS.
27pub fn headless() -> Application {
28    Application::with_platform(current_platform(true))
29}
30
31/// Returns a Linux app that may switch among `allowed_modes`.
32///
33/// It starts windowed in the process's own environment, or headless if that names no allowed
34/// display server. Set another initial mode with [`gpui::Application::with_windowing`], and
35/// switch later with [`gpui::App::request_windowing`].
36#[cfg(any(target_os = "linux", target_os = "freebsd"))]
37pub fn linux(allowed_modes: gpui::WindowingModes) -> gpui::Application {
38    gpui::Application::with_platform(gpui_linux::linux_platform(allowed_modes))
39}
40
41#[cfg(target_family = "wasm")]
42pub use gpui_web::WebBackendPreference;
43
44/// The producer's half of the imported-texture path on Windows: a producer reaches the Direct3D
45/// renderer's device through a `gpu_canvas` callback
46/// ([`GpuCanvasContext::device`](crate::GpuCanvasContext::device)), and a texture made on it becomes
47/// a token through this extension.
48#[cfg(target_os = "windows")]
49pub use gpui_windows::{DirectXImportedTexture, DirectXRenderer, DirectXTextureExt};
50
51/// The producer's half of the imported-texture path on macOS, the same shape as the Windows pair above: the device
52/// comes from the canvas's `device::<R>()`, and a texture made on it becomes a token through this extension.
53/// Unlike the Windows one it names no payload type of its own -- the engine's MetalTexture is the
54/// handle the renderer resolves.
55#[cfg(target_os = "macos")]
56pub use gpui_macos::MetalTextureExt;
57
58/// The producer's half of the imported-texture path where wgpu is the renderer, which is the platform's own default on
59/// these two targets: the same shape, from the crate that re-exports the wgpu renderer. A window on
60/// Windows whose factory installed `WgpuRenderer` takes the trait from `gpui_wgpu` instead, which
61/// is the crate that renderer comes from.
62#[cfg(any(target_os = "linux", target_os = "freebsd"))]
63pub use gpui_linux::ImportedTextureExt;
64
65#[cfg(target_family = "wasm")]
66pub fn application_with_web_backend(backend_preference: WebBackendPreference) -> Application {
67    let platform = Rc::new(gpui_web::WebPlatform::new_with_backend(
68        true,
69        backend_preference,
70    ));
71    let http_client = std::sync::Arc::new(platform.fetch_http_client());
72    Application::with_platform(platform).with_http_client(http_client)
73}
74
75/// Unlike `application`, this function returns a single-threaded web application.
76#[cfg(target_family = "wasm")]
77pub fn single_threaded_web() -> Application {
78    let platform = Rc::new(gpui_web::WebPlatform::new(false));
79    let http_client = std::sync::Arc::new(platform.fetch_http_client());
80    Application::with_platform(platform).with_http_client(http_client)
81}
82
83/// Initializes logging for the web platform. `gpui_web::init_logging` also
84/// installs the panic hook. Call this before running the application in a
85/// wasm_bindgen entrypoint.
86#[cfg(target_family = "wasm")]
87pub fn web_init() {
88    gpui_web::init_logging();
89}
90
91/// Returns the default [`Platform`] for the current OS.
92pub fn current_platform(headless: bool) -> Rc<dyn Platform> {
93    #[cfg(target_os = "macos")]
94    {
95        Rc::new(gpui_macos::MacPlatform::new(headless))
96    }
97
98    #[cfg(target_os = "windows")]
99    {
100        Rc::new(
101            gpui_windows::WindowsPlatform::new(headless)
102                .expect("failed to initialize Windows platform"),
103        )
104    }
105
106    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
107    {
108        gpui_linux::current_platform(headless)
109    }
110
111    #[cfg(target_family = "wasm")]
112    {
113        let _ = headless;
114        Rc::new(gpui_web::WebPlatform::new(true))
115    }
116}
117
118/// Returns a new [`crate::SceneRenderer`] for the current platform, if available.
119#[cfg(any(feature = "bench-support", feature = "test-support"))]
120pub fn current_headless_renderer() -> anyhow::Result<Option<Box<dyn crate::SceneRenderer>>> {
121    #[cfg(all(feature = "platform", any(target_os = "linux", target_os = "freebsd")))]
122    {
123        // Headless has no platform instance to ask, so the platform crate supplies its debug
124        // preference directly: the same `ZED_DEVICE_ID` lowering a windowed run gets through
125        // `set_gpu_preference`.
126        current_headless_renderer_with(
127            gpui_linux::gpu_preference_from_env().unwrap_or(crate::GpuPreference::Auto),
128        )
129    }
130
131    #[cfg(not(all(feature = "platform", any(target_os = "linux", target_os = "freebsd"))))]
132    {
133        current_headless_renderer_with(crate::GpuPreference::Auto)
134    }
135}
136
137/// As [`current_headless_renderer`], selecting the adapter by `gpu_preference` where the headless
138/// renderer is `wgpu`.
139///
140/// Metal's headless renderer takes no preference, and a platform with no headless renderer returns
141/// `Ok(None)`; the argument is ignored in both cases. The wgpu context is cached process-wide, so
142/// only the first renderer's preference takes effect.
143#[cfg(any(feature = "bench-support", feature = "test-support"))]
144pub fn current_headless_renderer_with(
145    gpu_preference: crate::GpuPreference,
146) -> anyhow::Result<Option<Box<dyn crate::SceneRenderer>>> {
147    let _ = &gpu_preference;
148
149    #[cfg(target_os = "macos")]
150    {
151        Ok(Some(Box::new(
152            gpui_macos::metal_renderer::MetalHeadlessRenderer::new(),
153        )))
154    }
155
156    #[cfg(all(feature = "platform", any(target_os = "linux", target_os = "freebsd")))]
157    {
158        // The Linux platform owns no renderer of its own: wgpu is the renderer a window uses here,
159        // so a headless context renders through the same code a window drives.
160        gpui_linux::WgpuHeadlessRenderer::with_gpu_preference(gpu_preference)
161            .map(|renderer| Some(Box::new(renderer) as Box<dyn crate::SceneRenderer>))
162    }
163
164    #[cfg(not(any(
165        target_os = "macos",
166        all(feature = "platform", any(target_os = "linux", target_os = "freebsd")),
167    )))]
168    {
169        Ok(None)
170    }
171}
172
173/// Returns the current platform's text system for benchmark contexts, built once per
174/// thread and shared by every context on it.
175///
176/// `#[gpui::bench]` creates a context for every Criterion routine call. Building a whole
177/// platform each time only to take its text system leaks the platform's threads on Linux
178/// (a 20-worker dispatcher and a timer thread per call), which slows every later
179/// benchmark in the process.
180#[cfg(feature = "bench-support")]
181pub fn bench_text_system() -> std::sync::Arc<dyn crate::PlatformTextSystem> {
182    thread_local! {
183        static TEXT_SYSTEM: std::sync::Arc<dyn crate::PlatformTextSystem> =
184            current_platform(true).text_system();
185    }
186    TEXT_SYSTEM.with(|text_system| text_system.clone())
187}
188
189#[cfg(all(test, target_os = "macos"))]
190mod tests {
191    use super::*;
192    use crate::{AppContext, Empty, VisualTestAppContext};
193    use std::cell::RefCell;
194    use std::time::Duration;
195
196    // Note: All VisualTestAppContext tests are ignored by default because they require
197    // the macOS main thread. Standard Rust tests run on worker threads, which causes
198    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
199    //
200    // To run these tests, use:
201    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
202
203    #[test]
204    #[ignore] // Requires macOS main thread
205    fn test_foreground_tasks_run_with_run_until_parked() {
206        let mut cx = VisualTestAppContext::new(current_platform(false));
207
208        let task_ran = Rc::new(RefCell::new(false));
209
210        // Spawn a foreground task via the App's spawn method
211        // This should use our TestDispatcher, not the MacDispatcher
212        {
213            let task_ran = task_ran.clone();
214            cx.update(|cx| {
215                cx.spawn(async move |_| {
216                    *task_ran.borrow_mut() = true;
217                })
218                .detach();
219            });
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
226        cx.run_until_parked();
227
228        // Now the task should have run
229        assert!(*task_ran.borrow());
230    }
231
232    #[test]
233    #[ignore] // Requires macOS main thread
234    fn test_advance_clock_triggers_delayed_tasks() {
235        let mut cx = VisualTestAppContext::new(current_platform(false));
236
237        let task_ran = Rc::new(RefCell::new(false));
238
239        // Spawn a task that waits for a timer
240        {
241            let task_ran = task_ran.clone();
242            let executor = cx.background_executor.clone();
243            cx.update(|cx| {
244                cx.spawn(async move |_| {
245                    executor.timer(Duration::from_millis(500)).await;
246                    *task_ran.borrow_mut() = true;
247                })
248                .detach();
249            });
250        }
251
252        // Run until parked - the task should be waiting on the timer
253        cx.run_until_parked();
254        assert!(!*task_ran.borrow());
255
256        // Advance clock past the timer duration
257        cx.advance_clock(Duration::from_millis(600));
258
259        // Now the task should have completed
260        assert!(*task_ran.borrow());
261    }
262
263    #[test]
264    #[ignore] // Requires macOS main thread - window creation fails on test threads
265    fn test_window_spawn_uses_test_dispatcher() {
266        let mut cx = VisualTestAppContext::new(current_platform(false));
267
268        let task_ran = Rc::new(RefCell::new(false));
269
270        let window = cx
271            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
272            .expect("Failed to open window");
273
274        // Spawn a task via window.spawn - this is the critical test case
275        // for tooltip behavior, as tooltips use window.spawn for delayed show
276        {
277            let task_ran = task_ran.clone();
278            cx.update_window(window.into(), |_, window, cx| {
279                window
280                    .spawn(cx, async move |_| {
281                        *task_ran.borrow_mut() = true;
282                    })
283                    .detach();
284            })
285            .ok();
286        }
287
288        // The task should not have run yet
289        assert!(!*task_ran.borrow());
290
291        // Run until parked should execute the foreground task spawned via window
292        cx.run_until_parked();
293
294        // Now the task should have run
295        assert!(*task_ran.borrow());
296    }
297}