Skip to main content

gpui_platform/
platform.rs

1//! The [`Platform`] trait implemented by each platform backend.
2//!
3//! `gpui` drives an application through a `dyn Platform`; backends
4//! (`gpui_macos`, `gpui_linux`, `gpui_windows`, `gpui_web`) implement it
5//! without depending on the framework itself.
6
7use crate::{
8    ActivityGuard, AppLifecyclePhase, BackgroundExecutor, ClipboardItem, ClipboardReadError,
9    CursorStyle, ForegroundExecutor, GpuAdapterInfo, GpuPreference, MenuCommandId,
10    PathPromptOptions, PlatformDisplay, PlatformGestures, PlatformKeyboardLayout,
11    PlatformKeyboardMapper, PlatformMenu, PlatformMenuItem, PlatformWindow, ScreenCaptureSource,
12    SystemNotification, SystemNotificationResponse, Task, ThermalState, WindowAppearance,
13    WindowButtonLayout, WindowId, WindowParams,
14};
15use anyhow::Result;
16use futures::channel::oneshot;
17use gpui_engine::{DmaBufCapabilities, PlatformTextSystem};
18use smallvec::SmallVec;
19use std::{
20    ffi::OsString,
21    path::{Path, PathBuf},
22    rc::Rc,
23    sync::Arc,
24};
25
26// TODO(jk): return an enum instead of a string
27/// Return which compositor we're guessing we'll use.
28/// Does not attempt to connect to the given compositor.
29#[cfg(any(target_os = "linux", target_os = "freebsd"))]
30#[inline]
31pub fn guess_compositor() -> &'static str {
32    if std::env::var_os("ZED_HEADLESS").is_some() {
33        return "Headless";
34    }
35
36    #[cfg(feature = "wayland")]
37    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
38    #[cfg(not(feature = "wayland"))]
39    let wayland_display: Option<std::ffi::OsString> = None;
40
41    #[cfg(feature = "x11")]
42    let x11_display = std::env::var_os("DISPLAY");
43    #[cfg(not(feature = "x11"))]
44    let x11_display: Option<std::ffi::OsString> = None;
45
46    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
47    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
48
49    if use_wayland {
50        "Wayland"
51    } else if use_x11 {
52        "X11"
53    } else {
54        "Headless"
55    }
56}
57
58#[expect(missing_docs)]
59pub trait Platform: 'static {
60    fn background_executor(&self) -> BackgroundExecutor;
61    fn foreground_executor(&self) -> ForegroundExecutor;
62    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
63
64    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
65    fn quit(&self);
66    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
67    fn activate(&self, ignoring_other_apps: bool);
68    fn hide(&self);
69    fn hide_other_apps(&self);
70    fn unhide_other_apps(&self);
71
72    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
73    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
74    fn active_window(&self) -> Option<WindowId>;
75    fn window_stack(&self) -> Option<Vec<WindowId>> {
76        None
77    }
78
79    fn is_screen_capture_supported(&self) -> bool {
80        false
81    }
82
83    fn screen_capture_sources(
84        &self,
85    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
86        let (sources_tx, sources_rx) = oneshot::channel();
87        sources_tx
88            .send(Err(anyhow::anyhow!(
89                "gpui was compiled without the screen-capture feature"
90            )))
91            .ok();
92        sources_rx
93    }
94
95    fn open_window(
96        &self,
97        handle: WindowId,
98        options: WindowParams,
99    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
100
101    /// Returns the appearance of the application's windows.
102    fn window_appearance(&self) -> WindowAppearance;
103
104    /// Overrides the appearance (light/dark) applied to the app's windows, independent
105    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
106    /// again. The override is reflected by [`Platform::window_appearance`].
107    ///
108    /// Currently only implemented on macOS, where it sets `NSApplication.appearance` so
109    /// the native window chrome (the window border and titlebar) of every window matches
110    /// a dark app theme even when the system is in light mode (or vice versa). A no-op on
111    /// other platforms.
112    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
113
114    /// States which GPU the application prefers the renderer to run on.
115    ///
116    /// The device is process-wide and chosen before the first window, so this has to be set before
117    /// the application runs. A backend that cannot choose ignores it. See decision `0008` in the
118    /// project record, `decisions/0008-gpu-preference-and-enumeration.md`.
119    fn set_gpu_preference(&self, _preference: GpuPreference) {}
120
121    /// The adapters the application could render on.
122    ///
123    /// Enumeration happens without creating a device, so this is safe to call before
124    /// [`run`](Self::run) and is what a preferences panel reads; it is also the list a
125    /// [`GpuPreference::Custom`] selector chooses from. The order is the platform's; a caller that
126    /// wants a particular one states a preference rather than indexing into this list.
127    ///
128    /// A backend that cannot enumerate returns **one** synthesized entry rather than an empty list:
129    /// the identity contract is that every reported adapter has a permanent part and a session, and
130    /// a single-GPU platform should still answer "which GPU" with "the GPU".
131    fn enumerate_gpus(&self) -> Vec<GpuAdapterInfo> {
132        vec![GpuAdapterInfo::single()]
133    }
134
135    /// What the renderer can import as a dma-buf surface, once it has a device.
136    ///
137    /// Answered on the device the renderer already made — no device is created — and `None` before
138    /// the first window or on a platform whose surfaces are not dma-bufs. A producer uses it to
139    /// choose a layout rather than discover a refusal at import time.
140    fn dmabuf_capabilities(&self) -> Option<DmaBufCapabilities> {
141        None
142    }
143
144    /// The adapter the renderer actually chose, once it has chosen one.
145    ///
146    /// The device is process-wide and selected before the first window, so the answer is `None`
147    /// until a window has created one; after that it is the adapter every window draws through. A
148    /// platform that cannot report it returns `None`.
149    fn active_gpu(&self) -> Option<GpuAdapterInfo> {
150        None
151    }
152
153    /// Returns the window button layout configuration when supported.
154    fn button_layout(&self) -> Option<WindowButtonLayout> {
155        None
156    }
157
158    fn open_url(&self, url: &str);
159    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
160    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
161
162    fn prompt_for_paths(
163        &self,
164        options: PathPromptOptions,
165    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
166    fn prompt_for_new_path(
167        &self,
168        directory: &Path,
169        suggested_name: Option<&str>,
170    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
171    fn can_select_mixed_files_and_dirs(&self) -> bool;
172    fn reveal_path(&self, path: &Path);
173    fn open_with_system(&self, path: &Path);
174
175    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
176    fn on_reopen(&self, callback: Box<dyn FnMut()>);
177    /// Registers the callback invoked when the system is about to sleep.
178    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
179    /// Registers the callback invoked when the system resumes from sleep.
180    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
181
182    // Mobile platform methods. On mobile the OS owns the application
183    // lifecycle: apps are backgrounded, foregrounded, and killed at the
184    // system's discretion, and must react rather than decide.
185
186    /// Registers a callback invoked whenever the application's lifecycle
187    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
188    /// its mapping onto iOS and Android.
189    ///
190    /// Desktop platforms never invoke this.
191    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
192
193    /// Registers a callback invoked when the OS signals memory pressure
194    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
195    ///
196    /// Desktop platforms never invoke this.
197    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
198
199    /// The platform's gesture recognition services, if it provides any
200    /// beyond gpui's portable recognizers. See
201    /// [`PlatformGestures`](crate::PlatformGestures).
202    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
203        None
204    }
205
206    fn set_menus(&self, menus: Vec<PlatformMenu>);
207
208    fn set_dock_menu(&self, menu: Vec<PlatformMenuItem>);
209    fn perform_dock_menu_action(&self, _action: usize) {}
210    fn add_recent_document(&self, _path: &Path) {}
211    fn update_jump_list(
212        &self,
213        _menus: Vec<PlatformMenuItem>,
214        _entries: Vec<SmallVec<[PathBuf; 2]>>,
215    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
216        Task::ready(Vec::new())
217    }
218    fn on_app_menu_action(&self, callback: Box<dyn FnMut(MenuCommandId)>);
219    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
220    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(MenuCommandId) -> bool>);
221
222    fn thermal_state(&self) -> ThermalState;
223    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
224    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
225
226    /// Sets the application's process-wide identity and user-visible name.
227    ///
228    /// The identifier is used for platform identity mechanisms such as the
229    /// Windows AppUserModelID. The name is used wherever the operating system
230    /// presents the application to the user. Call this once, early in startup,
231    /// before opening windows or posting notifications.
232    fn set_app_identity(&self, identifier: &str, name: &str) {
233        _ = (identifier, name);
234    }
235
236    /// Posts a notification to the operating system's notification center.
237    ///
238    /// Posting a notification whose [`SystemNotification::tag`] matches an
239    /// earlier one replaces that notification where the platform supports it.
240    /// No-op on platforms without notification support, or when delivery is
241    /// unavailable (e.g. authorization was denied).
242    fn show_system_notification(&self, notification: SystemNotification) {
243        _ = notification;
244    }
245
246    /// Removes the delivered or pending notification with this tag.
247    ///
248    /// Best-effort: some platforms cannot retract a notification once shown,
249    /// in which case it ages out of the notification center on its own.
250    fn dismiss_system_notification(&self, tag: &str) {
251        _ = tag;
252    }
253
254    /// Registers the callback invoked when the user activates a system
255    /// notification, either by clicking its body or one of its action
256    /// buttons.
257    ///
258    /// Implementations must invoke the callback on the main thread.
259    fn on_system_notification_response(
260        &self,
261        callback: Box<dyn FnMut(SystemNotificationResponse)>,
262    ) {
263        _ = callback;
264    }
265
266    fn compositor_name(&self) -> &'static str {
267        ""
268    }
269    fn app_path(&self) -> Result<PathBuf>;
270    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
271
272    fn set_cursor_style(&self, style: CursorStyle);
273
274    /// Hides the mouse cursor until the user moves the mouse over one of
275    /// this application's windows.
276    fn hide_cursor_until_mouse_moves(&self);
277
278    /// Returns whether the mouse cursor is currently visible.
279    fn is_cursor_visible(&self) -> bool;
280
281    fn should_auto_hide_scrollbars(&self) -> bool;
282
283    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
284    fn write_to_clipboard(&self, item: ClipboardItem);
285
286    /// Reads the clipboard, resolving once its contents are available.
287    ///
288    /// Most platforms read synchronously and return a ready task. Platforms
289    /// whose clipboard access is inherently asynchronous and permission-gated
290    /// (e.g. the browser's async clipboard API) override this method; on those
291    /// platforms [`Platform::read_from_clipboard`] cannot return the clipboard
292    /// contents, so callers that can await should prefer this method.
293    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
294        Task::ready(Ok(self.read_from_clipboard()))
295    }
296
297    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
298    fn read_from_primary(&self) -> Option<ClipboardItem>;
299    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
300    fn write_to_primary(&self, item: ClipboardItem);
301
302    #[cfg(target_os = "macos")]
303    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
304    #[cfg(target_os = "macos")]
305    fn write_to_find_pasteboard(&self, item: ClipboardItem);
306
307    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
308    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
309    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
310
311    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
312    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
313    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
314}