Skip to main content

gpui/
platform.rs

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