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