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    GraphicalEnvironment::detect().guess_compositor()
36}
37
38/// The graphical session to connect to: the variables that locate its display server, as some
39/// process sees them.
40///
41/// A long-running process can outlive the graphical session it was started in, so a platform
42/// that attaches to a display server later can be given a fresher environment than its own.
43/// While connected, programs the platform launches (for example to open a URL) get these
44/// variables instead of the ones this process started with. Apply
45/// [`App::graphical_environment`] to the programs an app launches, for the same reason.
46#[cfg(any(target_os = "linux", target_os = "freebsd"))]
47#[derive(Clone, Debug, Default)]
48pub struct GraphicalEnvironment {
49    /// `WAYLAND_DISPLAY`: a socket name relative to `xdg_runtime_dir`, or an absolute path.
50    pub wayland_display: Option<OsString>,
51    /// `DISPLAY`: the X11 display name.
52    pub x11_display: Option<OsString>,
53    /// `XDG_RUNTIME_DIR`: the directory containing Wayland sockets.
54    pub xdg_runtime_dir: Option<OsString>,
55    /// `XDG_ACTIVATION_TOKEN`: lets the first window take focus on Wayland.
56    ///
57    /// A platform takes this process's token from its environment when it's created, and uses
58    /// it if it starts windowed. Pass one here to switch to windowed mode later, for example
59    /// the token of the process that asked for a window.
60    pub activation_token: Option<String>,
61}
62
63/// The graphical session to connect to: the Windows session whose desktop windows appear on.
64///
65/// A process can only show windows in its own session, so switching to windowed mode fails if
66/// this names another one, for example when a process started over SSH is asked to show a
67/// window on the desktop.
68#[cfg(target_os = "windows")]
69#[derive(Clone, Debug, Default)]
70pub struct GraphicalEnvironment {
71    /// The session ID, as `ProcessIdToSessionId` reports it. `None` means this process's own
72    /// session.
73    pub session_id: Option<u32>,
74}
75
76#[cfg(target_os = "windows")]
77impl GraphicalEnvironment {
78    /// Returns the environment of this process's session.
79    pub fn detect() -> Self {
80        let mut session_id = 0;
81        // SAFETY: `session_id` is a valid pointer for the call's duration.
82        let result = unsafe {
83            windows::Win32::System::RemoteDesktop::ProcessIdToSessionId(
84                windows::Win32::System::Threading::GetCurrentProcessId(),
85                &mut session_id,
86            )
87        };
88        Self {
89            session_id: result.is_ok().then_some(session_id),
90        }
91    }
92
93    /// Does nothing on this platform: programs inherit the session of the process that
94    /// starts them.
95    pub fn apply_to(&self, _command: &mut std::process::Command) {}
96}
97
98/// The graphical session to connect to. Carries nothing yet on this platform.
99#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
100#[derive(Clone, Debug, Default)]
101pub struct GraphicalEnvironment;
102
103#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
104impl GraphicalEnvironment {
105    /// Returns the environment of this process's graphical session.
106    pub fn detect() -> Self {
107        Self
108    }
109
110    /// Does nothing on this platform.
111    pub fn apply_to(&self, _command: &mut std::process::Command) {}
112}
113
114/// A display mode for [`App::request_windowing`] to switch to.
115#[derive(Clone, Debug)]
116pub enum WindowingRequest {
117    /// No display server. Windows lay out and handle input but draw nothing.
118    Headless,
119    /// Connected to the display server that the environment names.
120    Windowed(GraphicalEnvironment),
121}
122
123#[cfg(any(target_os = "linux", target_os = "freebsd"))]
124impl GraphicalEnvironment {
125    /// Reads the display variables from this process's environment.
126    ///
127    /// This only reads environment variables: whether they name a reachable display server is
128    /// checked when a platform connects. Leaves `activation_token` unset: see its documentation.
129    pub fn detect() -> Self {
130        Self {
131            wayland_display: std::env::var_os("WAYLAND_DISPLAY"),
132            x11_display: std::env::var_os("DISPLAY"),
133            xdg_runtime_dir: std::env::var_os("XDG_RUNTIME_DIR"),
134            activation_token: None,
135        }
136    }
137
138    /// Sets this environment's display variables on `command`, and removes the ones it doesn't
139    /// set, so the program connects to this graphical session rather than the one this process
140    /// started in. Leaves `XDG_ACTIVATION_TOKEN` alone.
141    pub fn apply_to(&self, command: &mut std::process::Command) {
142        for (name, value) in [
143            ("WAYLAND_DISPLAY", &self.wayland_display),
144            ("DISPLAY", &self.x11_display),
145            ("XDG_RUNTIME_DIR", &self.xdg_runtime_dir),
146        ] {
147            match value {
148                Some(value) => command.env(name, value),
149                None => command.env_remove(name),
150            };
151        }
152    }
153
154    /// Returns the compositor this environment selects: Wayland, then X11, then headless.
155    ///
156    /// Does not attempt to connect to the compositor.
157    pub fn guess_compositor(&self) -> &'static str {
158        let is_set =
159            |value: &Option<OsString>| value.as_ref().is_some_and(|value| !value.is_empty());
160        if cfg!(feature = "wayland") && is_set(&self.wayland_display) {
161            "Wayland"
162        } else if cfg!(feature = "x11") && is_set(&self.x11_display) {
163            "X11"
164        } else {
165            "Headless"
166        }
167    }
168}
169
170#[cfg(any(target_os = "linux", target_os = "freebsd"))]
171bitflags::bitflags! {
172    /// The windowing modes a platform may start in or switch to.
173    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
174    pub struct WindowingModes: u8 {
175        /// Connected to a Wayland compositor.
176        const WAYLAND = 1 << 0;
177        /// Connected to an X server.
178        const X11 = 1 << 1;
179        /// No display server. Windows lay out and handle input but draw nothing.
180        const HEADLESS = 1 << 2;
181    }
182}
183
184/// Controls whether the application participates in the system's foreground UI.
185///
186/// Only has an effect on macOS; other platforms ignore this setting. There,
187/// [`App::request_windowing`] sets it: `Accessory` while headless and `Regular` while windowed.
188/// Set it directly with [`App::set_activation_policy`] for the one case that doesn't cover: an
189/// accessory app that shows windows, such as a menu bar utility.
190#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
191pub enum ActivationPolicy {
192    /// Participate in foreground application UI, such as the Dock and menu bar on macOS.
193    #[default]
194    Regular,
195    /// Run without foreground application UI while retaining the ability to open windows.
196    Accessory,
197}
198
199#[expect(missing_docs)]
200pub trait Platform: 'static {
201    fn background_executor(&self) -> BackgroundExecutor;
202    fn foreground_executor(&self) -> ForegroundExecutor;
203    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
204
205    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
206    fn quit(&self);
207    /// Switches a capable platform between headless and windowed modes. See
208    /// [`App::request_windowing`].
209    /// Sets the windowing mode the platform starts in. Called before `run`. See
210    /// [`Application::with_windowing`].
211    fn set_initial_windowing(&self, _request: WindowingRequest) {}
212    fn request_windowing(&self, _request: WindowingRequest) -> Task<anyhow::Result<()>> {
213        Task::ready(Err(anyhow::anyhow!(
214            "this platform cannot switch between headless and windowed modes"
215        )))
216    }
217    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
218    fn activate(&self, ignoring_other_apps: bool);
219    /// Sets the initial or current activation policy. Has no effect outside macOS.
220    fn set_activation_policy(&self, _policy: ActivationPolicy) {}
221    fn hide(&self);
222    fn hide_other_apps(&self);
223    fn unhide_other_apps(&self);
224
225    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
226    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
227    /// Registers the callback invoked when displays may have been added or
228    /// removed, or a display's properties may have changed. GPUI rereads
229    /// [`Self::displays`] to find out what changed, so calls may be spurious.
230    /// The callback runs on the main thread outside of any window update.
231    fn on_displays_changed(&self, callback: Box<dyn FnMut()>);
232    fn active_window(&self) -> Option<WindowId>;
233    fn window_stack(&self) -> Option<Vec<WindowId>> {
234        None
235    }
236
237    fn is_screen_capture_supported(&self) -> bool {
238        false
239    }
240
241    fn screen_capture_sources(
242        &self,
243    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
244        let (sources_tx, sources_rx) = oneshot::channel();
245        sources_tx
246            .send(Err(anyhow::anyhow!(
247                "gpui was compiled without the screen-capture feature"
248            )))
249            .ok();
250        sources_rx
251    }
252
253    fn open_window(
254        &self,
255        handle: WindowId,
256        options: WindowParams,
257    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
258
259    /// Returns the appearance of the application's windows.
260    fn window_appearance(&self) -> WindowAppearance;
261
262    /// Overrides the appearance (light/dark) applied to the app's windows, independent
263    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
264    /// again. The override is reflected by [`Platform::window_appearance`].
265    ///
266    /// Currently only implemented on macOS, where it sets `NSApplication.appearance` so
267    /// the native window chrome (the window border and titlebar) of every window matches
268    /// a dark app theme even when the system is in light mode (or vice versa). A no-op on
269    /// other platforms.
270    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
271
272    /// States which GPU the application prefers the renderer to run on.
273    ///
274    /// The device is process-wide and chosen before the first window, so this has to be set before
275    /// the application runs. A backend that cannot choose ignores it. See decision `0008` in the
276    /// project record, `decisions/0008-gpu-preference-and-enumeration.md`.
277    fn set_gpu_preference(&self, _preference: GpuPreference) {}
278
279    /// The adapters the application could render on.
280    ///
281    /// Enumeration happens without creating a device, so this is safe to call before
282    /// [`run`](Self::run) and is what a preferences panel reads; it is also the list a
283    /// [`GpuPreference::Custom`] selector chooses from. The order is the platform's; a caller that
284    /// wants a particular one states a preference rather than indexing into this list.
285    ///
286    /// A backend that cannot enumerate returns **one** synthesized entry rather than an empty list:
287    /// the identity contract is that every reported adapter has a permanent part and a session, and
288    /// a single-GPU platform should still answer "which GPU" with "the GPU".
289    fn enumerate_gpus(&self) -> Vec<GpuAdapterInfo> {
290        vec![GpuAdapterInfo::single()]
291    }
292
293    /// What the renderer can import as a dma-buf surface, once it has a device.
294    ///
295    /// Answered on the device the renderer already made — no device is created — and `None` before
296    /// the first window or on a platform whose surfaces are not dma-bufs. A producer uses it to
297    /// choose a layout rather than discover a refusal at import time.
298    fn dmabuf_capabilities(&self) -> Option<DmaBufCapabilities> {
299        None
300    }
301
302    /// The adapter the renderer actually chose, once it has chosen one.
303    ///
304    /// The device is process-wide and selected before the first window, so the answer is `None`
305    /// until a window has created one; after that it is the adapter every window draws through. A
306    /// platform that cannot report it returns `None`.
307    fn active_gpu(&self) -> Option<GpuAdapterInfo> {
308        None
309    }
310
311    /// Returns the window button layout configuration when supported.
312    fn button_layout(&self) -> Option<WindowButtonLayout> {
313        None
314    }
315
316    fn open_url(&self, url: &str);
317    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
318    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
319
320    fn prompt_for_paths(
321        &self,
322        options: PathPromptOptions,
323    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
324    fn prompt_for_new_path(
325        &self,
326        directory: &Path,
327        suggested_name: Option<&str>,
328    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
329    fn can_select_mixed_files_and_dirs(&self) -> bool;
330    fn reveal_path(&self, path: &Path);
331    fn open_with_system(&self, path: &Path);
332
333    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
334    fn on_reopen(&self, callback: Box<dyn FnMut()>);
335    /// Registers the callback invoked when the system is about to sleep.
336    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
337    /// Registers the callback invoked when the system resumes from sleep.
338    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
339
340    // Mobile platform methods. On mobile the OS owns the application
341    // lifecycle: apps are backgrounded, foregrounded, and killed at the
342    // system's discretion, and must react rather than decide.
343
344    /// Registers a callback invoked whenever the application's lifecycle
345    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
346    /// its mapping onto iOS and Android.
347    ///
348    /// Desktop platforms never invoke this.
349    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
350
351    /// Registers a callback invoked when the OS signals memory pressure
352    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
353    ///
354    /// Desktop platforms never invoke this.
355    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
356
357    /// The platform's gesture recognition services, if it provides any
358    /// beyond gpui's portable recognizers. See
359    /// [`PlatformGestures`](crate::PlatformGestures).
360    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
361        None
362    }
363
364    fn set_menus(&self, menus: Vec<PlatformMenu>);
365
366    fn set_dock_menu(&self, menu: Vec<PlatformMenuItem>);
367    fn perform_dock_menu_action(&self, _action: usize) {}
368    fn add_recent_document(&self, _path: &Path) {}
369    fn update_jump_list(
370        &self,
371        _menus: Vec<PlatformMenuItem>,
372        _entries: Vec<SmallVec<[PathBuf; 2]>>,
373    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
374        Task::ready(Vec::new())
375    }
376    fn on_app_menu_action(&self, callback: Box<dyn FnMut(MenuCommandId)>);
377    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
378    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(MenuCommandId) -> bool>);
379
380    fn thermal_state(&self) -> ThermalState;
381    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
382    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
383
384    /// Sets the application's process-wide identity and user-visible name.
385    ///
386    /// The identifier is used for platform identity mechanisms such as the
387    /// Windows AppUserModelID. The name is used wherever the operating system
388    /// presents the application to the user. Call this once, early in startup,
389    /// before opening windows or posting notifications.
390    fn set_app_identity(&self, identifier: &str, name: &str) {
391        _ = (identifier, name);
392    }
393
394    /// Posts a notification to the operating system's notification center.
395    ///
396    /// Posting a notification whose [`SystemNotification::tag`] matches an
397    /// earlier one replaces that notification where the platform supports it.
398    /// No-op on platforms without notification support, or when delivery is
399    /// unavailable (e.g. authorization was denied).
400    fn show_system_notification(&self, notification: SystemNotification) {
401        _ = notification;
402    }
403
404    /// Removes the delivered or pending notification with this tag.
405    ///
406    /// Best-effort: some platforms cannot retract a notification once shown,
407    /// in which case it ages out of the notification center on its own.
408    fn dismiss_system_notification(&self, tag: &str) {
409        _ = tag;
410    }
411
412    /// Registers the callback invoked when the user activates a system
413    /// notification, either by clicking its body or one of its action
414    /// buttons.
415    ///
416    /// Implementations must invoke the callback on the main thread.
417    fn on_system_notification_response(
418        &self,
419        callback: Box<dyn FnMut(SystemNotificationResponse)>,
420    ) {
421        _ = callback;
422    }
423
424    fn compositor_name(&self) -> &'static str {
425        ""
426    }
427    /// The environment of the display server a platform that can switch windowing modes is
428    /// connected to. See [`App::graphical_environment`].
429    fn graphical_environment(&self) -> Option<GraphicalEnvironment> {
430        None
431    }
432    fn app_path(&self) -> Result<PathBuf>;
433    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
434
435    fn set_cursor_style(&self, style: CursorStyle);
436
437    /// Hides the mouse cursor until the user moves the mouse over one of
438    /// this application's windows.
439    fn hide_cursor_until_mouse_moves(&self);
440
441    /// Returns whether the mouse cursor is currently visible.
442    fn is_cursor_visible(&self) -> bool;
443
444    fn should_auto_hide_scrollbars(&self) -> bool;
445
446    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
447    fn write_to_clipboard(&self, item: ClipboardItem);
448
449    /// Reads the clipboard, resolving once its contents are available.
450    ///
451    /// Most platforms read synchronously and return a ready task. Platforms
452    /// whose clipboard access is inherently asynchronous and permission-gated
453    /// (e.g. the browser's async clipboard API) override this method; on those
454    /// platforms [`Platform::read_from_clipboard`] cannot return the clipboard
455    /// contents, so callers that can await should prefer this method.
456    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
457        Task::ready(Ok(self.read_from_clipboard()))
458    }
459
460    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
461    fn read_from_primary(&self) -> Option<ClipboardItem>;
462    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
463    fn write_to_primary(&self, item: ClipboardItem);
464
465    #[cfg(target_os = "macos")]
466    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
467    #[cfg(target_os = "macos")]
468    fn write_to_find_pasteboard(&self, item: ClipboardItem);
469
470    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
471    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
472    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
473
474    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
475    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
476    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
477}