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, MenuCommandId, PathPromptOptions, PlatformDisplay,
10    PlatformGestures, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformMenu,
11    PlatformMenuItem, PlatformWindow, ScreenCaptureSource, SystemNotification,
12    SystemNotificationResponse, Task, ThermalState, WindowAppearance, WindowButtonLayout, WindowId,
13    WindowParams,
14};
15use anyhow::Result;
16use futures::channel::oneshot;
17use gpui_engine::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    /// Returns the window button layout configuration when supported.
115    fn button_layout(&self) -> Option<WindowButtonLayout> {
116        None
117    }
118
119    fn open_url(&self, url: &str);
120    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
121    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
122
123    fn prompt_for_paths(
124        &self,
125        options: PathPromptOptions,
126    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
127    fn prompt_for_new_path(
128        &self,
129        directory: &Path,
130        suggested_name: Option<&str>,
131    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
132    fn can_select_mixed_files_and_dirs(&self) -> bool;
133    fn reveal_path(&self, path: &Path);
134    fn open_with_system(&self, path: &Path);
135
136    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
137    fn on_reopen(&self, callback: Box<dyn FnMut()>);
138    /// Registers the callback invoked when the system is about to sleep.
139    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
140    /// Registers the callback invoked when the system resumes from sleep.
141    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
142
143    // Mobile platform methods. On mobile the OS owns the application
144    // lifecycle: apps are backgrounded, foregrounded, and killed at the
145    // system's discretion, and must react rather than decide.
146
147    /// Registers a callback invoked whenever the application's lifecycle
148    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
149    /// its mapping onto iOS and Android.
150    ///
151    /// Desktop platforms never invoke this.
152    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
153
154    /// Registers a callback invoked when the OS signals memory pressure
155    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
156    ///
157    /// Desktop platforms never invoke this.
158    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
159
160    /// The platform's gesture recognition services, if it provides any
161    /// beyond gpui's portable recognizers. See
162    /// [`PlatformGestures`](crate::PlatformGestures).
163    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
164        None
165    }
166
167    fn set_menus(&self, menus: Vec<PlatformMenu>);
168
169    fn set_dock_menu(&self, menu: Vec<PlatformMenuItem>);
170    fn perform_dock_menu_action(&self, _action: usize) {}
171    fn add_recent_document(&self, _path: &Path) {}
172    fn update_jump_list(
173        &self,
174        _menus: Vec<PlatformMenuItem>,
175        _entries: Vec<SmallVec<[PathBuf; 2]>>,
176    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
177        Task::ready(Vec::new())
178    }
179    fn on_app_menu_action(&self, callback: Box<dyn FnMut(MenuCommandId)>);
180    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
181    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(MenuCommandId) -> bool>);
182
183    fn thermal_state(&self) -> ThermalState;
184    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
185    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
186
187    /// Sets the application's process-wide identity and user-visible name.
188    ///
189    /// The identifier is used for platform identity mechanisms such as the
190    /// Windows AppUserModelID. The name is used wherever the operating system
191    /// presents the application to the user. Call this once, early in startup,
192    /// before opening windows or posting notifications.
193    fn set_app_identity(&self, identifier: &str, name: &str) {
194        _ = (identifier, name);
195    }
196
197    /// Posts a notification to the operating system's notification center.
198    ///
199    /// Posting a notification whose [`SystemNotification::tag`] matches an
200    /// earlier one replaces that notification where the platform supports it.
201    /// No-op on platforms without notification support, or when delivery is
202    /// unavailable (e.g. authorization was denied).
203    fn show_system_notification(&self, notification: SystemNotification) {
204        _ = notification;
205    }
206
207    /// Removes the delivered or pending notification with this tag.
208    ///
209    /// Best-effort: some platforms cannot retract a notification once shown,
210    /// in which case it ages out of the notification center on its own.
211    fn dismiss_system_notification(&self, tag: &str) {
212        _ = tag;
213    }
214
215    /// Registers the callback invoked when the user activates a system
216    /// notification, either by clicking its body or one of its action
217    /// buttons.
218    ///
219    /// Implementations must invoke the callback on the main thread.
220    fn on_system_notification_response(
221        &self,
222        callback: Box<dyn FnMut(SystemNotificationResponse)>,
223    ) {
224        _ = callback;
225    }
226
227    fn compositor_name(&self) -> &'static str {
228        ""
229    }
230    fn app_path(&self) -> Result<PathBuf>;
231    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
232
233    fn set_cursor_style(&self, style: CursorStyle);
234
235    /// Hides the mouse cursor until the user moves the mouse over one of
236    /// this application's windows.
237    fn hide_cursor_until_mouse_moves(&self);
238
239    /// Returns whether the mouse cursor is currently visible.
240    fn is_cursor_visible(&self) -> bool;
241
242    fn should_auto_hide_scrollbars(&self) -> bool;
243
244    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
245    fn write_to_clipboard(&self, item: ClipboardItem);
246
247    /// Reads the clipboard, resolving once its contents are available.
248    ///
249    /// Most platforms read synchronously and return a ready task. Platforms
250    /// whose clipboard access is inherently asynchronous and permission-gated
251    /// (e.g. the browser's async clipboard API) override this method; on those
252    /// platforms [`Platform::read_from_clipboard`] cannot return the clipboard
253    /// contents, so callers that can await should prefer this method.
254    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
255        Task::ready(Ok(self.read_from_clipboard()))
256    }
257
258    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
259    fn read_from_primary(&self) -> Option<ClipboardItem>;
260    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
261    fn write_to_primary(&self, item: ClipboardItem);
262
263    #[cfg(target_os = "macos")]
264    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
265    #[cfg(target_os = "macos")]
266    fn write_to_find_pasteboard(&self, item: ClipboardItem);
267
268    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
269    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
270    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
271
272    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
273    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
274    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
275}