Skip to main content

gpui/
platform.rs

1mod app_menu;
2mod keyboard;
3mod keystroke;
4mod platform_info;
5
6#[cfg(all(target_os = "linux", feature = "wayland"))]
7#[expect(missing_docs)]
8pub mod layer_shell;
9
10/// Types for configuring parent-anchored popup windows such as menus, dropdowns and tooltips.
11pub mod popup;
12
13#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
14mod threaded_dispatcher;
15
16#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
17mod test;
18
19#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
20mod visual_test;
21
22#[cfg(all(
23    feature = "screen-capture",
24    any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
25))]
26pub mod scap_screen_capture;
27
28#[cfg(all(
29    any(target_os = "windows", target_os = "linux"),
30    feature = "screen-capture"
31))]
32pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
33#[cfg(not(feature = "screen-capture"))]
34pub(crate) type PlatformScreenCaptureFrame = ();
35#[cfg(all(target_os = "macos", feature = "screen-capture"))]
36pub(crate) type PlatformScreenCaptureFrame =
37    objc2_core_foundation::CFRetained<objc2_core_video::CVImageBuffer>;
38
39use crate::{
40    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds, Corners,
41    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Edges, ExternalDragPayload, Font,
42    FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap,
43    LineLayout, MissingGlyphSink, Pixels, PlatformGestures, PlatformInput, Point, Priority,
44    RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
45    ShapedRun, SharedString, Size, SvgRenderer, SystemWindowTab, Task, Window, WindowControlArea,
46    hash, point, px, size,
47};
48#[cfg(any(target_os = "linux", target_os = "freebsd"))]
49use anyhow::bail;
50use anyhow::{Context as _, Result};
51use async_task::Runnable;
52use collections::FxHashMap;
53use futures::channel::oneshot;
54#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
55use image::RgbaImage;
56use image::codecs::gif::GifDecoder;
57use image::{AnimationDecoder as _, DynamicImage, Frame};
58use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
59use scheduler::Instant;
60pub use scheduler::RunnableMeta;
61use schemars::JsonSchema;
62use seahash::SeaHasher;
63use serde::{Deserialize, Serialize};
64use smallvec::SmallVec;
65use std::borrow::Cow;
66use std::collections::hash_map::Entry;
67use std::hash::{Hash, Hasher};
68use std::io::Cursor;
69use std::ops;
70use std::time::Duration;
71use std::{
72    ffi::OsString,
73    fmt::{self, Debug},
74    ops::Range,
75    path::{Path, PathBuf},
76    rc::Rc,
77    sync::Arc,
78};
79use strum::EnumIter;
80use uuid::Uuid;
81
82pub use app_menu::*;
83pub use keyboard::*;
84pub use keystroke::*;
85
86/// Whether the platform is presenting a window's frames.
87///
88/// This is about presentation, not the window's shown/hidden state: a shown
89/// window that is fully covered by other windows, minimized, on another
90/// Space or virtual desktop, or on a display that is asleep is `Hidden`. A
91/// window only partly covered by other windows is `Visible`.
92///
93/// Each platform reports from a single source, and what that source can see
94/// differs:
95///
96/// * macOS: `NSWindow.occlusionState`. Covers all of the cases above.
97/// * Windows: `WS_VISIBLE` and the minimized state. Windows keeps compositing
98///   covered windows for thumbnails and Alt-Tab and offers no occlusion
99///   notification, so a fully covered window stays `Visible`. Display sleep is
100///   not reported either.
101/// * Wayland: the `xdg_toplevel` `suspended` state (xdg-shell v6). Compositors
102///   that don't support it never report `Hidden`.
103/// * X11: mapped state plus `VisibilityNotify`. Compositing window managers
104///   generally never report a window as fully obscured, so covering is only
105///   detected without compositing.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum WindowVisibility {
108    /// At least part of the window is being presented; frames drawn for it
109    /// will be shown.
110    Visible,
111    /// No part of the window is being presented. The platform will not
112    /// request frames for it until it becomes visible again.
113    Hidden,
114}
115
116impl WindowVisibility {
117    /// Whether frames drawn for the window will be shown.
118    pub fn is_visible(self) -> bool {
119        self == Self::Visible
120    }
121}
122
123/// A change to the connected displays, reported through
124/// [`App::observe_displays`](crate::App::observe_displays).
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126#[non_exhaustive]
127pub enum DisplayEvent {
128    /// A display was connected.
129    Added(DisplayId),
130    /// A display was disconnected. Windows on it are moved to another
131    /// display by the platform.
132    Removed(DisplayId),
133    /// A connected display's properties changed.
134    Changed {
135        /// The display that changed.
136        id: DisplayId,
137        /// Which of its properties changed.
138        changes: DisplayChanges,
139    },
140}
141
142bitflags::bitflags! {
143    /// The properties of a display that a [`DisplayEvent::Changed`] reports.
144    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145    pub struct DisplayChanges: u8 {
146        /// [`PlatformDisplay::bounds`] changed.
147        const BOUNDS = 1 << 0;
148        /// [`PlatformDisplay::refresh_interval`] changed.
149        const REFRESH_INTERVAL = 1 << 1;
150    }
151}
152
153/// Converts a refresh rate in hertz to the time between refreshes, rejecting
154/// the zero, negative, and non-finite rates platforms use to mean "unknown".
155pub fn refresh_interval_from_hz(hertz: f64) -> Option<Duration> {
156    (hertz.is_finite() && hertz > 0.0).then(|| Duration::from_secs_f64(1.0 / hertz))
157}
158
159/// Controls whether the application participates in the system's foreground UI.
160///
161/// Only has an effect on macOS; other platforms ignore this setting. There,
162/// [`App::request_windowing`] sets it: `Accessory` while headless and `Regular` while windowed.
163/// Set it directly with [`App::set_activation_policy`] for the one case that doesn't cover: an
164/// accessory app that shows windows, such as a menu bar utility.
165#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
166pub enum ActivationPolicy {
167    /// Participate in foreground application UI, such as the Dock and menu bar on macOS.
168    #[default]
169    Regular,
170    /// Run without foreground application UI while retaining the ability to open windows.
171    Accessory,
172}
173
174#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
175pub(crate) use test::*;
176
177#[cfg(any(test, feature = "test-support"))]
178pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
179
180#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
181pub use threaded_dispatcher::ThreadedDispatcher;
182
183#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
184pub use visual_test::VisualTestPlatform;
185
186/// Keeps an operating system activity, such as an idle sleep inhibitor, alive until dropped.
187pub struct ActivityGuard {
188    _release: gpui_util::Deferred<Box<dyn FnOnce() + Send>>,
189}
190
191impl ActivityGuard {
192    /// Runs `release` when the guard is dropped.
193    pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
194        Self {
195            _release: gpui_util::defer(Box::new(release)),
196        }
197    }
198
199    /// A guard for platforms without a corresponding activity.
200    pub fn noop() -> Self {
201        Self::new(|| {})
202    }
203}
204
205// TODO(jk): return an enum instead of a string
206/// Return which compositor we're guessing we'll use.
207/// Does not attempt to connect to the given compositor.
208#[cfg(any(target_os = "linux", target_os = "freebsd"))]
209#[inline]
210pub fn guess_compositor() -> &'static str {
211    if std::env::var_os("ZED_HEADLESS").is_some() {
212        return "Headless";
213    }
214    GraphicalEnvironment::detect().guess_compositor()
215}
216
217/// The graphical session to connect to: the variables that locate its display server, as some
218/// process sees them.
219///
220/// A long-running process can outlive the graphical session it was started in, so a platform
221/// that attaches to a display server later can be given a fresher environment than its own.
222/// While connected, programs the platform launches (for example to open a URL) get these
223/// variables instead of the ones this process started with. Apply
224/// [`App::graphical_environment`] to the programs an app launches, for the same reason.
225#[cfg(any(target_os = "linux", target_os = "freebsd"))]
226#[derive(Clone, Debug, Default)]
227pub struct GraphicalEnvironment {
228    /// `WAYLAND_DISPLAY`: a socket name relative to `xdg_runtime_dir`, or an absolute path.
229    pub wayland_display: Option<OsString>,
230    /// `DISPLAY`: the X11 display name.
231    pub x11_display: Option<OsString>,
232    /// `XDG_RUNTIME_DIR`: the directory containing Wayland sockets.
233    pub xdg_runtime_dir: Option<OsString>,
234    /// `XDG_ACTIVATION_TOKEN`: lets the first window take focus on Wayland.
235    ///
236    /// A platform takes this process's token from its environment when it's created, and uses
237    /// it if it starts windowed. Pass one here to switch to windowed mode later, for example
238    /// the token of the process that asked for a window.
239    pub activation_token: Option<String>,
240}
241
242/// The graphical session to connect to: the Windows session whose desktop windows appear on.
243///
244/// A process can only show windows in its own session, so switching to windowed mode fails if
245/// this names another one, for example when a process started over SSH is asked to show a
246/// window on the desktop.
247#[cfg(target_os = "windows")]
248#[derive(Clone, Debug, Default)]
249pub struct GraphicalEnvironment {
250    /// The session ID, as `ProcessIdToSessionId` reports it. `None` means this process's own
251    /// session.
252    pub session_id: Option<u32>,
253}
254
255#[cfg(target_os = "windows")]
256impl GraphicalEnvironment {
257    /// Returns the environment of this process's session.
258    pub fn detect() -> Self {
259        let mut session_id = 0;
260        // SAFETY: `session_id` is a valid pointer for the call's duration.
261        let result = unsafe {
262            windows::Win32::System::RemoteDesktop::ProcessIdToSessionId(
263                windows::Win32::System::Threading::GetCurrentProcessId(),
264                &mut session_id,
265            )
266        };
267        Self {
268            session_id: result.is_ok().then_some(session_id),
269        }
270    }
271
272    /// Does nothing on this platform: programs inherit the session of the process that
273    /// starts them.
274    pub fn apply_to(&self, _command: &mut std::process::Command) {}
275}
276
277/// The graphical session to connect to. Carries nothing yet on this platform.
278#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
279#[derive(Clone, Debug, Default)]
280pub struct GraphicalEnvironment;
281
282#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
283impl GraphicalEnvironment {
284    /// Returns the environment of this process's graphical session.
285    pub fn detect() -> Self {
286        Self
287    }
288
289    /// Does nothing on this platform.
290    pub fn apply_to(&self, _command: &mut std::process::Command) {}
291}
292
293/// A display mode for [`App::request_windowing`] to switch to.
294#[derive(Clone, Debug)]
295pub enum WindowingRequest {
296    /// No display server. Windows lay out and handle input but draw nothing.
297    Headless,
298    /// Connected to the display server that the environment names.
299    Windowed(GraphicalEnvironment),
300}
301
302#[cfg(any(target_os = "linux", target_os = "freebsd"))]
303impl GraphicalEnvironment {
304    /// Reads the display variables from this process's environment.
305    ///
306    /// This only reads environment variables: whether they name a reachable display server is
307    /// checked when a platform connects. Leaves `activation_token` unset: see its documentation.
308    pub fn detect() -> Self {
309        Self {
310            wayland_display: std::env::var_os("WAYLAND_DISPLAY"),
311            x11_display: std::env::var_os("DISPLAY"),
312            xdg_runtime_dir: std::env::var_os("XDG_RUNTIME_DIR"),
313            activation_token: None,
314        }
315    }
316
317    /// Sets this environment's display variables on `command`, and removes the ones it doesn't
318    /// set, so the program connects to this graphical session rather than the one this process
319    /// started in. Leaves `XDG_ACTIVATION_TOKEN` alone.
320    pub fn apply_to(&self, command: &mut std::process::Command) {
321        for (name, value) in [
322            ("WAYLAND_DISPLAY", &self.wayland_display),
323            ("DISPLAY", &self.x11_display),
324            ("XDG_RUNTIME_DIR", &self.xdg_runtime_dir),
325        ] {
326            match value {
327                Some(value) => command.env(name, value),
328                None => command.env_remove(name),
329            };
330        }
331    }
332
333    /// Returns the compositor this environment selects: Wayland, then X11, then headless.
334    ///
335    /// Does not attempt to connect to the compositor.
336    pub fn guess_compositor(&self) -> &'static str {
337        let is_set =
338            |value: &Option<OsString>| value.as_ref().is_some_and(|value| !value.is_empty());
339        if cfg!(feature = "wayland") && is_set(&self.wayland_display) {
340            "Wayland"
341        } else if cfg!(feature = "x11") && is_set(&self.x11_display) {
342            "X11"
343        } else {
344            "Headless"
345        }
346    }
347}
348
349#[cfg(any(target_os = "linux", target_os = "freebsd"))]
350bitflags::bitflags! {
351    /// The windowing modes a platform may start in or switch to.
352    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
353    pub struct WindowingModes: u8 {
354        /// Connected to a Wayland compositor.
355        const WAYLAND = 1 << 0;
356        /// Connected to an X server.
357        const X11 = 1 << 1;
358        /// No display server. Windows lay out and handle input but draw nothing.
359        const HEADLESS = 1 << 2;
360    }
361}
362
363pub use platform_info::{OperatingSystem, PlatformInfo, PlatformTarget};
364
365#[expect(missing_docs)]
366pub trait Platform: 'static {
367    /// Returns the host OS and application target reported by this backend.
368    fn platform_info(&self) -> PlatformInfo;
369
370    fn background_executor(&self) -> BackgroundExecutor;
371    fn foreground_executor(&self) -> ForegroundExecutor;
372    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
373
374    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
375    fn quit(&self);
376    /// Switches a capable platform between headless and windowed modes. See
377    /// [`App::request_windowing`].
378    /// Sets the windowing mode the platform starts in. Called before `run`. See
379    /// [`Application::with_windowing`].
380    fn set_initial_windowing(&self, _request: WindowingRequest) {}
381    fn request_windowing(&self, _request: WindowingRequest) -> Task<anyhow::Result<()>> {
382        Task::ready(Err(anyhow::anyhow!(
383            "this platform cannot switch between headless and windowed modes"
384        )))
385    }
386    fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
387    fn activate(&self, ignoring_other_apps: bool);
388    /// Sets the initial or current activation policy. Has no effect outside macOS.
389    fn set_activation_policy(&self, _policy: ActivationPolicy) {}
390    fn hide(&self);
391    fn hide_other_apps(&self);
392    fn unhide_other_apps(&self);
393
394    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
395    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
396    /// Registers the callback invoked when displays may have been added or
397    /// removed, or a display's properties may have changed. GPUI rereads
398    /// [`Self::displays`] to find out what changed, so calls may be spurious.
399    /// The callback runs on the main thread outside of any window update.
400    fn on_displays_changed(&self, callback: Box<dyn FnMut()>);
401    fn active_window(&self) -> Option<AnyWindowHandle>;
402    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
403        None
404    }
405
406    fn is_screen_capture_supported(&self) -> bool {
407        false
408    }
409
410    fn screen_capture_sources(
411        &self,
412    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
413        let (sources_tx, sources_rx) = oneshot::channel();
414        sources_tx
415            .send(Err(anyhow::anyhow!(
416                "gpui was compiled without the screen-capture feature"
417            )))
418            .ok();
419        sources_rx
420    }
421
422    fn open_window(
423        &self,
424        handle: AnyWindowHandle,
425        options: WindowParams,
426    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
427
428    /// Returns the appearance of the application's windows.
429    fn window_appearance(&self) -> WindowAppearance;
430
431    /// Overrides the appearance (light/dark) applied to the app's windows, independent
432    /// of the OS-wide setting. Pass `None` to clear the override and follow the system
433    /// again. The override is reflected by [`Platform::window_appearance`].
434    ///
435    /// Currently only implemented on macOS, where it sets `NSApplication.appearance` so
436    /// the native window chrome (the window border and titlebar) of every window matches
437    /// a dark app theme even when the system is in light mode (or vice versa). A no-op on
438    /// other platforms.
439    fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
440
441    /// Returns the window button layout configuration when supported.
442    fn button_layout(&self) -> Option<WindowButtonLayout> {
443        None
444    }
445
446    fn open_url(&self, url: &str);
447    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
448    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
449
450    fn prompt_for_paths(
451        &self,
452        options: PathPromptOptions,
453    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
454    fn prompt_for_new_path(
455        &self,
456        directory: &Path,
457        suggested_name: Option<&str>,
458    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
459    fn can_select_mixed_files_and_dirs(&self) -> bool;
460    fn reveal_path(&self, path: &Path);
461    fn open_with_system(&self, path: &Path);
462
463    fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
464    fn on_reopen(&self, callback: Box<dyn FnMut()>);
465    /// Registers the callback invoked when the system is about to sleep.
466    fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
467    /// Registers the callback invoked when the system resumes from sleep.
468    fn on_system_wake(&self, callback: Box<dyn FnMut()>);
469
470    // Mobile platform methods. On mobile the OS owns the application
471    // lifecycle: apps are backgrounded, foregrounded, and killed at the
472    // system's discretion, and must react rather than decide.
473
474    /// Registers a callback invoked whenever the application's lifecycle
475    /// phase changes. See [`AppLifecyclePhase`] for the phase vocabulary and
476    /// its mapping onto iOS and Android.
477    ///
478    /// Desktop platforms never invoke this.
479    fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
480
481    /// Registers a callback invoked when the OS signals memory pressure
482    /// (iOS `didReceiveMemoryWarning`, Android `onTrimMemory`).
483    ///
484    /// Desktop platforms never invoke this.
485    fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
486
487    /// The platform's gesture recognition services, if it provides any
488    /// beyond gpui's portable recognizers. See
489    /// [`PlatformGestures`](crate::PlatformGestures).
490    fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
491        None
492    }
493
494    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
495    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
496        None
497    }
498
499    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
500    fn perform_dock_menu_action(&self, _action: usize) {}
501    fn add_recent_document(&self, _path: &Path) {}
502    fn update_jump_list(
503        &self,
504        _menus: Vec<MenuItem>,
505        _entries: Vec<SmallVec<[PathBuf; 2]>>,
506    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
507        Task::ready(Vec::new())
508    }
509    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
510    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
511    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
512
513    fn thermal_state(&self) -> ThermalState;
514    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
515    fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
516
517    /// Sets the application's process-wide identity and user-visible name.
518    ///
519    /// The identifier is used for platform identity mechanisms such as the
520    /// Windows AppUserModelID. The name is used wherever the operating system
521    /// presents the application to the user. Call this once, early in startup,
522    /// before opening windows or posting notifications.
523    fn set_app_identity(&self, identifier: &str, name: &str) {
524        _ = (identifier, name);
525    }
526
527    /// Posts a notification to the operating system's notification center.
528    ///
529    /// Posting a notification whose [`SystemNotification::tag`] matches an
530    /// earlier one replaces that notification where the platform supports it.
531    /// No-op on platforms without notification support, or when delivery is
532    /// unavailable (e.g. authorization was denied).
533    fn show_system_notification(&self, notification: SystemNotification) {
534        _ = notification;
535    }
536
537    /// Removes the delivered or pending notification with this tag.
538    ///
539    /// Best-effort: some platforms cannot retract a notification once shown,
540    /// in which case it ages out of the notification center on its own.
541    fn dismiss_system_notification(&self, tag: &str) {
542        _ = tag;
543    }
544
545    /// Registers the callback invoked when the user activates a system
546    /// notification, either by clicking its body or one of its action
547    /// buttons.
548    ///
549    /// Implementations must invoke the callback on the main thread.
550    fn on_system_notification_response(
551        &self,
552        callback: Box<dyn FnMut(SystemNotificationResponse)>,
553    ) {
554        _ = callback;
555    }
556
557    fn compositor_name(&self) -> &'static str {
558        ""
559    }
560    /// The environment of the display server a platform that can switch windowing modes is
561    /// connected to. See [`App::graphical_environment`].
562    fn graphical_environment(&self) -> Option<GraphicalEnvironment> {
563        None
564    }
565    fn app_path(&self) -> Result<PathBuf>;
566    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
567
568    fn set_cursor_style(&self, style: CursorStyle);
569
570    /// Hides the mouse cursor until the user moves the mouse over one of
571    /// this application's windows.
572    fn hide_cursor_until_mouse_moves(&self);
573
574    /// Returns whether the mouse cursor is currently visible.
575    fn is_cursor_visible(&self) -> bool;
576
577    fn should_auto_hide_scrollbars(&self) -> bool;
578
579    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
580    fn write_to_clipboard(&self, item: ClipboardItem);
581
582    /// Reads the clipboard, resolving once its contents are available.
583    ///
584    /// Most platforms read synchronously and return a ready task. Platforms
585    /// whose clipboard access is inherently asynchronous and permission-gated
586    /// (e.g. the browser's async clipboard API) override this method; on those
587    /// platforms [`Platform::read_from_clipboard`] cannot return the clipboard
588    /// contents, so callers that can await should prefer this method.
589    fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
590        Task::ready(Ok(self.read_from_clipboard()))
591    }
592
593    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
594    fn read_from_primary(&self) -> Option<ClipboardItem>;
595    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
596    fn write_to_primary(&self, item: ClipboardItem);
597
598    #[cfg(target_os = "macos")]
599    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
600    #[cfg(target_os = "macos")]
601    fn write_to_find_pasteboard(&self, item: ClipboardItem);
602
603    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
604
605    /// Atomically writes credentials unless an item with this URL and username exists.
606    ///
607    /// Returns `true` if written, or `false` if the existing item was left unchanged.
608    #[cfg(target_os = "macos")]
609    fn write_credentials_if_not_exists(
610        &self,
611        _url: &str,
612        _username: &str,
613        _password: &[u8],
614    ) -> Task<Result<bool>> {
615        Task::ready(Err(anyhow::anyhow!(
616            "this platform does not support atomic credential writes"
617        )))
618    }
619
620    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
621    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
622
623    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
624    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
625    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
626}
627
628/// A handle to a platform's display, e.g. a monitor or laptop screen.
629pub trait PlatformDisplay: Debug {
630    /// Get the ID for this display
631    fn id(&self) -> DisplayId;
632
633    /// Returns a stable identifier for this display that can be persisted and used
634    /// across system restarts.
635    fn uuid(&self) -> Result<Uuid>;
636
637    /// Get the bounds for this display
638    fn bounds(&self) -> Bounds<Pixels>;
639
640    /// The time between the display's refreshes. Variable refresh rate
641    /// displays report their maximum rate. `None` when the platform doesn't
642    /// report it.
643    ///
644    /// May query the operating system; GPUI caches it per display, see
645    /// [`Window::refresh_interval`](crate::Window::refresh_interval).
646    fn refresh_interval(&self) -> Option<Duration>;
647
648    /// Get the visible bounds for this display, excluding taskbar/dock areas.
649    /// This is the usable area where windows can be placed without being obscured.
650    /// Defaults to the full display bounds if not overridden.
651    fn visible_bounds(&self) -> Bounds<Pixels> {
652        self.bounds()
653    }
654
655    /// Get the default bounds for this display to place a window
656    fn default_bounds(&self) -> Bounds<Pixels> {
657        let bounds = self.bounds();
658        let center = bounds.center();
659        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
660
661        let offset = clipped_window_size / 2.0;
662        let origin = point(center.x - offset.width, center.y - offset.height);
663        Bounds::new(origin, clipped_window_size)
664    }
665}
666
667/// A notification posted to the operating system's notification center,
668/// rather than rendered as in-app UI.
669#[derive(Clone, Debug, PartialEq, Eq)]
670pub struct SystemNotification {
671    /// Stable identity for the notification. Posting a new notification with
672    /// the same tag replaces the previous one where the platform supports it,
673    /// and responses carry the tag back to the application.
674    pub tag: SharedString,
675    /// The notification's headline.
676    pub title: SharedString,
677    /// Additional text displayed below the title.
678    pub body: SharedString,
679    /// Buttons offered on the notification. Platforms that cannot display
680    /// action buttons show the notification without them.
681    pub actions: Vec<SystemNotificationAction>,
682}
683
684/// A button offered on a [`SystemNotification`].
685#[derive(Clone, Debug, PartialEq, Eq, Hash)]
686pub struct SystemNotificationAction {
687    /// Identifies the action in [`SystemNotificationResponse::action_id`]
688    /// when the user presses this button.
689    pub id: SharedString,
690    /// The button's user-visible label.
691    pub label: SharedString,
692}
693
694/// The user's activation of a [`SystemNotification`].
695#[derive(Clone, Debug, PartialEq, Eq)]
696pub struct SystemNotificationResponse {
697    /// The [`SystemNotification::tag`] of the activated notification.
698    pub tag: SharedString,
699    /// The pressed action button's [`SystemNotificationAction::id`], or
700    /// `None` when the user activated the notification body itself.
701    pub action_id: Option<SharedString>,
702}
703
704/// Thermal state of the system
705#[derive(Debug, Clone, Copy, PartialEq, Eq)]
706pub enum ThermalState {
707    /// System has no thermal constraints
708    Nominal,
709    /// System is slightly constrained, reduce discretionary work
710    Fair,
711    /// System is moderately constrained, reduce CPU/GPU intensive work
712    Serious,
713    /// System is critically constrained, minimize all resource usage
714    Critical,
715}
716
717/// Metadata for a given [ScreenCaptureSource]
718#[derive(Clone)]
719pub struct SourceMetadata {
720    /// Opaque identifier of this screen.
721    pub id: u64,
722    /// Human-readable label for this source.
723    pub label: Option<SharedString>,
724    /// Whether this source is the main display.
725    pub is_main: Option<bool>,
726    /// Video resolution of this source.
727    pub resolution: Size<DevicePixels>,
728}
729
730/// A source of on-screen video content that can be captured.
731pub trait ScreenCaptureSource {
732    /// Returns metadata for this source.
733    fn metadata(&self) -> Result<SourceMetadata>;
734
735    /// Start capture video from this source, invoking the given callback
736    /// with each frame.
737    fn stream(
738        &self,
739        foreground_executor: &ForegroundExecutor,
740        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
741    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
742}
743
744/// A video stream captured from a screen.
745pub trait ScreenCaptureStream {
746    /// Returns metadata for this source.
747    fn metadata(&self) -> Result<SourceMetadata>;
748}
749
750/// A frame of video captured from a screen.
751pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
752
753/// An opaque identifier for a hardware display
754#[derive(PartialEq, Eq, Hash, Copy, Clone)]
755pub struct DisplayId(pub(crate) u64);
756
757impl DisplayId {
758    /// Create a new `DisplayId` from a raw platform display identifier.
759    pub fn new(id: u64) -> Self {
760        Self(id)
761    }
762}
763
764impl From<u64> for DisplayId {
765    fn from(id: u64) -> Self {
766        Self(id)
767    }
768}
769
770impl From<DisplayId> for u64 {
771    fn from(id: DisplayId) -> Self {
772        id.0
773    }
774}
775
776impl Debug for DisplayId {
777    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
778        write!(f, "DisplayId({})", self.0)
779    }
780}
781
782/// Which part of the window to resize
783#[derive(Debug, Clone, Copy, PartialEq, Eq)]
784pub enum ResizeEdge {
785    /// The top edge
786    Top,
787    /// The top right corner
788    TopRight,
789    /// The right edge
790    Right,
791    /// The bottom right corner
792    BottomRight,
793    /// The bottom edge
794    Bottom,
795    /// The bottom left corner
796    BottomLeft,
797    /// The left edge
798    Left,
799    /// The top left corner
800    TopLeft,
801}
802
803/// A type to describe the appearance of a window
804#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
805pub enum WindowDecorations {
806    #[default]
807    /// Server side decorations
808    Server,
809    /// Client side decorations
810    Client,
811}
812
813/// A type to describe how this window is currently configured
814#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
815pub enum Decorations {
816    /// The window is configured to use server side decorations
817    #[default]
818    Server,
819    /// The window is configured to use client side decorations
820    Client {
821        /// The edge tiling state
822        tiling: Tiling,
823    },
824}
825
826/// What window controls this platform supports
827#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
828pub struct WindowControls {
829    /// Whether this platform supports fullscreen
830    pub fullscreen: bool,
831    /// Whether this platform supports maximize
832    pub maximize: bool,
833    /// Whether this platform supports minimize
834    pub minimize: bool,
835    /// Whether this platform supports a window menu
836    pub window_menu: bool,
837}
838
839impl Default for WindowControls {
840    fn default() -> Self {
841        // Assume that we can do anything, unless told otherwise
842        Self {
843            fullscreen: true,
844            maximize: true,
845            minimize: true,
846            window_menu: true,
847        }
848    }
849}
850
851/// A window control button type used in [`WindowButtonLayout`].
852#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
853pub enum WindowButton {
854    /// The minimize button
855    Minimize,
856    /// The maximize button
857    Maximize,
858    /// The close button
859    Close,
860}
861
862impl WindowButton {
863    /// Returns a stable element ID for rendering this button.
864    pub fn id(&self) -> &'static str {
865        match self {
866            WindowButton::Minimize => "minimize",
867            WindowButton::Maximize => "maximize",
868            WindowButton::Close => "close",
869        }
870    }
871
872    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
873    fn index(&self) -> usize {
874        match self {
875            WindowButton::Minimize => 0,
876            WindowButton::Maximize => 1,
877            WindowButton::Close => 2,
878        }
879    }
880}
881
882/// Maximum number of [`WindowButton`]s per side in the titlebar.
883pub const MAX_BUTTONS_PER_SIDE: usize = 3;
884
885/// Describes which [`WindowButton`]s appear on each side of the titlebar.
886///
887/// On Linux, this is read from the desktop environment's configuration
888/// (e.g. GNOME's `gtk-decoration-layout` gsetting) via [`WindowButtonLayout::parse`].
889#[derive(Debug, Clone, Copy, PartialEq, Eq)]
890pub struct WindowButtonLayout {
891    /// Buttons on the left side of the titlebar.
892    pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
893    /// Buttons on the right side of the titlebar.
894    pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
895}
896
897#[cfg(any(target_os = "linux", target_os = "freebsd"))]
898impl WindowButtonLayout {
899    /// Returns Zed's built-in fallback button layout for Linux titlebars.
900    pub fn linux_default() -> Self {
901        Self {
902            left: [None; MAX_BUTTONS_PER_SIDE],
903            right: [
904                Some(WindowButton::Minimize),
905                Some(WindowButton::Maximize),
906                Some(WindowButton::Close),
907            ],
908        }
909    }
910
911    /// Parses a GNOME-style `button-layout` string (e.g. `"close,minimize:maximize"`).
912    pub fn parse(layout_string: &str) -> Result<Self> {
913        fn parse_side(
914            s: &str,
915            seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
916            unrecognized: &mut Vec<String>,
917        ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
918            let mut result = [None; MAX_BUTTONS_PER_SIDE];
919            let mut i = 0;
920            for name in s.split(',') {
921                let trimmed = name.trim();
922                if trimmed.is_empty() {
923                    continue;
924                }
925                let button = match trimmed {
926                    "minimize" => Some(WindowButton::Minimize),
927                    "maximize" => Some(WindowButton::Maximize),
928                    "close" => Some(WindowButton::Close),
929                    other => {
930                        unrecognized.push(other.to_string());
931                        None
932                    }
933                };
934                if let Some(button) = button {
935                    if seen_buttons[button.index()] {
936                        continue;
937                    }
938                    if let Some(slot) = result.get_mut(i) {
939                        *slot = Some(button);
940                        seen_buttons[button.index()] = true;
941                        i += 1;
942                    }
943                }
944            }
945            result
946        }
947
948        let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
949        let mut unrecognized = Vec::new();
950        let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
951        let layout = Self {
952            left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
953            right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
954        };
955
956        if !unrecognized.is_empty()
957            && layout.left.iter().all(Option::is_none)
958            && layout.right.iter().all(Option::is_none)
959        {
960            bail!(
961                "button layout string {:?} contains no valid buttons (unrecognized: {})",
962                layout_string,
963                unrecognized.join(", ")
964            );
965        }
966
967        Ok(layout)
968    }
969
970    /// Formats the layout back into a GNOME-style `button-layout` string.
971    #[cfg(test)]
972    pub fn format(&self) -> String {
973        fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
974            buttons
975                .iter()
976                .flatten()
977                .map(|button| match button {
978                    WindowButton::Minimize => "minimize",
979                    WindowButton::Maximize => "maximize",
980                    WindowButton::Close => "close",
981                })
982                .collect::<Vec<_>>()
983                .join(",")
984        }
985
986        format!("{}:{}", format_side(&self.left), format_side(&self.right))
987    }
988}
989
990/// A type to describe which sides of the window are currently tiled in some way
991#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
992pub struct Tiling {
993    /// Whether the top edge is tiled
994    pub top: bool,
995    /// Whether the left edge is tiled
996    pub left: bool,
997    /// Whether the right edge is tiled
998    pub right: bool,
999    /// Whether the bottom edge is tiled
1000    pub bottom: bool,
1001}
1002
1003impl Tiling {
1004    /// Initializes a [`Tiling`] type with all sides tiled
1005    pub fn tiled() -> Self {
1006        Self {
1007            top: true,
1008            left: true,
1009            right: true,
1010            bottom: true,
1011        }
1012    }
1013
1014    /// Whether any edge is tiled
1015    pub fn is_tiled(&self) -> bool {
1016        self.top || self.left || self.right || self.bottom
1017    }
1018}
1019
1020/// Callbacks for the accessibility adapter.
1021pub struct A11yCallbacks {
1022    /// Called when the adapter is activated (a screen reader connects).
1023    pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
1024    /// Called when an action is requested by the screen reader.
1025    pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
1026    /// Called when the adapter is deactivated (screen reader disconnects).
1027    pub deactivation: Box<dyn Fn() + Send + 'static>,
1028}
1029
1030/// The source of a platform frame request's timestamp.
1031#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1032pub enum FrameRequestSource {
1033    /// An OS callback or compositor-paced frame request.
1034    #[default]
1035    NativeCallback,
1036    /// A refresh timer, retry, queued wakeup, or fallback sleep paced by the app.
1037    LocalSchedule,
1038}
1039
1040#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1041#[expect(missing_docs)]
1042pub struct RequestFrameOptions {
1043    /// Whether a presentation is required.
1044    pub require_presentation: bool,
1045    /// Force refresh of all rendering states when true.
1046    pub force_render: bool,
1047    /// When the platform first requested this frame, before main-thread dispatch.
1048    ///
1049    /// `None` means the captured request time is unavailable.
1050    /// Coalesced requests carry their first request time, not their delivery time.
1051    /// Built-in backends collect timestamps only with the `profiler` feature.
1052    pub signal_at: Option<Instant>,
1053    /// Distinguishes native callbacks from local scheduling requests.
1054    pub signal_source: FrameRequestSource,
1055}
1056
1057/// Preserves the first platform frame request time across coalesced notifications.
1058///
1059/// Producers may record from a platform thread without waiting for the UI thread.
1060/// The consumer drains the timestamp before dispatching the request on the UI thread.
1061/// The timestamp offset and source share one atomic word so coalescing cannot
1062/// mix a request's time with another request's source. The low bit encodes the
1063/// source; the remaining bits encode nanoseconds from `origin`. `u64::MAX` is empty.
1064/// Without the `profiler` feature, this accumulator has no timestamp storage.
1065pub struct PlatformFrameSignal {
1066    #[cfg(feature = "profiler")]
1067    origin: Instant,
1068    #[cfg(feature = "profiler")]
1069    first_signal: std::sync::atomic::AtomicU64,
1070}
1071
1072impl Default for PlatformFrameSignal {
1073    fn default() -> Self {
1074        Self::new()
1075    }
1076}
1077
1078impl PlatformFrameSignal {
1079    /// Creates an empty signal accumulator.
1080    pub fn new() -> Self {
1081        Self {
1082            #[cfg(feature = "profiler")]
1083            origin: Instant::now(),
1084            #[cfg(feature = "profiler")]
1085            first_signal: std::sync::atomic::AtomicU64::new(u64::MAX),
1086        }
1087    }
1088
1089    /// Captures a platform request timestamp only when profiling is enabled.
1090    ///
1091    /// The closure is not invoked in profiler-disabled builds.
1092    #[inline]
1093    pub fn capture(capture: impl FnOnce() -> Instant) -> Option<Instant> {
1094        if cfg!(feature = "profiler") {
1095            Some(capture())
1096        } else {
1097            None
1098        }
1099    }
1100
1101    /// Records a platform frame request, retaining the first undrained timestamp.
1102    #[inline]
1103    #[cfg_attr(not(feature = "profiler"), expect(unused_variables))]
1104    pub fn record(&self, at: Instant, source: FrameRequestSource) {
1105        #[cfg(feature = "profiler")]
1106        {
1107            let nanoseconds = at
1108                .saturating_duration_since(self.origin)
1109                .as_nanos()
1110                .min(u128::from((u64::MAX >> 1) - 1)) as u64;
1111            let source_bit = match source {
1112                FrameRequestSource::NativeCallback => 0,
1113                FrameRequestSource::LocalSchedule => 1,
1114            };
1115            self.first_signal.fetch_min(
1116                (nanoseconds << 1) | source_bit,
1117                std::sync::atomic::Ordering::Relaxed,
1118            );
1119        }
1120    }
1121
1122    /// Drains the first platform frame request time and source, leaving the accumulator empty.
1123    #[inline]
1124    pub fn take(&self) -> Option<(Instant, FrameRequestSource)> {
1125        #[cfg(feature = "profiler")]
1126        {
1127            let signal = self
1128                .first_signal
1129                .swap(u64::MAX, std::sync::atomic::Ordering::Relaxed);
1130            (signal != u64::MAX).then(|| {
1131                let source = match signal & 1 {
1132                    0 => FrameRequestSource::NativeCallback,
1133                    _ => FrameRequestSource::LocalSchedule,
1134                };
1135                (self.origin + Duration::from_nanos(signal >> 1), source)
1136            })
1137        }
1138        #[cfg(not(feature = "profiler"))]
1139        {
1140            None
1141        }
1142    }
1143}
1144
1145/// The application's lifecycle phase, as owned and reported by a mobile OS.
1146///
1147/// `Inactive` means visible but not receiving input (a system dialog on
1148/// top), while `Background` means not visible at all, with process death
1149/// possible at any time thereafter.
1150///
1151/// | Phase        | iOS                          | Android      |
1152/// |--------------|------------------------------|--------------|
1153/// | `Active`     | `didBecomeActive`            | `onResume`   |
1154/// | `Inactive`   | `willResignActive`           | `onPause`    |
1155/// | `Background` | `didEnterBackground`         | `onStop`     |
1156/// | `Foreground` | `willEnterForeground`        | `onStart`    |
1157#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1158pub enum AppLifecyclePhase {
1159    /// Foreground and receiving input.
1160    Active,
1161    /// Foreground (visible) but not receiving input.
1162    Inactive,
1163    /// Not visible. The GPU surface may be destroyed while backgrounded and
1164    /// the process may be killed without further notice.
1165    Background,
1166    /// Becoming visible again, before input is restored.
1167    Foreground,
1168    /// The native scene has disconnected. Its windows may reconnect later.
1169    /// This is not a process-termination notification or a guaranteed final
1170    /// opportunity to save state.
1171    Disconnected,
1172}
1173
1174/// Regions of a window that are obscured or reserved by the system.
1175///
1176/// Mobile applications often share space in their window with system-specific
1177/// geometry, from keyboards to camera notches. In GPUI, all this is abstracted
1178/// into a single "inset" which should be overlaid on the window's bounds.
1179/// It is up to the application develop to determine how to handle these cases.
1180#[derive(Debug, Clone, Default, PartialEq)]
1181pub struct WindowInsets {
1182    /// Regions covered by system UI or hardware: status bar, display
1183    /// cutouts/notch, home indicator, navigation bars.
1184    /// (iOS: `safeAreaInsets`. Android: `WindowInsets` of types
1185    /// `systemBars() | displayCutout()`.)
1186    pub safe_area: Edges<Pixels>,
1187    /// The region covered by the keyboard, when present.
1188    /// (iOS: derived from `keyboardWillShow`/frame-change notifications.
1189    /// Android: `WindowInsets.Type.ime()`.)
1190    pub ime: Edges<Pixels>,
1191    /// The radii of the window's visible corners where the display rounds
1192    /// them, and zero where corners are square or the platform can't say.
1193    /// Content inset from an edge by `d` lines up with the display by using a
1194    /// radius of `radius - d`. (iOS 26+: a concentric `UICornerConfiguration`.)
1195    pub corner_radii: Corners<Pixels>,
1196}
1197
1198impl WindowInsets {
1199    /// The combined inset content should avoid.
1200    pub fn effective(&self) -> Edges<Pixels> {
1201        Edges {
1202            top: self.safe_area.top.max(self.ime.top),
1203            right: self.safe_area.right.max(self.ime.right),
1204            bottom: self.safe_area.bottom.max(self.ime.bottom),
1205            left: self.safe_area.left.max(self.ime.left),
1206        }
1207    }
1208}
1209
1210/// A change in the state of the focused text input.
1211#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1212pub enum TextInputStateChange {
1213    /// The window changed from having no active text input to having one.
1214    FocusGained,
1215    /// The window no longer has an active text input.
1216    FocusLost,
1217    /// The selection or caret moved
1218    SelectionChanged,
1219    /// The document content changed outside of platform-initiated edits.
1220    ContentChanged,
1221}
1222
1223#[expect(missing_docs)]
1224pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
1225    fn bounds(&self) -> Bounds<Pixels>;
1226    fn is_maximized(&self) -> bool;
1227    fn window_bounds(&self) -> WindowBounds;
1228    fn content_size(&self) -> Size<Pixels>;
1229    /// Returns the visible viewport in logical pixels relative to the content origin.
1230    ///
1231    /// This may be smaller or offset when a keyboard or zoom obscures content;
1232    /// it must not change the full layout size returned by `content_size`.
1233    /// Implementations should return a frame snapshot, not query platform layout here.
1234    fn visual_viewport_bounds(&self) -> Bounds<Pixels> {
1235        Bounds::new(Point::default(), self.content_size())
1236    }
1237    /// Registers a callback when visible geometry may have changed.
1238    ///
1239    /// This requests a frame; backends can sample the new viewport and safe-area
1240    /// geometry in `prepare_frame` rather than updating it inside the callback.
1241    fn on_visual_viewport_changed(&self, _callback: Box<dyn FnMut()>) {}
1242    /// Samples platform geometry before a draw, returning whether view caches must be invalidated.
1243    ///
1244    /// Geometry getters must remain consistent throughout the ensuing draw.
1245    /// Do not invoke callbacks here: GPUI is already updating this window.
1246    fn prepare_frame(&self) -> bool {
1247        false
1248    }
1249    fn resize(&mut self, size: Size<Pixels>);
1250    fn scale_factor(&self) -> f32;
1251    fn appearance(&self) -> WindowAppearance;
1252    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
1253    fn mouse_position(&self) -> Point<Pixels>;
1254    fn modifiers(&self) -> Modifiers;
1255    fn capslock(&self) -> Capslock;
1256    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
1257    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
1258    /// Apply the focused text region's [`TextInputConfiguration`] to the
1259    /// platform's text input session (e.g. attributes of the hidden editable
1260    /// element on web). Called only when the configuration changes, because
1261    /// reconfiguring a live input session can restart the IME connection.
1262    fn set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
1263    fn prompt(
1264        &self,
1265        level: PromptLevel,
1266        msg: &str,
1267        detail: Option<&str>,
1268        answers: &[PromptButton],
1269    ) -> Option<oneshot::Receiver<usize>>;
1270    fn activate(&self);
1271    /// Requests that the operating system draw attention to this window.
1272    fn request_attention(&self) {}
1273    fn is_active(&self) -> bool;
1274    /// The current [`WindowVisibility`]. Read once when the window is created;
1275    /// afterwards changes arrive through [`Self::on_visibility_change`].
1276    fn visibility(&self) -> WindowVisibility;
1277    fn is_hovered(&self) -> bool;
1278    fn background_appearance(&self) -> WindowBackgroundAppearance;
1279    fn set_title(&mut self, title: &str);
1280    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
1281    fn minimize(&self);
1282    fn zoom(&self);
1283    fn toggle_fullscreen(&self);
1284    fn is_fullscreen(&self) -> bool;
1285    fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
1286        None
1287    }
1288    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
1289    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
1290    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
1291    /// Registers the callback invoked when [`Self::visibility`] changes. Only
1292    /// transitions are reported; the callback runs on the main thread outside
1293    /// of any window update.
1294    fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
1295    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
1296    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
1297    fn on_moved(&self, callback: Box<dyn FnMut()>);
1298    /// Registers the callback invoked when the window moves to another
1299    /// display. Calls may be spurious. The callback runs on the main thread
1300    /// outside of any window update.
1301    fn on_display_changed(&self, callback: Box<dyn FnMut()>);
1302    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
1303    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
1304    fn on_close(&self, callback: Box<dyn FnOnce()>);
1305    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
1306    fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
1307    fn draw(&self, scene: &Scene);
1308    fn schedule_frame(&self) {}
1309    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1310    fn is_subpixel_rendering_supported(&self) -> bool;
1311
1312    // macOS specific methods
1313    fn get_title(&self) -> String {
1314        String::new()
1315    }
1316    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1317        None
1318    }
1319    fn tab_bar_visible(&self) -> bool {
1320        false
1321    }
1322    fn set_edited(&mut self, _edited: bool) {}
1323    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
1324    fn toggle_simple_fullscreen(&self) {}
1325    fn is_simple_fullscreen(&self) -> bool {
1326        false
1327    }
1328    #[cfg(target_os = "macos")]
1329    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
1330    fn show_character_palette(&self) {}
1331    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
1332    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
1333    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
1334    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
1335    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
1336    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
1337    fn merge_all_windows(&self) {}
1338    fn move_tab_to_new_window(&self) {}
1339    fn toggle_window_tab_overview(&self) {}
1340    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
1341
1342    fn native_window_state(&self) -> Option<Vec<u8>> {
1343        None
1344    }
1345    fn restore_native_window_state(&self, _state: &[u8]) {}
1346
1347    #[cfg(target_os = "windows")]
1348    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1349
1350    // Linux specific methods
1351    fn inner_window_bounds(&self) -> WindowBounds {
1352        self.window_bounds()
1353    }
1354    fn request_decorations(&self, _decorations: WindowDecorations) {}
1355    fn show_window_menu(&self, _position: Point<Pixels>) {}
1356    fn start_window_move(&self) {}
1357    fn can_start_external_drag(&self) -> bool {
1358        false
1359    }
1360    fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1361        false
1362    }
1363    fn start_window_resize(&self, _edge: ResizeEdge) {}
1364    fn set_exclusive_zone(&self, _zone: Pixels) {}
1365    #[cfg(all(target_os = "linux", feature = "wayland"))]
1366    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1367    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1368    fn window_decorations(&self) -> Decorations {
1369        Decorations::Server
1370    }
1371    fn set_app_id(&mut self, _app_id: &str) {}
1372    fn map_window(&mut self) -> anyhow::Result<()> {
1373        Ok(())
1374    }
1375    fn window_controls(&self) -> WindowControls {
1376        WindowControls::default()
1377    }
1378    fn set_client_inset(&self, _inset: Pixels) {}
1379    fn gpu_specs(&self) -> Option<GpuSpecs>;
1380
1381    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1382
1383    // Mobile platform methods.
1384
1385    /// The regions of this window currently obscured or reserved by the
1386    /// system. Zero on platforms without such regions.
1387    fn insets(&self) -> WindowInsets {
1388        WindowInsets::default()
1389    }
1390
1391    /// Registers a callback invoked whenever [`Self::insets`] change.
1392    ///
1393    /// Contract: fires continuously during animated transitions (Android
1394    /// `WindowInsetsAnimation` progress; on iOS the platform interpolates
1395    /// the keyboard animation curve on frame ticks) and is exact at rest.
1396    fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1397
1398    /// Sets the handler for the system back action (Android back
1399    /// button/gesture; no source on iOS or desktop).
1400    fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1401
1402    /// Declares whether the application would currently handle the system
1403    /// back action (e.g. navigation depth > 0).
1404    fn set_back_enabled(&self, _enabled: bool) {}
1405
1406    /// Requests that the soft keyboard be shown.
1407    fn show_soft_keyboard(&self) {}
1408
1409    /// Requests that the soft keyboard be hidden.
1410    fn hide_soft_keyboard(&self) {}
1411
1412    /// Inform the operating system that the text input state has changed
1413    fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1414
1415    fn play_system_bell(&self) {}
1416
1417    /// Initialize the accessibility adapter with callbacks.
1418    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1419
1420    /// Provide a TreeUpdate to the accessibility adapter.
1421    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1422
1423    /// Inform the adapter of updated window bounds.
1424    fn a11y_update_window_bounds(&self) {}
1425
1426    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1427    fn as_test(&mut self) -> Option<&mut TestWindow> {
1428        None
1429    }
1430
1431    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
1432    /// This does not present the frame to screen - useful for visual testing where we want
1433    /// to capture what would be rendered without displaying it or requiring the window to be visible.
1434    #[cfg(any(test, feature = "test-support"))]
1435    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1436        anyhow::bail!("render_to_image not implemented for this platform")
1437    }
1438}
1439
1440/// A renderer for headless windows that can produce real rendered output.
1441#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1442pub trait PlatformHeadlessRenderer {
1443    /// Render a scene and return the result as an RGBA image.
1444    fn render_scene_to_image(
1445        &mut self,
1446        scene: &Scene,
1447        size: Size<DevicePixels>,
1448    ) -> Result<RgbaImage>;
1449
1450    /// Render a scene to an offscreen target without reading the result back.
1451    ///
1452    /// This is the headless analogue of presenting a frame: it performs the
1453    /// same CPU-side scene encoding and GPU submission as drawing to a real
1454    /// window, but doesn't block on GPU completion or copy pixels back.
1455    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1456
1457    /// Returns the sprite atlas used by this renderer.
1458    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1459}
1460
1461/// Type alias for runnables with metadata.
1462/// Previously an enum with a single variant, now simplified to a direct type alias.
1463#[doc(hidden)]
1464pub type RunnableVariant = Runnable<RunnableMeta>;
1465
1466#[doc(hidden)]
1467pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1468
1469#[doc(hidden)]
1470pub enum TasksIncluded {
1471    OnlyCompleted,
1472    CompletedAndRunning,
1473}
1474
1475/// This type is public so that our test macro can generate and use it, but it should not
1476/// be considered part of our public API.
1477#[doc(hidden)]
1478pub trait PlatformDispatcher: Send + Sync {
1479    fn is_main_thread(&self) -> bool;
1480    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1481    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1482    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1483
1484    fn dispatch_on_main_thread_when_idle(
1485        &self,
1486        runnable: RunnableVariant,
1487        timeout: Option<Duration>,
1488    ) {
1489        let _ = timeout;
1490        self.dispatch_on_main_thread(runnable, Priority::Low);
1491    }
1492
1493    fn idle_time_remaining(&self) -> Option<Duration> {
1494        None
1495    }
1496
1497    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1498
1499    fn now(&self) -> Instant {
1500        Instant::now()
1501    }
1502
1503    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1504        gpui_util::defer(Box::new(|| {}))
1505    }
1506
1507    fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1508        ActivityGuard::noop()
1509    }
1510
1511    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1512    fn as_test(&self) -> Option<&TestDispatcher> {
1513        None
1514    }
1515
1516    // This cfg must match the `threaded_dispatcher` module's, which implements
1517    // this method whenever it compiles.
1518    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1519    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1520        None
1521    }
1522}
1523
1524#[expect(missing_docs)]
1525pub trait PlatformTextSystem: Send + Sync {
1526    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1527    /// Installs a nonblocking sink for unresolved grapheme clusters.
1528    fn set_missing_glyph_sink(&self, _sink: Option<Arc<dyn MissingGlyphSink>>) {}
1529    /// Get all available font names.
1530    fn all_font_names(&self) -> Vec<String>;
1531    /// Get the font ID for a font descriptor.
1532    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1533    /// Prewarm any system font caches needed to shape text.
1534    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1535    /// Get metrics for a font.
1536    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1537    /// Get typographic bounds for a glyph.
1538    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1539    /// Get the advance width for a glyph.
1540    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1541    /// Get the glyph ID for a character.
1542    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1543    /// Get raster bounds for a glyph.
1544    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1545    /// Rasterize a glyph.
1546    fn rasterize_glyph(
1547        &self,
1548        params: &RenderGlyphParams,
1549        raster_bounds: Bounds<DevicePixels>,
1550    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1551    /// Layout a line of text with the given font runs.
1552    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1553    /// Returns the recommended text rendering mode for the given font and size.
1554    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1555    -> TextRenderingMode;
1556    /// Returns the dilation level to use for a glyph painted in the given color.
1557    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1558        0
1559    }
1560}
1561
1562#[expect(missing_docs)]
1563pub struct NoopTextSystem;
1564
1565#[expect(missing_docs)]
1566impl NoopTextSystem {
1567    #[allow(dead_code)]
1568    pub fn new() -> Self {
1569        Self
1570    }
1571}
1572
1573impl PlatformTextSystem for NoopTextSystem {
1574    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1575        Ok(())
1576    }
1577
1578    fn all_font_names(&self) -> Vec<String> {
1579        Vec::new()
1580    }
1581
1582    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1583        Ok(FontId(1))
1584    }
1585
1586    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1587        FontMetrics {
1588            units_per_em: 1000,
1589            ascent: 1025.0,
1590            descent: -275.0,
1591            line_gap: 0.0,
1592            underline_position: -95.0,
1593            underline_thickness: 60.0,
1594            cap_height: 698.0,
1595            x_height: 516.0,
1596            bounding_box: Bounds {
1597                origin: Point {
1598                    x: -260.0,
1599                    y: -245.0,
1600                },
1601                size: Size {
1602                    width: 1501.0,
1603                    height: 1364.0,
1604                },
1605            },
1606        }
1607    }
1608
1609    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1610        Ok(Bounds {
1611            origin: Point { x: 54.0, y: 0.0 },
1612            size: size(392.0, 528.0),
1613        })
1614    }
1615
1616    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1617        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1618    }
1619
1620    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1621        Some(GlyphId(ch.len_utf16() as u32))
1622    }
1623
1624    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1625        Ok(Default::default())
1626    }
1627
1628    fn rasterize_glyph(
1629        &self,
1630        _params: &RenderGlyphParams,
1631        raster_bounds: Bounds<DevicePixels>,
1632    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1633        Ok((raster_bounds.size, Vec::new()))
1634    }
1635
1636    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1637        let mut position = px(0.);
1638        let metrics = self.font_metrics(FontId(0));
1639        let em_width = font_size
1640            * self
1641                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1642                .unwrap()
1643                .width
1644            / metrics.units_per_em as f32;
1645        let mut glyphs = Vec::new();
1646        for (ix, c) in text.char_indices() {
1647            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1648                glyphs.push(ShapedGlyph {
1649                    id: glyph,
1650                    position: point(position, px(0.)),
1651                    index: ix,
1652                    is_emoji: glyph.0 == 2,
1653                });
1654                if glyph.0 == 2 {
1655                    position += em_width * 2.0;
1656                } else {
1657                    position += em_width;
1658                }
1659            } else {
1660                position += em_width
1661            }
1662        }
1663        let mut runs = Vec::default();
1664        if !glyphs.is_empty() {
1665            runs.push(ShapedRun {
1666                font_id: FontId(0),
1667                glyphs,
1668            });
1669        } else {
1670            position = px(0.);
1671        }
1672
1673        LineLayout {
1674            font_size,
1675            width: position,
1676            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1677            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1678            runs,
1679            len: text.len(),
1680        }
1681    }
1682
1683    fn recommended_rendering_mode(
1684        &self,
1685        _font_id: FontId,
1686        _font_size: Pixels,
1687    ) -> TextRenderingMode {
1688        TextRenderingMode::Grayscale
1689    }
1690}
1691
1692// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1693// Copyright (c) Microsoft Corporation.
1694// Licensed under the MIT license.
1695/// Compute gamma correction ratios for subpixel text rendering.
1696#[allow(dead_code)]
1697pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1698    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1699        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1700        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1701        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1702        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1703        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1704        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1705        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1706        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1707        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1708        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1709        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1710        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1711        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1712    ];
1713
1714    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1715    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1716
1717    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1718    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1719
1720    [
1721        ratios[0] * NORM13,
1722        ratios[1] * NORM24,
1723        ratios[2] * NORM13,
1724        ratios[3] * NORM24,
1725    ]
1726}
1727
1728#[derive(PartialEq, Eq, Hash, Clone)]
1729#[expect(missing_docs)]
1730pub enum AtlasKey {
1731    Glyph(RenderGlyphParams),
1732    Svg(RenderSvgParams),
1733    Image(RenderImageParams),
1734}
1735
1736impl AtlasKey {
1737    /// Returns the texture kind for this atlas key.
1738    pub fn texture_kind(&self) -> AtlasTextureKind {
1739        match self {
1740            AtlasKey::Glyph(params) => {
1741                if params.is_emoji {
1742                    AtlasTextureKind::Polychrome
1743                } else if params.subpixel_rendering {
1744                    AtlasTextureKind::Subpixel
1745                } else {
1746                    AtlasTextureKind::Monochrome
1747                }
1748            }
1749            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1750            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1751        }
1752    }
1753}
1754
1755impl From<RenderGlyphParams> for AtlasKey {
1756    fn from(params: RenderGlyphParams) -> Self {
1757        Self::Glyph(params)
1758    }
1759}
1760
1761impl From<RenderSvgParams> for AtlasKey {
1762    fn from(params: RenderSvgParams) -> Self {
1763        Self::Svg(params)
1764    }
1765}
1766
1767impl From<RenderImageParams> for AtlasKey {
1768    fn from(params: RenderImageParams) -> Self {
1769        Self::Image(params)
1770    }
1771}
1772
1773#[expect(missing_docs)]
1774pub trait PlatformAtlas {
1775    /// The builder runs with the atlas locked and must not re-enter the same atlas.
1776    fn get_or_insert_with<'a>(
1777        &self,
1778        key: AtlasKey,
1779        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1780    ) -> Result<Option<AtlasTile>>;
1781    fn remove(&self, key: &AtlasKey);
1782
1783    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1784    fn contains(&self, _key: &AtlasKey) -> bool {
1785        false
1786    }
1787}
1788
1789#[doc(hidden)]
1790pub trait AtlasBackend {
1791    fn insert(
1792        &mut self,
1793        kind: AtlasTextureKind,
1794        size: Size<DevicePixels>,
1795        bytes: &[u8],
1796    ) -> Result<AtlasTile>;
1797
1798    fn remove(&mut self, tile: AtlasTile);
1799}
1800
1801#[doc(hidden)]
1802pub struct AtlasState<Backend> {
1803    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
1804    pub backend: Backend,
1805}
1806
1807impl<Backend> AtlasState<Backend> {
1808    pub fn new(backend: Backend) -> Self {
1809        Self {
1810            tiles_by_key: FxHashMap::default(),
1811            backend,
1812        }
1813    }
1814
1815    pub fn contains(&self, key: &AtlasKey) -> bool {
1816        self.tiles_by_key.contains_key(key)
1817    }
1818
1819    pub fn clear(&mut self, reset_backend: impl FnOnce(&mut Backend)) {
1820        self.tiles_by_key.clear();
1821        reset_backend(&mut self.backend);
1822    }
1823}
1824
1825impl<Backend: Default> Default for AtlasState<Backend> {
1826    fn default() -> Self {
1827        Self::new(Backend::default())
1828    }
1829}
1830
1831impl<Backend: AtlasBackend> AtlasState<Backend> {
1832    pub fn get_or_insert_with<'a>(
1833        &mut self,
1834        key: AtlasKey,
1835        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1836    ) -> Result<Option<AtlasTile>> {
1837        match self.tiles_by_key.entry(key) {
1838            Entry::Occupied(entry) => Ok(Some(*entry.get())),
1839            Entry::Vacant(entry) => {
1840                profiling::scope!("new tile");
1841                let Some((size, bytes)) = build()? else {
1842                    return Ok(None);
1843                };
1844                let tile = self
1845                    .backend
1846                    .insert(entry.key().texture_kind(), size, &bytes)?;
1847                entry.insert(tile);
1848                Ok(Some(tile))
1849            }
1850        }
1851    }
1852
1853    pub fn remove(&mut self, key: &AtlasKey) {
1854        if let Some(tile) = self.tiles_by_key.remove(key) {
1855            self.backend.remove(tile);
1856        }
1857    }
1858}
1859
1860/// A sprite atlas for windows without a GPU. It hands out uniquely identified
1861/// tiles without uploading any pixels, so glyph, SVG, and image painting can
1862/// run to completion in tests and headless platforms.
1863#[derive(Default)]
1864pub struct HeadlessAtlas(parking_lot::Mutex<AtlasState<HeadlessAtlasBackend>>);
1865
1866#[doc(hidden)]
1867#[derive(Default)]
1868pub struct HeadlessAtlasBackend {
1869    next_id: u32,
1870}
1871
1872impl AtlasBackend for HeadlessAtlasBackend {
1873    fn insert(
1874        &mut self,
1875        kind: AtlasTextureKind,
1876        size: Size<DevicePixels>,
1877        _bytes: &[u8],
1878    ) -> Result<AtlasTile> {
1879        self.next_id += 1;
1880        let texture_id = self.next_id;
1881        self.next_id += 1;
1882        let tile_id = self.next_id;
1883        Ok(AtlasTile {
1884            texture_id: AtlasTextureId {
1885                index: texture_id,
1886                kind,
1887            },
1888            tile_id: TileId(tile_id),
1889            padding: 0,
1890            bounds: Bounds {
1891                origin: Point::default(),
1892                size,
1893            },
1894        })
1895    }
1896
1897    fn remove(&mut self, _tile: AtlasTile) {}
1898}
1899
1900impl PlatformAtlas for HeadlessAtlas {
1901    fn get_or_insert_with<'a>(
1902        &self,
1903        key: AtlasKey,
1904        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1905    ) -> Result<Option<AtlasTile>> {
1906        self.0.lock().get_or_insert_with(key, build)
1907    }
1908
1909    fn remove(&self, key: &AtlasKey) {
1910        self.0.lock().remove(key);
1911    }
1912
1913    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1914    fn contains(&self, key: &AtlasKey) -> bool {
1915        self.0.lock().contains(key)
1916    }
1917}
1918
1919#[doc(hidden)]
1920pub struct AtlasTextureList<T> {
1921    pub textures: Vec<Option<T>>,
1922    pub free_list: Vec<usize>,
1923}
1924
1925impl<T> Default for AtlasTextureList<T> {
1926    fn default() -> Self {
1927        Self {
1928            textures: Vec::default(),
1929            free_list: Vec::default(),
1930        }
1931    }
1932}
1933
1934impl<T> ops::Index<usize> for AtlasTextureList<T> {
1935    type Output = Option<T>;
1936
1937    fn index(&self, index: usize) -> &Self::Output {
1938        &self.textures[index]
1939    }
1940}
1941
1942impl<T> AtlasTextureList<T> {
1943    #[allow(unused)]
1944    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1945        self.free_list.clear();
1946        self.textures.drain(..)
1947    }
1948
1949    #[allow(dead_code)]
1950    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1951        self.textures.iter_mut().flatten()
1952    }
1953}
1954
1955#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1956#[repr(C)]
1957#[expect(missing_docs)]
1958pub struct AtlasTile {
1959    /// The texture this tile belongs to.
1960    pub texture_id: AtlasTextureId,
1961    /// The unique ID of this tile within its texture.
1962    pub tile_id: TileId,
1963    /// Padding around the tile content in pixels.
1964    pub padding: u32,
1965    /// The bounds of this tile within the texture.
1966    pub bounds: Bounds<DevicePixels>,
1967}
1968
1969#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1970#[repr(C)]
1971#[expect(missing_docs)]
1972pub struct AtlasTextureId {
1973    // We use u32 instead of usize for Metal Shader Language compatibility
1974    /// The index of this texture in the atlas.
1975    pub index: u32,
1976    /// The kind of content stored in this texture.
1977    pub kind: AtlasTextureKind,
1978}
1979
1980#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1981#[repr(C)]
1982#[cfg_attr(
1983    all(
1984        any(target_os = "linux", target_os = "freebsd"),
1985        not(any(feature = "x11", feature = "wayland"))
1986    ),
1987    allow(dead_code)
1988)]
1989#[expect(missing_docs)]
1990pub enum AtlasTextureKind {
1991    Monochrome = 0,
1992    Polychrome = 1,
1993    Subpixel = 2,
1994}
1995
1996#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1997#[repr(C)]
1998#[expect(missing_docs)]
1999pub struct TileId(pub u32);
2000
2001impl From<etagere::AllocId> for TileId {
2002    fn from(id: etagere::AllocId) -> Self {
2003        Self(id.serialize())
2004    }
2005}
2006
2007impl From<TileId> for etagere::AllocId {
2008    fn from(id: TileId) -> Self {
2009        Self::deserialize(id.0)
2010    }
2011}
2012
2013#[expect(missing_docs)]
2014pub struct PlatformInputHandler {
2015    cx: AsyncWindowContext,
2016    handler: Box<dyn InputHandler>,
2017    focus_id: Option<crate::FocusId>,
2018}
2019
2020#[expect(missing_docs)]
2021#[cfg_attr(
2022    all(
2023        any(target_os = "linux", target_os = "freebsd"),
2024        not(any(feature = "x11", feature = "wayland"))
2025    ),
2026    allow(dead_code)
2027)]
2028impl PlatformInputHandler {
2029    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
2030        Self {
2031            cx,
2032            handler,
2033            focus_id: None,
2034        }
2035    }
2036
2037    pub(crate) fn with_focus(mut self, focus_id: crate::FocusId) -> Self {
2038        self.focus_id = Some(focus_id);
2039        self
2040    }
2041
2042    /// Identifies the input field registered through [`Window::handle_input`].
2043    ///
2044    /// This identity survives adapter replacement during repaint. Backends can
2045    /// use it to detect a field change without restarting the keyboard session.
2046    pub fn focus_id(&self) -> Option<crate::FocusId> {
2047        self.focus_id
2048    }
2049
2050    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
2051        self.cx
2052            .update(|window, cx| {
2053                self.handler
2054                    .selected_text_range(ignore_disabled_input, window, cx)
2055            })
2056            .ok()
2057            .flatten()
2058    }
2059
2060    #[cfg_attr(target_os = "windows", allow(dead_code))]
2061    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
2062        self.cx
2063            .update(|window, cx| self.handler.marked_text_range(window, cx))
2064            .ok()
2065            .flatten()
2066    }
2067
2068    #[cfg_attr(
2069        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2070        allow(dead_code)
2071    )]
2072    pub fn text_for_range(
2073        &mut self,
2074        range_utf16: Range<usize>,
2075        adjusted: &mut Option<Range<usize>>,
2076    ) -> Option<String> {
2077        self.cx
2078            .update(|window, cx| {
2079                self.handler
2080                    .text_for_range(range_utf16, adjusted, window, cx)
2081            })
2082            .ok()
2083            .flatten()
2084    }
2085
2086    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
2087        self.cx
2088            .update(|window, cx| {
2089                self.handler
2090                    .replace_text_in_range(replacement_range, text, window, cx);
2091            })
2092            .ok();
2093    }
2094
2095    pub fn replace_and_mark_text_in_range(
2096        &mut self,
2097        range_utf16: Option<Range<usize>>,
2098        new_text: &str,
2099        new_selected_range: Option<Range<usize>>,
2100    ) {
2101        self.cx
2102            .update(|window, cx| {
2103                self.handler.replace_and_mark_text_in_range(
2104                    range_utf16,
2105                    new_text,
2106                    new_selected_range,
2107                    window,
2108                    cx,
2109                )
2110            })
2111            .ok();
2112    }
2113
2114    #[cfg_attr(target_os = "windows", allow(dead_code))]
2115    pub fn unmark_text(&mut self) {
2116        self.cx
2117            .update(|window, cx| self.handler.unmark_text(window, cx))
2118            .ok();
2119    }
2120
2121    pub fn paste(&mut self, item: ClipboardItem) {
2122        self.cx
2123            .update(|window, cx| self.handler.paste(item, window, cx))
2124            .ok();
2125    }
2126
2127    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
2128        self.cx
2129            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
2130            .ok()
2131            .flatten()
2132    }
2133
2134    pub fn selection_bounds_for_range(&mut self, range_utf16: Range<usize>) -> Vec<Bounds<Pixels>> {
2135        self.cx
2136            .update(|window, cx| {
2137                self.handler
2138                    .selection_bounds_for_range(range_utf16, window, cx)
2139            })
2140            .unwrap_or_default()
2141    }
2142
2143    #[allow(dead_code)]
2144    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
2145        self.handler.apple_press_and_hold_enabled()
2146    }
2147
2148    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
2149        self.handler.replace_text_in_range(None, input, window, cx);
2150    }
2151
2152    pub fn compute_ime_candidate_bounds(
2153        marked_range: Option<Range<usize>>,
2154        selection: &UTF16Selection,
2155        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
2156    ) -> Option<Bounds<Pixels>> {
2157        if let Some(marked_range) = marked_range {
2158            // Default to the start of the marked (composing) range.
2159            let mut line_start = marked_range.start;
2160
2161            // Walk backward from the caret looking for a line break. A change in
2162            // the Y coordinate means we crossed into the previous visual line, so
2163            // the line start is one position after the break point.
2164            let caret = selection.range.end;
2165            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
2166                for i in (marked_range.start..caret).rev() {
2167                    if let Some(b) = bounds_for_range(i..i) {
2168                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
2169                            line_start = i + 1;
2170                            break;
2171                        }
2172                    }
2173                }
2174            }
2175            bounds_for_range(line_start..line_start)
2176        } else {
2177            // No active composition — use the selection endpoint.
2178            let offset = if selection.reversed {
2179                selection.range.start
2180            } else {
2181                selection.range.end
2182            };
2183            bounds_for_range(offset..offset)
2184        }
2185    }
2186
2187    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
2188        let marked_range = self.handler.marked_text_range(window, cx);
2189        let selection = self.handler.selected_text_range(true, window, cx)?;
2190        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2191            self.handler.bounds_for_range(range, window, cx)
2192        })
2193    }
2194
2195    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
2196        let marked_range = self.marked_text_range();
2197        let selection = self.selected_text_range(true)?;
2198        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2199            self.bounds_for_range(range)
2200        })
2201    }
2202
2203    #[allow(unused)]
2204    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
2205        self.cx
2206            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
2207            .ok()
2208            .flatten()
2209    }
2210
2211    /// See [`InputHandler::set_selected_text_range`].
2212    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
2213        self.cx
2214            .update(|window, cx| {
2215                self.handler
2216                    .set_selected_text_range(range_utf16, window, cx)
2217            })
2218            .ok();
2219    }
2220
2221    /// See [`InputHandler::element_bounds`].
2222    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
2223        self.cx
2224            .update(|window, cx| self.handler.element_bounds(window, cx))
2225            .ok()
2226            .flatten()
2227    }
2228
2229    /// See [`InputHandler::text_length_utf16`].
2230    pub fn text_length_utf16(&mut self) -> Option<usize> {
2231        self.cx
2232            .update(|window, cx| self.handler.text_length_utf16(window, cx))
2233            .ok()
2234            .flatten()
2235    }
2236
2237    #[allow(dead_code)]
2238    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
2239        self.handler.accepts_text_input(window, cx)
2240    }
2241
2242    #[allow(dead_code)]
2243    pub fn query_accepts_text_input(&mut self) -> bool {
2244        self.cx
2245            .update(|window, cx| self.handler.accepts_text_input(window, cx))
2246            .unwrap_or(true)
2247    }
2248
2249    /// Checks eligibility against the current focus, including before the next frame is drawn.
2250    ///
2251    /// Unlike the legacy acceptance query, this fails closed when the window cannot
2252    /// be updated and requires registration through `Window::handle_input`.
2253    pub fn query_accepts_focused_text_input(&mut self) -> bool {
2254        let focus_id = self.focus_id;
2255        self.cx
2256            .update(|window, cx| {
2257                focus_id.is_some()
2258                    && window.focus == focus_id
2259                    && self.handler.accepts_text_input(window, cx)
2260            })
2261            .unwrap_or(false)
2262    }
2263
2264    /// See [`InputHandler::prefers_ime_for_printable_keys`].
2265    ///
2266    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
2267    /// returns `false` regardless of the handler's preference, because the next printable key may
2268    /// complete a binding whose prefix already bypassed the IME.
2269    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
2270        self.cx
2271            .update(|window, cx| {
2272                // The next printable key may complete a chord whose prefix bypassed the IME.
2273                !window.has_pending_keystrokes()
2274                    && self.handler.prefers_ime_for_printable_keys(window, cx)
2275            })
2276            .unwrap_or(false)
2277    }
2278
2279    /// See [`InputHandler::text_input_configuration`].
2280    pub fn text_input_configuration(
2281        &mut self,
2282        window: &mut Window,
2283        cx: &mut App,
2284    ) -> TextInputConfiguration {
2285        self.handler.text_input_configuration(window, cx)
2286    }
2287
2288    /// See [`InputHandler::text_input_editable_range`].
2289    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
2290        self.cx
2291            .update(|window, cx| self.handler.text_input_editable_range(window, cx))
2292            .ok()
2293            .flatten()
2294    }
2295}
2296
2297/// A struct representing a selection in a text buffer, in UTF16 characters.
2298/// This is different from a range because the head may be before the tail.
2299#[derive(Debug)]
2300pub struct UTF16Selection {
2301    /// The range of text in the document this selection corresponds to
2302    /// in UTF16 characters.
2303    pub range: Range<usize>,
2304    /// Whether the head of this selection is at the start (true), or end (false)
2305    /// of the range
2306    pub reversed: bool,
2307}
2308
2309/// GPUI's interface for native text input, IME composition, and selection.
2310/// Platform backends translate their native text protocols into these operations.
2311///
2312/// Text offsets and ranges use UTF-16 code units unless stated otherwise.
2313pub trait InputHandler: 'static {
2314    /// Get the range of the user's currently selected text, if any
2315    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
2316    ///
2317    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
2318    fn selected_text_range(
2319        &mut self,
2320        ignore_disabled_input: bool,
2321        window: &mut Window,
2322        cx: &mut App,
2323    ) -> Option<UTF16Selection>;
2324
2325    /// Get the range of the currently marked text, if any
2326    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
2327    ///
2328    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
2329    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
2330
2331    /// Get the text for the given document range in UTF-16 characters
2332    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
2333    ///
2334    /// range_utf16 is in terms of UTF-16 characters
2335    fn text_for_range(
2336        &mut self,
2337        range_utf16: Range<usize>,
2338        adjusted_range: &mut Option<Range<usize>>,
2339        window: &mut Window,
2340        cx: &mut App,
2341    ) -> Option<String>;
2342
2343    /// Replace the text in the given document range with the given text
2344    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
2345    ///
2346    /// replacement_range is in terms of UTF-16 characters
2347    fn replace_text_in_range(
2348        &mut self,
2349        replacement_range: Option<Range<usize>>,
2350        text: &str,
2351        window: &mut Window,
2352        cx: &mut App,
2353    );
2354
2355    /// Replace the text in the given document range with the given text,
2356    /// and mark the given text as part of an IME 'composing' state
2357    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
2358    ///
2359    /// range_utf16 is in terms of UTF-16 characters
2360    /// new_selected_range is in terms of UTF-16 characters
2361    fn replace_and_mark_text_in_range(
2362        &mut self,
2363        range_utf16: Option<Range<usize>>,
2364        new_text: &str,
2365        new_selected_range: Option<Range<usize>>,
2366        window: &mut Window,
2367        cx: &mut App,
2368    );
2369
2370    /// Remove the IME 'composing' state from the document
2371    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
2372    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
2373
2374    /// Insert a platform-initiated paste at the current selection.
2375    ///
2376    /// Platforms that deliver paste as an input event rather than through an
2377    /// application-defined action (e.g. the DOM `paste` event on web) call
2378    /// this with the full clipboard contents. The default implementation
2379    /// inserts only the plain-text portion of the item.
2380    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
2381        if let Some(text) = item.text() {
2382            self.replace_text_in_range(None, &text, window, cx);
2383        }
2384    }
2385
2386    /// Get the bounds of the given document range in window coordinates.
2387    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
2388    ///
2389    /// This is used for positioning the IME candidate window
2390    fn bounds_for_range(
2391        &mut self,
2392        range_utf16: Range<usize>,
2393        window: &mut Window,
2394        cx: &mut App,
2395    ) -> Option<Bounds<Pixels>>;
2396
2397    /// Returns selection rectangles in document order, in window coordinates.
2398    ///
2399    /// Return line or shaped-run fragments, not one rectangle per character. The
2400    /// first and last rectangles contain the range's start and end respectively.
2401    /// Empty means native selection geometry is unavailable; caret/IME geometry can
2402    /// still be supplied by `bounds_for_range`.
2403    fn selection_bounds_for_range(
2404        &mut self,
2405        _range_utf16: Range<usize>,
2406        _window: &mut Window,
2407        _cx: &mut App,
2408    ) -> Vec<Bounds<Pixels>> {
2409        Vec::new()
2410    }
2411
2412    /// Get the character offset for the given point in terms of UTF16 characters
2413    ///
2414    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
2415    fn character_index_for_point(
2416        &mut self,
2417        point: Point<Pixels>,
2418        window: &mut Window,
2419        cx: &mut App,
2420    ) -> Option<usize>;
2421
2422    /// Set the range of the user's currently selected text.
2423    ///
2424    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
2425    /// platforms call it when the system text machinery moves the selection on the
2426    /// application's behalf — e.g. the user drags a system selection handle or
2427    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
2428    /// Android `InputConnection.setSelection`).
2429    ///
2430    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
2431    fn set_selected_text_range(
2432        &mut self,
2433        _range_utf16: Range<usize>,
2434        _window: &mut Window,
2435        _cx: &mut App,
2436    ) {
2437    }
2438
2439    /// Get the bounds of the focused text element in window coordinates, if known.
2440    ///
2441    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
2442    /// push: mobile platforms ask for the focused element's geometry when they
2443    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
2444    /// element).
2445    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2446        None
2447    }
2448
2449    /// Get the length of the document in UTF-16 characters, if known.
2450    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2451        None
2452    }
2453
2454    /// Allows a given input context to opt into getting raw key repeats instead of
2455    /// sending these to the platform.
2456    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
2457    /// (which is how iTerm does it) but it doesn't seem to work for me.
2458    #[allow(dead_code)]
2459    fn apple_press_and_hold_enabled(&mut self) -> bool {
2460        true
2461    }
2462
2463    /// Returns whether this handler is accepting text input to be inserted.
2464    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2465        true
2466    }
2467
2468    /// The contiguous range of text, in UTF-16 code units, that platform text
2469    /// input may read and edit around the current selection.
2470    ///
2471    /// Platforms that mirror document text into an IME-editable buffer clamp
2472    /// the mirrored window to this range, so multi-step IME edit gestures
2473    /// (word deletion, autocorrect rewrites, suggestion picks) cannot reach
2474    /// content outside it. The range should contain the current selection;
2475    /// when it cannot (a selection spanning a region boundary), platforms
2476    /// degrade the mirrored IME context rather than widening the range.
2477    /// `None` places no bound.
2478    fn text_input_editable_range(
2479        &mut self,
2480        _window: &mut Window,
2481        _cx: &mut App,
2482    ) -> Option<Range<usize>> {
2483        None
2484    }
2485
2486    /// Returns whether printable keys should be routed to the IME before keybinding
2487    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
2488    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
2489    /// keys that the IME should compose.
2490    ///
2491    /// Defaults to `false`. The editor overrides this based on whether it expects
2492    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
2493    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
2494    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2495        false
2496    }
2497
2498    /// Get this handler's preferences for platform text assistance.
2499    ///
2500    /// GPUI re-queries this every frame and forwards it to the platform window
2501    /// only when it changes, so implementations must be cheap and may vary the
2502    /// result with application state (e.g. with the cursor's position).
2503    fn text_input_configuration(
2504        &mut self,
2505        _window: &mut Window,
2506        _cx: &mut App,
2507    ) -> TextInputConfiguration {
2508        TextInputConfiguration::default()
2509    }
2510}
2511
2512/// Platform text-assistance preferences for the focused text region.
2513///
2514/// Returned by [`InputHandler::text_input_configuration`] and forwarded to the
2515/// platform whenever it changes; the platform maps the fields onto its native
2516/// input-session attributes (on web, DOM attributes of the hidden editable
2517/// element such as `autocorrect` and `enterkeyhint`).
2518///
2519/// The default disables all text assistance and requests no particular action
2520/// key presentation.
2521#[derive(Clone, Debug, Default, PartialEq, Eq)]
2522pub struct TextInputConfiguration {
2523    /// Whether the platform may automatically correct entered text.
2524    pub autocorrect: bool,
2525    /// How software keyboards automatically capitalize entered text.
2526    pub autocapitalize: Autocapitalize,
2527    /// Whether software keyboards may offer word suggestions and spellcheck.
2528    pub suggestions: bool,
2529    /// The action advertised on a software keyboard's confirm ("enter") key.
2530    pub input_action: TextInputAction,
2531}
2532
2533/// Automatic capitalization applied by software keyboards.
2534#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2535pub enum Autocapitalize {
2536    /// No automatic capitalization.
2537    #[default]
2538    None,
2539    /// Capitalize the first letter of each word.
2540    Words,
2541    /// Capitalize the first letter of each sentence.
2542    Sentences,
2543    /// Capitalize every letter.
2544    Characters,
2545}
2546
2547/// The action a software keyboard advertises on its confirm ("enter") key.
2548///
2549/// This affects only how the key is presented (icon or label); pressing it is
2550/// still delivered as ordinary input.
2551///
2552/// The variants are the HTML `enterkeyhint` attribute's value set
2553/// (<https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute>),
2554/// which also maps onto Android's `IME_ACTION_*` constants and iOS's
2555/// `UIReturnKeyType`; [`TextInputAction::Unspecified`] means "emit no hint".
2556#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2557pub enum TextInputAction {
2558    /// Let the platform choose its default presentation.
2559    #[default]
2560    Unspecified,
2561    /// Inserting a line break.
2562    Enter,
2563    /// Committing the field's value.
2564    Done,
2565    /// Navigating to the typed target.
2566    Go,
2567    /// Moving to the next field.
2568    Next,
2569    /// Moving to the previous field.
2570    Previous,
2571    /// Executing a search.
2572    Search,
2573    /// Sending a message.
2574    Send,
2575}
2576
2577/// The variables that can be configured when creating a new window
2578#[derive(Debug)]
2579pub struct WindowOptions {
2580    /// Specifies the state and bounds of the window in screen coordinates.
2581    /// - `None`: Inherit the bounds.
2582    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
2583    pub window_bounds: Option<WindowBounds>,
2584
2585    /// The titlebar configuration of the window
2586    pub titlebar: Option<TitlebarOptions>,
2587
2588    /// Whether the window should be focused when created
2589    pub focus: bool,
2590
2591    /// Whether the window should be shown when created
2592    pub show: bool,
2593
2594    /// The kind of window to create
2595    pub kind: WindowKind,
2596
2597    /// Whether the window can be moved by the user. When `false`, the user cannot drag
2598    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
2599    /// Window-menu tiling items); programmatic moves are still allowed.
2600    pub is_movable: bool,
2601
2602    /// Whether the application owns dragging of the (custom) titlebar, rather than
2603    /// AppKit. Only has an effect on macOS.
2604    ///
2605    /// Set this to `true` for windows that draw their own titlebar and move the window
2606    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
2607    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
2608    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
2609    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
2610    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
2611    ///
2612    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
2613    pub app_owns_titlebar_drag: bool,
2614
2615    /// The minimum interval between animation frames while the window is inactive.
2616    ///
2617    /// Set to `None` to disable inactive-window animation frame throttling.
2618    pub inactive_frame_interval: Option<Duration>,
2619
2620    /// Whether the window should be resizable by the user
2621    pub is_resizable: bool,
2622
2623    /// Whether the window should be minimized by the user
2624    pub is_minimizable: bool,
2625
2626    /// The display to create the window on, if this is None,
2627    /// the window will be created on the main display
2628    pub display_id: Option<DisplayId>,
2629
2630    /// The appearance of the window background.
2631    pub window_background: WindowBackgroundAppearance,
2632
2633    /// Application identifier of the window. Can by used by desktop environments to group applications together.
2634    pub app_id: Option<String>,
2635
2636    /// Window minimum size
2637    pub window_min_size: Option<Size<Pixels>>,
2638
2639    /// Whether to use client or server-side decorations on X11 and Wayland.
2640    /// The platform may ignore requests it cannot satisfy.
2641    pub window_decorations: Option<WindowDecorations>,
2642
2643    /// Icon image (X11 only)
2644    pub icon: Option<Arc<image::RgbaImage>>,
2645
2646    /// Tab group name, allows opening the window as a native tab on macOS 10.12+. Windows with the same tabbing identifier will be grouped together.
2647    pub tabbing_identifier: Option<String>,
2648
2649    /// Whether to render the window using the Display P3 color space on macOS.
2650    pub use_display_p3: bool,
2651}
2652
2653/// The variables that can be configured when creating a new window
2654#[derive(Debug)]
2655#[cfg_attr(
2656    all(
2657        any(target_os = "linux", target_os = "freebsd"),
2658        not(any(feature = "x11", feature = "wayland"))
2659    ),
2660    allow(dead_code)
2661)]
2662#[allow(missing_docs)]
2663pub struct WindowParams {
2664    pub bounds: Bounds<Pixels>,
2665
2666    /// The titlebar configuration of the window
2667    #[cfg_attr(feature = "wayland", allow(dead_code))]
2668    pub titlebar: Option<TitlebarOptions>,
2669
2670    /// The kind of window to create
2671    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2672    pub kind: WindowKind,
2673
2674    /// Whether the window should be movable by the user
2675    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2676    pub is_movable: bool,
2677
2678    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
2679    #[cfg_attr(
2680        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2681        allow(dead_code)
2682    )]
2683    pub app_owns_titlebar_drag: bool,
2684
2685    /// Whether the window should be resizable by the user
2686    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2687    pub is_resizable: bool,
2688
2689    /// Whether the window should be minimized by the user
2690    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2691    pub is_minimizable: bool,
2692
2693    #[cfg_attr(
2694        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2695        allow(dead_code)
2696    )]
2697    pub focus: bool,
2698
2699    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2700    pub show: bool,
2701
2702    /// An image to set as the window icon (x11 only)
2703    #[cfg_attr(feature = "wayland", allow(dead_code))]
2704    pub icon: Option<Arc<image::RgbaImage>>,
2705
2706    #[cfg_attr(feature = "wayland", allow(dead_code))]
2707    pub display_id: Option<DisplayId>,
2708
2709    #[cfg_attr(feature = "wayland", allow(dead_code))]
2710    pub app_id: Option<String>,
2711
2712    pub window_min_size: Option<Size<Pixels>>,
2713
2714    #[cfg(target_os = "macos")]
2715    pub tabbing_identifier: Option<String>,
2716
2717    #[cfg(target_os = "macos")]
2718    pub use_display_p3: bool,
2719}
2720
2721/// Represents the status of how a window should be opened.
2722#[derive(Debug, Copy, Clone, PartialEq)]
2723pub enum WindowBounds {
2724    /// Indicates that the window should open in a windowed state with the given bounds.
2725    Windowed(Bounds<Pixels>),
2726    /// Indicates that the window should open in a maximized state.
2727    /// The bounds provided here represent the restore size of the window.
2728    Maximized(Bounds<Pixels>),
2729    /// Indicates that the window should open in fullscreen mode.
2730    /// The bounds provided here represent the restore size of the window.
2731    Fullscreen(Bounds<Pixels>),
2732}
2733
2734impl Default for WindowBounds {
2735    fn default() -> Self {
2736        WindowBounds::Windowed(Bounds::default())
2737    }
2738}
2739
2740impl WindowBounds {
2741    /// Retrieve the inner bounds
2742    pub fn get_bounds(&self) -> Bounds<Pixels> {
2743        match self {
2744            WindowBounds::Windowed(bounds) => *bounds,
2745            WindowBounds::Maximized(bounds) => *bounds,
2746            WindowBounds::Fullscreen(bounds) => *bounds,
2747        }
2748    }
2749
2750    /// Creates a new window bounds that centers the window on the screen.
2751    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2752        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2753    }
2754}
2755
2756impl Default for WindowOptions {
2757    fn default() -> Self {
2758        Self {
2759            window_bounds: None,
2760            titlebar: Some(TitlebarOptions {
2761                title: Default::default(),
2762                appears_transparent: Default::default(),
2763                traffic_light_position: Default::default(),
2764            }),
2765            focus: true,
2766            show: true,
2767            kind: WindowKind::Normal,
2768            is_movable: true,
2769            app_owns_titlebar_drag: false,
2770            inactive_frame_interval: Some(Duration::from_micros(33_333)),
2771            is_resizable: true,
2772            is_minimizable: true,
2773            display_id: None,
2774            window_background: WindowBackgroundAppearance::default(),
2775            icon: None,
2776            app_id: None,
2777            window_min_size: None,
2778            window_decorations: None,
2779            tabbing_identifier: None,
2780            use_display_p3: false,
2781        }
2782    }
2783}
2784
2785/// The options that can be configured for a window's titlebar
2786#[derive(Debug, Default)]
2787pub struct TitlebarOptions {
2788    /// The initial title of the window
2789    pub title: Option<SharedString>,
2790
2791    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
2792    /// Refer to [`WindowOptions::window_decorations`] on Linux
2793    pub appears_transparent: bool,
2794
2795    /// The position of the macOS traffic light buttons
2796    pub traffic_light_position: Option<Point<Pixels>>,
2797}
2798
2799/// The kind of window to create
2800#[derive(Clone, Debug, PartialEq, Eq)]
2801pub enum WindowKind {
2802    /// A normal application window
2803    Normal,
2804
2805    /// A window that appears above all other windows, usually used for alerts or popups
2806    /// use sparingly!
2807    PopUp,
2808
2809    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
2810    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
2811    ///
2812    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
2813    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
2814    /// implementation reject it with [`popup::PopupNotSupportedError`].
2815    AnchoredPopup(popup::PopupOptions),
2816
2817    /// A floating window that appears on top of its parent window
2818    Floating,
2819
2820    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
2821    /// docks, notifications or wallpapers.
2822    #[cfg(all(target_os = "linux", feature = "wayland"))]
2823    LayerShell(layer_shell::LayerShellOptions),
2824
2825    /// A window that appears on top of its parent window and blocks interaction with it
2826    /// until the modal window is closed
2827    Dialog,
2828}
2829
2830/// The appearance of the window, as defined by the operating system.
2831///
2832/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2833/// values.
2834#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2835pub enum WindowAppearance {
2836    /// A light appearance.
2837    ///
2838    /// On macOS, this corresponds to the `aqua` appearance.
2839    #[default]
2840    Light,
2841
2842    /// A light appearance with vibrant colors.
2843    ///
2844    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
2845    VibrantLight,
2846
2847    /// A dark appearance.
2848    ///
2849    /// On macOS, this corresponds to the `darkAqua` appearance.
2850    Dark,
2851
2852    /// A dark appearance with vibrant colors.
2853    ///
2854    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
2855    VibrantDark,
2856}
2857
2858/// The appearance of the background of the window itself, when there is
2859/// no content or the content is transparent.
2860#[derive(Copy, Clone, Debug, Default, PartialEq)]
2861pub enum WindowBackgroundAppearance {
2862    /// Opaque.
2863    ///
2864    /// This lets the window manager know that content behind this
2865    /// window does not need to be drawn.
2866    ///
2867    /// Actual color depends on the system and themes should define a fully
2868    /// opaque background color instead.
2869    #[default]
2870    Opaque,
2871    /// Plain alpha transparency.
2872    Transparent,
2873    /// Transparency, but the contents behind the window are blurred.
2874    ///
2875    /// Not always supported.
2876    Blurred,
2877    /// The Mica backdrop material, supported on Windows 11.
2878    MicaBackdrop,
2879    /// The Mica Alt backdrop material, supported on Windows 11.
2880    MicaAltBackdrop,
2881}
2882
2883/// The text rendering mode to use for drawing glyphs.
2884#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2885pub enum TextRenderingMode {
2886    /// Use the platform's default text rendering mode.
2887    #[default]
2888    PlatformDefault,
2889    /// Use subpixel (ClearType-style) text rendering.
2890    Subpixel,
2891    /// Use grayscale text rendering.
2892    Grayscale,
2893}
2894
2895/// The options that can be configured for a file dialog prompt
2896#[derive(Clone, Debug)]
2897pub struct PathPromptOptions {
2898    /// Should the prompt allow files to be selected?
2899    pub files: bool,
2900    /// Should the prompt allow directories to be selected?
2901    pub directories: bool,
2902    /// Should the prompt allow multiple files to be selected?
2903    pub multiple: bool,
2904    /// The prompt to show to a user when selecting a path
2905    pub prompt: Option<SharedString>,
2906}
2907
2908/// What kind of prompt styling to show
2909#[derive(Copy, Clone, Debug, PartialEq)]
2910pub enum PromptLevel {
2911    /// A prompt that is shown when the user should be notified of something
2912    Info,
2913
2914    /// A prompt that is shown when the user needs to be warned of a potential problem
2915    Warning,
2916
2917    /// A prompt that is shown when a critical problem has occurred
2918    Critical,
2919}
2920
2921/// Prompt Button
2922#[derive(Clone, Debug, PartialEq)]
2923pub enum PromptButton {
2924    /// Ok button
2925    Ok(SharedString),
2926    /// Cancel button
2927    Cancel(SharedString),
2928    /// Other button
2929    Other(SharedString),
2930}
2931
2932impl PromptButton {
2933    /// Create a button with label
2934    pub fn new(label: impl Into<SharedString>) -> Self {
2935        PromptButton::Other(label.into())
2936    }
2937
2938    /// Create an Ok button
2939    pub fn ok(label: impl Into<SharedString>) -> Self {
2940        PromptButton::Ok(label.into())
2941    }
2942
2943    /// Create a Cancel button
2944    pub fn cancel(label: impl Into<SharedString>) -> Self {
2945        PromptButton::Cancel(label.into())
2946    }
2947
2948    /// Returns true if this button is a cancel button.
2949    #[allow(dead_code)]
2950    pub fn is_cancel(&self) -> bool {
2951        matches!(self, PromptButton::Cancel(_))
2952    }
2953
2954    /// Returns the label of the button
2955    pub fn label(&self) -> &SharedString {
2956        match self {
2957            PromptButton::Ok(label) => label,
2958            PromptButton::Cancel(label) => label,
2959            PromptButton::Other(label) => label,
2960        }
2961    }
2962}
2963
2964impl From<&str> for PromptButton {
2965    fn from(value: &str) -> Self {
2966        match value.to_lowercase().as_str() {
2967            "ok" => PromptButton::Ok("OK".into()),
2968            "cancel" => PromptButton::Cancel("Cancel".into()),
2969            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2970        }
2971    }
2972}
2973
2974/// The style of the cursor (pointer)
2975#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2976pub enum CursorStyle {
2977    /// The default cursor
2978    #[default]
2979    Arrow,
2980
2981    /// A text input cursor
2982    /// corresponds to the CSS cursor value `text`
2983    IBeam,
2984
2985    /// A crosshair cursor
2986    /// corresponds to the CSS cursor value `crosshair`
2987    Crosshair,
2988
2989    /// A closed hand cursor
2990    /// corresponds to the CSS cursor value `grabbing`
2991    ClosedHand,
2992
2993    /// An open hand cursor
2994    /// corresponds to the CSS cursor value `grab`
2995    OpenHand,
2996
2997    /// A pointing hand cursor
2998    /// corresponds to the CSS cursor value `pointer`
2999    PointingHand,
3000
3001    /// A resize left cursor
3002    /// corresponds to the CSS cursor value `w-resize`
3003    ResizeLeft,
3004
3005    /// A resize right cursor
3006    /// corresponds to the CSS cursor value `e-resize`
3007    ResizeRight,
3008
3009    /// A resize cursor to the left and right
3010    /// corresponds to the CSS cursor value `ew-resize`
3011    ResizeLeftRight,
3012
3013    /// A resize up cursor
3014    /// corresponds to the CSS cursor value `n-resize`
3015    ResizeUp,
3016
3017    /// A resize down cursor
3018    /// corresponds to the CSS cursor value `s-resize`
3019    ResizeDown,
3020
3021    /// A resize cursor directing up and down
3022    /// corresponds to the CSS cursor value `ns-resize`
3023    ResizeUpDown,
3024
3025    /// A resize cursor directing up-left and down-right
3026    /// corresponds to the CSS cursor value `nesw-resize`
3027    ResizeUpLeftDownRight,
3028
3029    /// A resize cursor directing up-right and down-left
3030    /// corresponds to the CSS cursor value `nwse-resize`
3031    ResizeUpRightDownLeft,
3032
3033    /// A cursor indicating that the item/column can be resized horizontally.
3034    /// corresponds to the CSS cursor value `col-resize`
3035    ResizeColumn,
3036
3037    /// A cursor indicating that the item/row can be resized vertically.
3038    /// corresponds to the CSS cursor value `row-resize`
3039    ResizeRow,
3040
3041    /// A text input cursor for vertical layout
3042    /// corresponds to the CSS cursor value `vertical-text`
3043    IBeamCursorForVerticalLayout,
3044
3045    /// A cursor indicating that the operation is not allowed
3046    /// corresponds to the CSS cursor value `not-allowed`
3047    OperationNotAllowed,
3048
3049    /// A cursor indicating that the operation will result in a link
3050    /// corresponds to the CSS cursor value `alias`
3051    DragLink,
3052
3053    /// A cursor indicating that the operation will result in a copy
3054    /// corresponds to the CSS cursor value `copy`
3055    DragCopy,
3056
3057    /// A cursor indicating that the operation will result in a context menu
3058    /// corresponds to the CSS cursor value `context-menu`
3059    ContextualMenu,
3060}
3061
3062/// A clipboard item that should be copied to the clipboard
3063#[derive(Clone, Debug, Eq, PartialEq)]
3064pub struct ClipboardItem {
3065    /// The entries in this clipboard item.
3066    pub entries: Vec<ClipboardEntry>,
3067}
3068
3069/// An error produced by [`Platform::read_from_clipboard_async`].
3070///
3071/// Callers surface these failures to users, so the variants distinguish
3072/// conditions that call for different user-facing guidance.
3073#[derive(Clone, Debug, PartialEq, Eq)]
3074pub enum ClipboardReadError {
3075    /// The platform clipboard is not available in this context, e.g. the
3076    /// browser does not expose the async clipboard API or the page is not a
3077    /// secure context.
3078    Unavailable,
3079    /// The platform refused access, e.g. the user declined the browser's
3080    /// clipboard permission prompt or paste confirmation.
3081    Denied(String),
3082    /// The clipboard contents could not be converted into a
3083    /// [`ClipboardItem`].
3084    UnsupportedContent,
3085}
3086
3087impl std::fmt::Display for ClipboardReadError {
3088    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3089        match self {
3090            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
3091            Self::Denied(message) => {
3092                write!(formatter, "clipboard access was denied: {message}")
3093            }
3094            Self::UnsupportedContent => {
3095                formatter.write_str("the clipboard contents are unsupported")
3096            }
3097        }
3098    }
3099}
3100
3101impl std::error::Error for ClipboardReadError {}
3102
3103/// Either a ClipboardString or a ClipboardImage
3104#[derive(Clone, Debug, Eq, PartialEq)]
3105pub enum ClipboardEntry {
3106    /// A string entry
3107    String(ClipboardString),
3108    /// An image entry
3109    Image(Image),
3110    /// A file entry
3111    ExternalPaths(crate::ExternalPaths),
3112}
3113
3114impl ClipboardItem {
3115    /// Create a new ClipboardItem::String with no associated metadata
3116    pub fn new_string(text: String) -> Self {
3117        Self {
3118            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
3119        }
3120    }
3121
3122    /// Create a new ClipboardItem::String with the given text and associated metadata
3123    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
3124        Self {
3125            entries: vec![ClipboardEntry::String(ClipboardString {
3126                text,
3127                metadata: Some(metadata),
3128            })],
3129        }
3130    }
3131
3132    /// Create a new ClipboardItem::String with the given text and associated metadata
3133    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
3134        Self {
3135            entries: vec![ClipboardEntry::String(
3136                ClipboardString::new(text).with_json_metadata(metadata),
3137            )],
3138        }
3139    }
3140
3141    /// Create a new ClipboardItem::Image with the given image with no associated metadata
3142    pub fn new_image(image: &Image) -> Self {
3143        Self {
3144            entries: vec![ClipboardEntry::Image(image.clone())],
3145        }
3146    }
3147
3148    /// Concatenates together all the ClipboardString entries in the item.
3149    /// Returns None if there were no ClipboardString entries.
3150    pub fn text(&self) -> Option<String> {
3151        let mut answer = String::new();
3152
3153        for entry in self.entries.iter() {
3154            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
3155                answer.push_str(text);
3156            }
3157        }
3158
3159        if answer.is_empty() {
3160            for entry in self.entries.iter() {
3161                if let ClipboardEntry::ExternalPaths(paths) = entry {
3162                    for path in &paths.0 {
3163                        use std::fmt::Write as _;
3164                        _ = write!(answer, "{}", path.display());
3165                    }
3166                }
3167            }
3168        }
3169
3170        if !answer.is_empty() {
3171            Some(answer)
3172        } else {
3173            None
3174        }
3175    }
3176
3177    /// If this item is one ClipboardEntry::String, returns its metadata.
3178    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
3179    pub fn metadata(&self) -> Option<&String> {
3180        match self.entries().first() {
3181            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
3182                clipboard_string.metadata.as_ref()
3183            }
3184            _ => None,
3185        }
3186    }
3187
3188    /// Get the item's entries
3189    pub fn entries(&self) -> &[ClipboardEntry] {
3190        &self.entries
3191    }
3192
3193    /// Get owned versions of the item's entries
3194    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
3195        self.entries.into_iter()
3196    }
3197}
3198
3199impl From<ClipboardString> for ClipboardEntry {
3200    fn from(value: ClipboardString) -> Self {
3201        Self::String(value)
3202    }
3203}
3204
3205impl From<String> for ClipboardEntry {
3206    fn from(value: String) -> Self {
3207        Self::from(ClipboardString::from(value))
3208    }
3209}
3210
3211impl From<Image> for ClipboardEntry {
3212    fn from(value: Image) -> Self {
3213        Self::Image(value)
3214    }
3215}
3216
3217impl From<ClipboardEntry> for ClipboardItem {
3218    fn from(value: ClipboardEntry) -> Self {
3219        Self {
3220            entries: vec![value],
3221        }
3222    }
3223}
3224
3225impl From<String> for ClipboardItem {
3226    fn from(value: String) -> Self {
3227        Self::from(ClipboardEntry::from(value))
3228    }
3229}
3230
3231impl From<Image> for ClipboardItem {
3232    fn from(value: Image) -> Self {
3233        Self::from(ClipboardEntry::from(value))
3234    }
3235}
3236
3237/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
3238#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
3239pub enum ImageFormat {
3240    // Sorted from most to least likely to be pasted into an editor,
3241    // which matters when we iterate through them trying to see if
3242    // clipboard content matches them.
3243    /// .png
3244    Png,
3245    /// .jpeg or .jpg
3246    Jpeg,
3247    /// .webp
3248    Webp,
3249    /// .gif
3250    Gif,
3251    /// .svg
3252    Svg,
3253    /// .bmp
3254    Bmp,
3255    /// .tif or .tiff
3256    Tiff,
3257    /// .ico
3258    Ico,
3259    /// Netpbm image formats (.pbm, .ppm, .pgm).
3260    Pnm,
3261}
3262
3263impl ImageFormat {
3264    /// Returns the mime type for the ImageFormat
3265    pub const fn mime_type(self) -> &'static str {
3266        match self {
3267            ImageFormat::Png => "image/png",
3268            ImageFormat::Jpeg => "image/jpeg",
3269            ImageFormat::Webp => "image/webp",
3270            ImageFormat::Gif => "image/gif",
3271            ImageFormat::Svg => "image/svg+xml",
3272            ImageFormat::Bmp => "image/bmp",
3273            ImageFormat::Tiff => "image/tiff",
3274            ImageFormat::Ico => "image/ico",
3275            ImageFormat::Pnm => "image/x-portable-anymap",
3276        }
3277    }
3278
3279    /// Returns the file extension for this image format (without leading dot).
3280    pub const fn extension(self) -> &'static str {
3281        match self {
3282            ImageFormat::Png => "png",
3283            ImageFormat::Jpeg => "jpg",
3284            ImageFormat::Webp => "webp",
3285            ImageFormat::Gif => "gif",
3286            ImageFormat::Svg => "svg",
3287            ImageFormat::Bmp => "bmp",
3288            ImageFormat::Tiff => "tiff",
3289            ImageFormat::Ico => "ico",
3290            ImageFormat::Pnm => "pnm",
3291        }
3292    }
3293
3294    /// Returns the ImageFormat for the given mime type, including known aliases.
3295    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
3296        use strum::IntoEnumIterator;
3297        Self::iter()
3298            .find(|format| format.mime_type() == mime_type)
3299            .or_else(|| Self::from_mime_type_alias(mime_type))
3300    }
3301
3302    /// Non-canonical mime types that some producers use in the wild.
3303    /// Unlike `mime_type()` which returns the single canonical form,
3304    /// these are legacy or shortened variants we still need to recognize.
3305    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
3306        match mime_type {
3307            "image/jpg" => Some(Self::Jpeg),
3308            "image/tif" => Some(Self::Tiff),
3309            _ => None,
3310        }
3311    }
3312}
3313
3314/// An image, with a format and certain bytes
3315#[derive(Clone, Debug, PartialEq, Eq)]
3316pub struct Image {
3317    /// The image format the bytes represent (e.g. PNG)
3318    pub format: ImageFormat,
3319    /// The raw image bytes
3320    pub bytes: Vec<u8>,
3321    /// The unique ID for the image
3322    pub id: u64,
3323}
3324
3325pub(crate) fn decode_static_image(
3326    bytes: &[u8],
3327    format: image::ImageFormat,
3328) -> Result<SmallVec<[Frame; 1]>> {
3329    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
3330        .into_decoder()
3331        .context("creating image decoder")?;
3332    decode_static_image_from_decoder(decoder)
3333}
3334
3335pub(crate) fn decode_static_image_from_decoder(
3336    mut decoder: impl image::ImageDecoder,
3337) -> Result<SmallVec<[Frame; 1]>> {
3338    let orientation = decoder
3339        .orientation()
3340        .context("reading decoder's orientation")?;
3341    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
3342    image.apply_orientation(orientation);
3343
3344    let mut data = image.into_rgba8();
3345    for pixel in data.chunks_exact_mut(4) {
3346        pixel.swap(0, 2);
3347    }
3348
3349    Ok(SmallVec::from_elem(Frame::new(data), 1))
3350}
3351
3352impl Hash for Image {
3353    fn hash<H: Hasher>(&self, state: &mut H) {
3354        state.write_u64(self.id);
3355    }
3356}
3357
3358impl Image {
3359    /// An empty image containing no data
3360    pub fn empty() -> Self {
3361        Self::from_bytes(ImageFormat::Png, Vec::new())
3362    }
3363
3364    /// Create an image from a format and bytes
3365    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
3366        Self {
3367            id: hash(&bytes),
3368            format,
3369            bytes,
3370        }
3371    }
3372
3373    /// Get this image's ID
3374    pub fn id(&self) -> u64 {
3375        self.id
3376    }
3377
3378    /// Use the GPUI `use_asset` API to make this image renderable
3379    pub fn use_render_image(
3380        self: Arc<Self>,
3381        window: &mut Window,
3382        cx: &mut App,
3383    ) -> Option<Arc<RenderImage>> {
3384        ImageSource::Image(self)
3385            .use_data(None, window, cx)
3386            .and_then(|result| result.ok())
3387    }
3388
3389    /// Use the GPUI `get_asset` API to make this image renderable
3390    pub fn get_render_image(
3391        self: Arc<Self>,
3392        window: &mut Window,
3393        cx: &mut App,
3394    ) -> Option<Arc<RenderImage>> {
3395        ImageSource::Image(self)
3396            .get_data(None, window, cx)
3397            .and_then(|result| result.ok())
3398    }
3399
3400    /// Use the GPUI `remove_asset` API to drop this image, if possible.
3401    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
3402        ImageSource::Image(self).remove_asset(cx);
3403    }
3404
3405    /// Check whether this image is present in GPUI's asset cache (loading or
3406    /// loaded), without fetching it.
3407    #[cfg(any(test, feature = "test-support"))]
3408    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
3409        ImageSource::Image(self.clone()).is_asset_cached(cx)
3410    }
3411
3412    /// Convert the clipboard image to an `ImageData` object.
3413    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
3414        let frames = match self.format {
3415            ImageFormat::Gif => {
3416                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
3417                let mut frames = SmallVec::new();
3418
3419                for frame in decoder.into_frames() {
3420                    match frame {
3421                        Ok(mut frame) => {
3422                            // Convert from RGBA to BGRA.
3423                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
3424                                pixel.swap(0, 2);
3425                            }
3426                            frames.push(frame);
3427                        }
3428                        Err(err) => {
3429                            log::debug!("Skipping GIF frame due to decode error: {err}");
3430                        }
3431                    }
3432                }
3433
3434                if frames.is_empty() {
3435                    anyhow::bail!("GIF could not be decoded: all frames failed");
3436                }
3437
3438                frames
3439            }
3440            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
3441            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
3442            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
3443            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
3444            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
3445            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
3446            ImageFormat::Svg => {
3447                return svg_renderer
3448                    .render_single_frame(&self.bytes, 1.0)
3449                    .map_err(Into::into);
3450            }
3451            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
3452        };
3453
3454        Ok(Arc::new(RenderImage::new(frames)))
3455    }
3456
3457    /// Get the format of the clipboard image
3458    pub fn format(&self) -> ImageFormat {
3459        self.format
3460    }
3461
3462    /// Get the raw bytes of the clipboard image
3463    pub fn bytes(&self) -> &[u8] {
3464        self.bytes.as_slice()
3465    }
3466}
3467
3468/// A clipboard item that should be copied to the clipboard
3469#[derive(Clone, Debug, Eq, PartialEq)]
3470pub struct ClipboardString {
3471    /// The text content.
3472    pub text: String,
3473    /// Optional metadata associated with this clipboard string.
3474    pub metadata: Option<String>,
3475}
3476
3477impl ClipboardString {
3478    /// Create a new clipboard string with the given text
3479    pub fn new(text: String) -> Self {
3480        Self {
3481            text,
3482            metadata: None,
3483        }
3484    }
3485
3486    /// Return a new clipboard item with the metadata replaced by the given metadata,
3487    /// after serializing it as JSON.
3488    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
3489        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
3490        self
3491    }
3492
3493    /// Get the text of the clipboard string
3494    pub fn text(&self) -> &String {
3495        &self.text
3496    }
3497
3498    /// Get the owned text of the clipboard string
3499    pub fn into_text(self) -> String {
3500        self.text
3501    }
3502
3503    /// Get the metadata of the clipboard string, formatted as JSON
3504    pub fn metadata_json<T>(&self) -> Option<T>
3505    where
3506        T: for<'a> Deserialize<'a>,
3507    {
3508        self.metadata
3509            .as_ref()
3510            .and_then(|m| serde_json::from_str(m).ok())
3511    }
3512
3513    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3514    /// Compute a hash of the given text for clipboard change detection.
3515    pub fn text_hash(text: &str) -> u64 {
3516        let mut hasher = SeaHasher::new();
3517        text.hash(&mut hasher);
3518        hasher.finish()
3519    }
3520}
3521
3522impl From<String> for ClipboardString {
3523    fn from(value: String) -> Self {
3524        Self {
3525            text: value,
3526            metadata: None,
3527        }
3528    }
3529}
3530
3531#[cfg(test)]
3532mod image_tests {
3533    use super::*;
3534    use std::sync::Arc;
3535
3536    #[test]
3537    fn test_image_to_image_data_applies_exif_orientation() {
3538        let image = Image::from_bytes(
3539            ImageFormat::Jpeg,
3540            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
3541        );
3542
3543        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3544
3545        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3546
3547        let bytes = render_image.as_bytes(0).unwrap();
3548        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3549        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3550    }
3551
3552    #[test]
3553    fn test_svg_image_to_image_data_converts_to_bgra() {
3554        let image = Image::from_bytes(
3555            ImageFormat::Svg,
3556            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3557<rect width="1" height="1" fill="#38BDF8"/>
3558</svg>"##
3559                .to_vec(),
3560        );
3561
3562        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3563        let bytes = render_image.as_bytes(0).unwrap();
3564
3565        for pixel in bytes.chunks_exact(4) {
3566            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3567        }
3568    }
3569}
3570
3571#[cfg(test)]
3572mod atlas_tests {
3573    use super::*;
3574
3575    const TILE_SIZE: Size<DevicePixels> = Size {
3576        width: DevicePixels(1),
3577        height: DevicePixels(1),
3578    };
3579
3580    #[derive(Default)]
3581    struct RecordingAtlasBackend {
3582        insert_calls: u32,
3583        fail_next_insert: bool,
3584        removed_tiles: Vec<AtlasTile>,
3585    }
3586
3587    impl AtlasBackend for RecordingAtlasBackend {
3588        fn insert(
3589            &mut self,
3590            kind: AtlasTextureKind,
3591            size: Size<DevicePixels>,
3592            _bytes: &[u8],
3593        ) -> Result<AtlasTile> {
3594            self.insert_calls += 1;
3595            if std::mem::take(&mut self.fail_next_insert) {
3596                anyhow::bail!("backend failed");
3597            }
3598            Ok(AtlasTile {
3599                texture_id: AtlasTextureId { index: 0, kind },
3600                tile_id: TileId(self.insert_calls),
3601                padding: 0,
3602                bounds: Bounds {
3603                    origin: Point::default(),
3604                    size,
3605                },
3606            })
3607        }
3608
3609        fn remove(&mut self, tile: AtlasTile) {
3610            self.removed_tiles.push(tile);
3611        }
3612    }
3613
3614    fn image_key(image_id: usize) -> AtlasKey {
3615        AtlasKey::Image(RenderImageParams {
3616            image_id: crate::ImageId(image_id),
3617            frame_index: 0,
3618        })
3619    }
3620
3621    fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
3622        Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
3623    }
3624
3625    #[test]
3626    fn only_successful_inserts_are_cached() -> Result<()> {
3627        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3628        let key = image_key(1);
3629
3630        assert_eq!(
3631            state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
3632            None
3633        );
3634        state
3635            .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
3636            .expect_err("builder error should propagate");
3637        assert!(!state.contains(&key));
3638        assert_eq!(state.backend.insert_calls, 0);
3639
3640        state.backend.fail_next_insert = true;
3641        state
3642            .get_or_insert_with(key.clone(), &mut build_tile)
3643            .expect_err("backend error should propagate");
3644        assert!(!state.contains(&key));
3645        assert_eq!(state.backend.insert_calls, 1);
3646
3647        let tile = state
3648            .get_or_insert_with(key.clone(), &mut build_tile)?
3649            .context("builder should produce a tile")?;
3650        assert_eq!(tile.texture_id.kind, key.texture_kind());
3651        assert_eq!(
3652            state.get_or_insert_with(key.clone(), &mut || {
3653                anyhow::bail!("cache hit must not call the builder")
3654            })?,
3655            Some(tile)
3656        );
3657        assert!(state.contains(&key));
3658        assert_eq!(state.backend.insert_calls, 2);
3659        Ok(())
3660    }
3661
3662    #[test]
3663    fn remove_and_clear_invalidate_keys() -> Result<()> {
3664        let mut state = AtlasState::new(RecordingAtlasBackend::default());
3665        let key = image_key(1);
3666        let other_key = image_key(2);
3667        let tile = state
3668            .get_or_insert_with(key.clone(), &mut build_tile)?
3669            .context("builder should produce a tile")?;
3670        state
3671            .get_or_insert_with(other_key.clone(), &mut build_tile)?
3672            .context("builder should produce another tile")?;
3673
3674        state.remove(&key);
3675        state.remove(&key);
3676        assert!(!state.contains(&key));
3677        assert!(state.contains(&other_key));
3678        assert_eq!(state.backend.removed_tiles, vec![tile]);
3679
3680        let mut reset_calls = 0;
3681        state.clear(|_| reset_calls += 1);
3682        assert_eq!(reset_calls, 1);
3683        assert!(!state.contains(&other_key));
3684        assert_eq!(state.backend.removed_tiles, vec![tile]);
3685        Ok(())
3686    }
3687}
3688
3689#[cfg(test)]
3690mod frame_signal_tests {
3691    use super::*;
3692
3693    #[cfg(feature = "profiler")]
3694    #[test]
3695    fn coalesced_signals_retain_the_earliest_until_drained() {
3696        for (first_source, later_source) in [
3697            (
3698                FrameRequestSource::NativeCallback,
3699                FrameRequestSource::LocalSchedule,
3700            ),
3701            (
3702                FrameRequestSource::LocalSchedule,
3703                FrameRequestSource::NativeCallback,
3704            ),
3705        ] {
3706            let signal = PlatformFrameSignal::new();
3707            let first =
3708                PlatformFrameSignal::capture(Instant::now).expect("profiling captures requests");
3709            assert_eq!(signal.take(), None);
3710            signal.record(first + Duration::from_millis(16), later_source);
3711            signal.record(first, first_source);
3712            signal.record(first + Duration::from_millis(32), later_source);
3713            assert_eq!(signal.take(), Some((first, first_source)));
3714            assert_eq!(signal.take(), None);
3715            let next = first + Duration::from_millis(48);
3716            signal.record(next, later_source);
3717            assert_eq!(signal.take(), Some((next, later_source)));
3718        }
3719    }
3720
3721    #[cfg(not(feature = "profiler"))]
3722    #[test]
3723    fn disabled_profiling_does_not_capture_or_store_timestamps() {
3724        let captured = PlatformFrameSignal::capture(|| {
3725            panic!("profiler-disabled builds must not evaluate the capture closure");
3726        });
3727        assert_eq!(captured, None);
3728        let signal = PlatformFrameSignal::new();
3729        signal.record(Instant::now(), FrameRequestSource::LocalSchedule);
3730        assert_eq!(signal.take(), None);
3731    }
3732}
3733
3734#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3735mod tests {
3736    use super::*;
3737    use std::collections::HashSet;
3738
3739    #[test]
3740    fn test_refresh_interval_from_hz() {
3741        assert_eq!(
3742            refresh_interval_from_hz(50.0),
3743            Some(Duration::from_millis(20))
3744        );
3745        assert_eq!(
3746            refresh_interval_from_hz(120.0),
3747            Some(Duration::from_secs(1) / 120)
3748        );
3749        for unknown in [0.0, -60.0, f64::NAN, f64::INFINITY] {
3750            assert_eq!(refresh_interval_from_hz(unknown), None);
3751        }
3752    }
3753
3754    #[test]
3755    fn test_window_button_layout_parse_standard() {
3756        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3757        assert_eq!(
3758            layout.left,
3759            [
3760                Some(WindowButton::Close),
3761                Some(WindowButton::Minimize),
3762                None
3763            ]
3764        );
3765        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3766    }
3767
3768    #[test]
3769    fn test_window_button_layout_parse_right_only() {
3770        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3771        assert_eq!(layout.left, [None, None, None]);
3772        assert_eq!(
3773            layout.right,
3774            [
3775                Some(WindowButton::Minimize),
3776                Some(WindowButton::Maximize),
3777                Some(WindowButton::Close)
3778            ]
3779        );
3780    }
3781
3782    #[test]
3783    fn test_window_button_layout_parse_left_only() {
3784        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3785        assert_eq!(
3786            layout.left,
3787            [
3788                Some(WindowButton::Close),
3789                Some(WindowButton::Minimize),
3790                Some(WindowButton::Maximize)
3791            ]
3792        );
3793        assert_eq!(layout.right, [None, None, None]);
3794    }
3795
3796    #[test]
3797    fn test_window_button_layout_parse_with_whitespace() {
3798        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3799        assert_eq!(
3800            layout.left,
3801            [
3802                Some(WindowButton::Close),
3803                Some(WindowButton::Minimize),
3804                None
3805            ]
3806        );
3807        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3808    }
3809
3810    #[test]
3811    fn test_window_button_layout_parse_empty() {
3812        let layout = WindowButtonLayout::parse("").unwrap();
3813        assert_eq!(layout.left, [None, None, None]);
3814        assert_eq!(layout.right, [None, None, None]);
3815    }
3816
3817    #[test]
3818    fn test_window_button_layout_parse_intentionally_empty() {
3819        let layout = WindowButtonLayout::parse(":").unwrap();
3820        assert_eq!(layout.left, [None, None, None]);
3821        assert_eq!(layout.right, [None, None, None]);
3822    }
3823
3824    #[test]
3825    fn test_window_button_layout_parse_invalid_buttons() {
3826        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3827        assert_eq!(
3828            layout.left,
3829            [
3830                Some(WindowButton::Close),
3831                Some(WindowButton::Minimize),
3832                None
3833            ]
3834        );
3835        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3836    }
3837
3838    #[test]
3839    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3840        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3841        assert_eq!(
3842            layout.right,
3843            [
3844                Some(WindowButton::Close),
3845                Some(WindowButton::Minimize),
3846                None
3847            ]
3848        );
3849        assert_eq!(layout.format(), ":close,minimize");
3850    }
3851
3852    #[test]
3853    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3854        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3855        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3856        assert_eq!(
3857            layout.right,
3858            [
3859                Some(WindowButton::Maximize),
3860                Some(WindowButton::Minimize),
3861                None
3862            ]
3863        );
3864
3865        let button_ids: Vec<_> = layout
3866            .left
3867            .iter()
3868            .chain(layout.right.iter())
3869            .flatten()
3870            .map(WindowButton::id)
3871            .collect();
3872        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3873        assert_eq!(unique_button_ids.len(), button_ids.len());
3874        assert_eq!(layout.format(), "close:maximize,minimize");
3875    }
3876
3877    #[test]
3878    fn test_window_button_layout_parse_gnome_style() {
3879        let layout = WindowButtonLayout::parse("close").unwrap();
3880        assert_eq!(layout.left, [None, None, None]);
3881        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3882    }
3883
3884    #[test]
3885    fn test_window_button_layout_parse_elementary_style() {
3886        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3887        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3888        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3889    }
3890
3891    #[test]
3892    fn test_window_button_layout_round_trip() {
3893        let cases = [
3894            "close:minimize,maximize",
3895            "minimize,maximize,close:",
3896            ":close",
3897            "close:",
3898            "close:maximize",
3899            ":",
3900        ];
3901
3902        for case in cases {
3903            let layout = WindowButtonLayout::parse(case).unwrap();
3904            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3905        }
3906    }
3907
3908    #[test]
3909    fn test_window_button_layout_linux_default() {
3910        let layout = WindowButtonLayout::linux_default();
3911        assert_eq!(layout.left, [None, None, None]);
3912        assert_eq!(
3913            layout.right,
3914            [
3915                Some(WindowButton::Minimize),
3916                Some(WindowButton::Maximize),
3917                Some(WindowButton::Close)
3918            ]
3919        );
3920
3921        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3922        assert_eq!(round_tripped, layout);
3923    }
3924
3925    #[test]
3926    fn test_window_button_layout_parse_all_invalid() {
3927        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3928    }
3929}