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    fn schedule_frame(&self) {}
870    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
871    fn is_subpixel_rendering_supported(&self) -> bool;
872
873    // macOS specific methods
874    fn get_title(&self) -> String {
875        String::new()
876    }
877    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
878        None
879    }
880    fn tab_bar_visible(&self) -> bool {
881        false
882    }
883    fn set_edited(&mut self, _edited: bool) {}
884    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
885    fn toggle_simple_fullscreen(&self) {}
886    fn is_simple_fullscreen(&self) -> bool {
887        false
888    }
889    #[cfg(target_os = "macos")]
890    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
891    fn show_character_palette(&self) {}
892    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
893    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
894    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
895    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
896    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
897    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
898    fn merge_all_windows(&self) {}
899    fn move_tab_to_new_window(&self) {}
900    fn toggle_window_tab_overview(&self) {}
901    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
902
903    #[cfg(target_os = "windows")]
904    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
905
906    // Linux specific methods
907    fn inner_window_bounds(&self) -> WindowBounds {
908        self.window_bounds()
909    }
910    fn request_decorations(&self, _decorations: WindowDecorations) {}
911    fn show_window_menu(&self, _position: Point<Pixels>) {}
912    fn start_window_move(&self) {}
913    fn can_start_external_drag(&self) -> bool {
914        false
915    }
916    fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
917        false
918    }
919    fn start_window_resize(&self, _edge: ResizeEdge) {}
920    fn set_exclusive_zone(&self, _zone: Pixels) {}
921    #[cfg(all(target_os = "linux", feature = "wayland"))]
922    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
923    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
924    fn window_decorations(&self) -> Decorations {
925        Decorations::Server
926    }
927    fn set_app_id(&mut self, _app_id: &str) {}
928    fn map_window(&mut self) -> anyhow::Result<()> {
929        Ok(())
930    }
931    fn window_controls(&self) -> WindowControls {
932        WindowControls::default()
933    }
934    fn set_client_inset(&self, _inset: Pixels) {}
935    fn gpu_specs(&self) -> Option<GpuSpecs>;
936
937    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
938
939    // Mobile platform methods.
940
941    /// The regions of this window currently obscured or reserved by the
942    /// system. Zero on platforms without such regions.
943    fn insets(&self) -> WindowInsets {
944        WindowInsets::default()
945    }
946
947    /// Registers a callback invoked whenever [`Self::insets`] change.
948    ///
949    /// Contract: fires continuously during animated transitions (Android
950    /// `WindowInsetsAnimation` progress; on iOS the platform interpolates
951    /// the keyboard animation curve on frame ticks) and is exact at rest.
952    fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
953
954    /// Sets the handler for the system back action (Android back
955    /// button/gesture; no source on iOS or desktop).
956    fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
957
958    /// Declares whether the application would currently handle the system
959    /// back action (e.g. navigation depth > 0).
960    fn set_back_enabled(&self, _enabled: bool) {}
961
962    /// Requests that the soft keyboard be shown.
963    fn show_soft_keyboard(&self) {}
964
965    /// Requests that the soft keyboard be hidden.
966    fn hide_soft_keyboard(&self) {}
967
968    /// Inform the operating system that the text input state has changed
969    fn text_input_state_changed(&self, _change: TextInputStateChange) {}
970
971    fn play_system_bell(&self) {}
972
973    /// Initialize the accessibility adapter with callbacks.
974    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
975
976    /// Provide a TreeUpdate to the accessibility adapter.
977    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
978
979    /// Inform the adapter of updated window bounds.
980    fn a11y_update_window_bounds(&self) {}
981
982    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
983    fn as_test(&mut self) -> Option<&mut TestWindow> {
984        None
985    }
986
987    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
988    /// This does not present the frame to screen - useful for visual testing where we want
989    /// to capture what would be rendered without displaying it or requiring the window to be visible.
990    #[cfg(any(test, feature = "test-support"))]
991    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
992        anyhow::bail!("render_to_image not implemented for this platform")
993    }
994}
995
996/// A renderer for headless windows that can produce real rendered output.
997#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
998pub trait PlatformHeadlessRenderer {
999    /// Render a scene and return the result as an RGBA image.
1000    fn render_scene_to_image(
1001        &mut self,
1002        scene: &Scene,
1003        size: Size<DevicePixels>,
1004    ) -> Result<RgbaImage>;
1005
1006    /// Render a scene to an offscreen target without reading the result back.
1007    ///
1008    /// This is the headless analogue of presenting a frame: it performs the
1009    /// same CPU-side scene encoding and GPU submission as drawing to a real
1010    /// window, but doesn't block on GPU completion or copy pixels back.
1011    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1012
1013    /// Returns the sprite atlas used by this renderer.
1014    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1015}
1016
1017/// Type alias for runnables with metadata.
1018/// Previously an enum with a single variant, now simplified to a direct type alias.
1019#[doc(hidden)]
1020pub type RunnableVariant = Runnable<RunnableMeta>;
1021
1022#[doc(hidden)]
1023pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1024
1025#[doc(hidden)]
1026pub enum TasksIncluded {
1027    OnlyCompleted,
1028    CompletedAndRunning,
1029}
1030
1031/// This type is public so that our test macro can generate and use it, but it should not
1032/// be considered part of our public API.
1033#[doc(hidden)]
1034pub trait PlatformDispatcher: Send + Sync {
1035    fn is_main_thread(&self) -> bool;
1036    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1037    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1038    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1039
1040    fn dispatch_on_main_thread_when_idle(
1041        &self,
1042        runnable: RunnableVariant,
1043        timeout: Option<Duration>,
1044    ) {
1045        let _ = timeout;
1046        self.dispatch_on_main_thread(runnable, Priority::Low);
1047    }
1048
1049    fn idle_time_remaining(&self) -> Option<Duration> {
1050        None
1051    }
1052
1053    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1054
1055    fn now(&self) -> Instant {
1056        Instant::now()
1057    }
1058
1059    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1060        gpui_util::defer(Box::new(|| {}))
1061    }
1062
1063    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1064    fn as_test(&self) -> Option<&TestDispatcher> {
1065        None
1066    }
1067
1068    // This cfg must match the `threaded_dispatcher` module's, which implements
1069    // this method whenever it compiles.
1070    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1071    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1072        None
1073    }
1074}
1075
1076#[expect(missing_docs)]
1077pub trait PlatformTextSystem: Send + Sync {
1078    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1079    /// Get all available font names.
1080    fn all_font_names(&self) -> Vec<String>;
1081    /// Get the font ID for a font descriptor.
1082    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1083    /// Prewarm any system font caches needed to shape text.
1084    fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1085    /// Get metrics for a font.
1086    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1087    /// Get typographic bounds for a glyph.
1088    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1089    /// Get the advance width for a glyph.
1090    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1091    /// Get the glyph ID for a character.
1092    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1093    /// Get raster bounds for a glyph.
1094    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1095    /// Rasterize a glyph.
1096    fn rasterize_glyph(
1097        &self,
1098        params: &RenderGlyphParams,
1099        raster_bounds: Bounds<DevicePixels>,
1100    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1101    /// Layout a line of text with the given font runs.
1102    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1103    /// Returns the recommended text rendering mode for the given font and size.
1104    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1105    -> TextRenderingMode;
1106    /// Returns the dilation level to use for a glyph painted in the given color.
1107    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1108        0
1109    }
1110}
1111
1112#[expect(missing_docs)]
1113pub struct NoopTextSystem;
1114
1115#[expect(missing_docs)]
1116impl NoopTextSystem {
1117    #[allow(dead_code)]
1118    pub fn new() -> Self {
1119        Self
1120    }
1121}
1122
1123impl PlatformTextSystem for NoopTextSystem {
1124    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1125        Ok(())
1126    }
1127
1128    fn all_font_names(&self) -> Vec<String> {
1129        Vec::new()
1130    }
1131
1132    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1133        Ok(FontId(1))
1134    }
1135
1136    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1137        FontMetrics {
1138            units_per_em: 1000,
1139            ascent: 1025.0,
1140            descent: -275.0,
1141            line_gap: 0.0,
1142            underline_position: -95.0,
1143            underline_thickness: 60.0,
1144            cap_height: 698.0,
1145            x_height: 516.0,
1146            bounding_box: Bounds {
1147                origin: Point {
1148                    x: -260.0,
1149                    y: -245.0,
1150                },
1151                size: Size {
1152                    width: 1501.0,
1153                    height: 1364.0,
1154                },
1155            },
1156        }
1157    }
1158
1159    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1160        Ok(Bounds {
1161            origin: Point { x: 54.0, y: 0.0 },
1162            size: size(392.0, 528.0),
1163        })
1164    }
1165
1166    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1167        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1168    }
1169
1170    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1171        Some(GlyphId(ch.len_utf16() as u32))
1172    }
1173
1174    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1175        Ok(Default::default())
1176    }
1177
1178    fn rasterize_glyph(
1179        &self,
1180        _params: &RenderGlyphParams,
1181        raster_bounds: Bounds<DevicePixels>,
1182    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1183        Ok((raster_bounds.size, Vec::new()))
1184    }
1185
1186    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1187        let mut position = px(0.);
1188        let metrics = self.font_metrics(FontId(0));
1189        let em_width = font_size
1190            * self
1191                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1192                .unwrap()
1193                .width
1194            / metrics.units_per_em as f32;
1195        let mut glyphs = Vec::new();
1196        for (ix, c) in text.char_indices() {
1197            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1198                glyphs.push(ShapedGlyph {
1199                    id: glyph,
1200                    position: point(position, px(0.)),
1201                    index: ix,
1202                    is_emoji: glyph.0 == 2,
1203                });
1204                if glyph.0 == 2 {
1205                    position += em_width * 2.0;
1206                } else {
1207                    position += em_width;
1208                }
1209            } else {
1210                position += em_width
1211            }
1212        }
1213        let mut runs = Vec::default();
1214        if !glyphs.is_empty() {
1215            runs.push(ShapedRun {
1216                font_id: FontId(0),
1217                glyphs,
1218            });
1219        } else {
1220            position = px(0.);
1221        }
1222
1223        LineLayout {
1224            font_size,
1225            width: position,
1226            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1227            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1228            runs,
1229            len: text.len(),
1230        }
1231    }
1232
1233    fn recommended_rendering_mode(
1234        &self,
1235        _font_id: FontId,
1236        _font_size: Pixels,
1237    ) -> TextRenderingMode {
1238        TextRenderingMode::Grayscale
1239    }
1240}
1241
1242// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1243// Copyright (c) Microsoft Corporation.
1244// Licensed under the MIT license.
1245/// Compute gamma correction ratios for subpixel text rendering.
1246#[allow(dead_code)]
1247pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1248    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1249        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1250        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1251        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1252        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1253        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1254        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1255        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1256        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1257        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1258        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1259        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1260        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1261        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1262    ];
1263
1264    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1265    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1266
1267    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1268    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1269
1270    [
1271        ratios[0] * NORM13,
1272        ratios[1] * NORM24,
1273        ratios[2] * NORM13,
1274        ratios[3] * NORM24,
1275    ]
1276}
1277
1278#[derive(PartialEq, Eq, Hash, Clone)]
1279#[expect(missing_docs)]
1280pub enum AtlasKey {
1281    Glyph(RenderGlyphParams),
1282    Svg(RenderSvgParams),
1283    Image(RenderImageParams),
1284}
1285
1286impl AtlasKey {
1287    #[cfg_attr(
1288        all(
1289            any(target_os = "linux", target_os = "freebsd"),
1290            not(any(feature = "x11", feature = "wayland"))
1291        ),
1292        allow(dead_code)
1293    )]
1294    /// Returns the texture kind for this atlas key.
1295    pub fn texture_kind(&self) -> AtlasTextureKind {
1296        match self {
1297            AtlasKey::Glyph(params) => {
1298                if params.is_emoji {
1299                    AtlasTextureKind::Polychrome
1300                } else if params.subpixel_rendering {
1301                    AtlasTextureKind::Subpixel
1302                } else {
1303                    AtlasTextureKind::Monochrome
1304                }
1305            }
1306            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1307            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1308        }
1309    }
1310}
1311
1312impl From<RenderGlyphParams> for AtlasKey {
1313    fn from(params: RenderGlyphParams) -> Self {
1314        Self::Glyph(params)
1315    }
1316}
1317
1318impl From<RenderSvgParams> for AtlasKey {
1319    fn from(params: RenderSvgParams) -> Self {
1320        Self::Svg(params)
1321    }
1322}
1323
1324impl From<RenderImageParams> for AtlasKey {
1325    fn from(params: RenderImageParams) -> Self {
1326        Self::Image(params)
1327    }
1328}
1329
1330#[expect(missing_docs)]
1331pub trait PlatformAtlas {
1332    fn get_or_insert_with<'a>(
1333        &self,
1334        key: &AtlasKey,
1335        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1336    ) -> Result<Option<AtlasTile>>;
1337    fn remove(&self, key: &AtlasKey);
1338
1339    #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1340    fn contains(&self, _key: &AtlasKey) -> bool {
1341        false
1342    }
1343}
1344
1345#[doc(hidden)]
1346pub struct AtlasTextureList<T> {
1347    pub textures: Vec<Option<T>>,
1348    pub free_list: Vec<usize>,
1349}
1350
1351impl<T> Default for AtlasTextureList<T> {
1352    fn default() -> Self {
1353        Self {
1354            textures: Vec::default(),
1355            free_list: Vec::default(),
1356        }
1357    }
1358}
1359
1360impl<T> ops::Index<usize> for AtlasTextureList<T> {
1361    type Output = Option<T>;
1362
1363    fn index(&self, index: usize) -> &Self::Output {
1364        &self.textures[index]
1365    }
1366}
1367
1368impl<T> AtlasTextureList<T> {
1369    #[allow(unused)]
1370    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1371        self.free_list.clear();
1372        self.textures.drain(..)
1373    }
1374
1375    #[allow(dead_code)]
1376    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1377        self.textures.iter_mut().flatten()
1378    }
1379}
1380
1381#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1382#[repr(C)]
1383#[expect(missing_docs)]
1384pub struct AtlasTile {
1385    /// The texture this tile belongs to.
1386    pub texture_id: AtlasTextureId,
1387    /// The unique ID of this tile within its texture.
1388    pub tile_id: TileId,
1389    /// Padding around the tile content in pixels.
1390    pub padding: u32,
1391    /// The bounds of this tile within the texture.
1392    pub bounds: Bounds<DevicePixels>,
1393}
1394
1395#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1396#[repr(C)]
1397#[expect(missing_docs)]
1398pub struct AtlasTextureId {
1399    // We use u32 instead of usize for Metal Shader Language compatibility
1400    /// The index of this texture in the atlas.
1401    pub index: u32,
1402    /// The kind of content stored in this texture.
1403    pub kind: AtlasTextureKind,
1404}
1405
1406#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1407#[repr(C)]
1408#[cfg_attr(
1409    all(
1410        any(target_os = "linux", target_os = "freebsd"),
1411        not(any(feature = "x11", feature = "wayland"))
1412    ),
1413    allow(dead_code)
1414)]
1415#[expect(missing_docs)]
1416pub enum AtlasTextureKind {
1417    Monochrome = 0,
1418    Polychrome = 1,
1419    Subpixel = 2,
1420}
1421
1422#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1423#[repr(C)]
1424#[expect(missing_docs)]
1425pub struct TileId(pub u32);
1426
1427impl From<etagere::AllocId> for TileId {
1428    fn from(id: etagere::AllocId) -> Self {
1429        Self(id.serialize())
1430    }
1431}
1432
1433impl From<TileId> for etagere::AllocId {
1434    fn from(id: TileId) -> Self {
1435        Self::deserialize(id.0)
1436    }
1437}
1438
1439#[expect(missing_docs)]
1440pub struct PlatformInputHandler {
1441    cx: AsyncWindowContext,
1442    handler: Box<dyn InputHandler>,
1443}
1444
1445#[expect(missing_docs)]
1446#[cfg_attr(
1447    all(
1448        any(target_os = "linux", target_os = "freebsd"),
1449        not(any(feature = "x11", feature = "wayland"))
1450    ),
1451    allow(dead_code)
1452)]
1453impl PlatformInputHandler {
1454    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1455        Self { cx, handler }
1456    }
1457
1458    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1459        self.cx
1460            .update(|window, cx| {
1461                self.handler
1462                    .selected_text_range(ignore_disabled_input, window, cx)
1463            })
1464            .ok()
1465            .flatten()
1466    }
1467
1468    #[cfg_attr(target_os = "windows", allow(dead_code))]
1469    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1470        self.cx
1471            .update(|window, cx| self.handler.marked_text_range(window, cx))
1472            .ok()
1473            .flatten()
1474    }
1475
1476    #[cfg_attr(
1477        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1478        allow(dead_code)
1479    )]
1480    pub fn text_for_range(
1481        &mut self,
1482        range_utf16: Range<usize>,
1483        adjusted: &mut Option<Range<usize>>,
1484    ) -> Option<String> {
1485        self.cx
1486            .update(|window, cx| {
1487                self.handler
1488                    .text_for_range(range_utf16, adjusted, window, cx)
1489            })
1490            .ok()
1491            .flatten()
1492    }
1493
1494    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1495        self.cx
1496            .update(|window, cx| {
1497                self.handler
1498                    .replace_text_in_range(replacement_range, text, window, cx);
1499            })
1500            .ok();
1501    }
1502
1503    pub fn replace_and_mark_text_in_range(
1504        &mut self,
1505        range_utf16: Option<Range<usize>>,
1506        new_text: &str,
1507        new_selected_range: Option<Range<usize>>,
1508    ) {
1509        self.cx
1510            .update(|window, cx| {
1511                self.handler.replace_and_mark_text_in_range(
1512                    range_utf16,
1513                    new_text,
1514                    new_selected_range,
1515                    window,
1516                    cx,
1517                )
1518            })
1519            .ok();
1520    }
1521
1522    #[cfg_attr(target_os = "windows", allow(dead_code))]
1523    pub fn unmark_text(&mut self) {
1524        self.cx
1525            .update(|window, cx| self.handler.unmark_text(window, cx))
1526            .ok();
1527    }
1528
1529    pub fn paste(&mut self, item: ClipboardItem) {
1530        self.cx
1531            .update(|window, cx| self.handler.paste(item, window, cx))
1532            .ok();
1533    }
1534
1535    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1536        self.cx
1537            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1538            .ok()
1539            .flatten()
1540    }
1541
1542    #[allow(dead_code)]
1543    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1544        self.handler.apple_press_and_hold_enabled()
1545    }
1546
1547    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1548        self.handler.replace_text_in_range(None, input, window, cx);
1549    }
1550
1551    pub fn compute_ime_candidate_bounds(
1552        marked_range: Option<Range<usize>>,
1553        selection: &UTF16Selection,
1554        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1555    ) -> Option<Bounds<Pixels>> {
1556        if let Some(marked_range) = marked_range {
1557            // Default to the start of the marked (composing) range.
1558            let mut line_start = marked_range.start;
1559
1560            // Walk backward from the caret looking for a line break. A change in
1561            // the Y coordinate means we crossed into the previous visual line, so
1562            // the line start is one position after the break point.
1563            let caret = selection.range.end;
1564            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1565                for i in (marked_range.start..caret).rev() {
1566                    if let Some(b) = bounds_for_range(i..i) {
1567                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1568                            line_start = i + 1;
1569                            break;
1570                        }
1571                    }
1572                }
1573            }
1574            bounds_for_range(line_start..line_start)
1575        } else {
1576            // No active composition — use the selection endpoint.
1577            let offset = if selection.reversed {
1578                selection.range.start
1579            } else {
1580                selection.range.end
1581            };
1582            bounds_for_range(offset..offset)
1583        }
1584    }
1585
1586    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1587        let marked_range = self.handler.marked_text_range(window, cx);
1588        let selection = self.handler.selected_text_range(true, window, cx)?;
1589        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1590            self.handler.bounds_for_range(range, window, cx)
1591        })
1592    }
1593
1594    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1595        let marked_range = self.marked_text_range();
1596        let selection = self.selected_text_range(true)?;
1597        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1598            self.bounds_for_range(range)
1599        })
1600    }
1601
1602    #[allow(unused)]
1603    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1604        self.cx
1605            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1606            .ok()
1607            .flatten()
1608    }
1609
1610    /// See [`InputHandler::set_selected_text_range`].
1611    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1612        self.cx
1613            .update(|window, cx| {
1614                self.handler
1615                    .set_selected_text_range(range_utf16, window, cx)
1616            })
1617            .ok();
1618    }
1619
1620    /// See [`InputHandler::element_bounds`].
1621    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1622        self.cx
1623            .update(|window, cx| self.handler.element_bounds(window, cx))
1624            .ok()
1625            .flatten()
1626    }
1627
1628    /// See [`InputHandler::text_length_utf16`].
1629    pub fn text_length_utf16(&mut self) -> Option<usize> {
1630        self.cx
1631            .update(|window, cx| self.handler.text_length_utf16(window, cx))
1632            .ok()
1633            .flatten()
1634    }
1635
1636    #[allow(dead_code)]
1637    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1638        self.handler.accepts_text_input(window, cx)
1639    }
1640
1641    #[allow(dead_code)]
1642    pub fn query_accepts_text_input(&mut self) -> bool {
1643        self.cx
1644            .update(|window, cx| self.handler.accepts_text_input(window, cx))
1645            .unwrap_or(true)
1646    }
1647
1648    /// See [`InputHandler::prefers_ime_for_printable_keys`].
1649    ///
1650    /// This is not a pure delegation to the handler: while a multi-stroke binding is pending this
1651    /// returns `false` regardless of the handler's preference, because the next printable key may
1652    /// complete a binding whose prefix already bypassed the IME.
1653    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1654        self.cx
1655            .update(|window, cx| {
1656                // The next printable key may complete a chord whose prefix bypassed the IME.
1657                !window.has_pending_keystrokes()
1658                    && self.handler.prefers_ime_for_printable_keys(window, cx)
1659            })
1660            .unwrap_or(false)
1661    }
1662
1663    /// See [`InputHandler::text_input_configuration`].
1664    pub fn text_input_configuration(
1665        &mut self,
1666        window: &mut Window,
1667        cx: &mut App,
1668    ) -> TextInputConfiguration {
1669        self.handler.text_input_configuration(window, cx)
1670    }
1671
1672    /// See [`InputHandler::text_input_editable_range`].
1673    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1674        self.cx
1675            .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1676            .ok()
1677            .flatten()
1678    }
1679}
1680
1681/// A struct representing a selection in a text buffer, in UTF16 characters.
1682/// This is different from a range because the head may be before the tail.
1683#[derive(Debug)]
1684pub struct UTF16Selection {
1685    /// The range of text in the document this selection corresponds to
1686    /// in UTF16 characters.
1687    pub range: Range<usize>,
1688    /// Whether the head of this selection is at the start (true), or end (false)
1689    /// of the range
1690    pub reversed: bool,
1691}
1692
1693/// Zed's interface for handling text input from the platform's IME system
1694/// This is currently a 1:1 exposure of the NSTextInputClient API:
1695///
1696/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1697pub trait InputHandler: 'static {
1698    /// Get the range of the user's currently selected text, if any
1699    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1700    ///
1701    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1702    fn selected_text_range(
1703        &mut self,
1704        ignore_disabled_input: bool,
1705        window: &mut Window,
1706        cx: &mut App,
1707    ) -> Option<UTF16Selection>;
1708
1709    /// Get the range of the currently marked text, if any
1710    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1711    ///
1712    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1713    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1714
1715    /// Get the text for the given document range in UTF-16 characters
1716    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1717    ///
1718    /// range_utf16 is in terms of UTF-16 characters
1719    fn text_for_range(
1720        &mut self,
1721        range_utf16: Range<usize>,
1722        adjusted_range: &mut Option<Range<usize>>,
1723        window: &mut Window,
1724        cx: &mut App,
1725    ) -> Option<String>;
1726
1727    /// Replace the text in the given document range with the given text
1728    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1729    ///
1730    /// replacement_range is in terms of UTF-16 characters
1731    fn replace_text_in_range(
1732        &mut self,
1733        replacement_range: Option<Range<usize>>,
1734        text: &str,
1735        window: &mut Window,
1736        cx: &mut App,
1737    );
1738
1739    /// Replace the text in the given document range with the given text,
1740    /// and mark the given text as part of an IME 'composing' state
1741    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1742    ///
1743    /// range_utf16 is in terms of UTF-16 characters
1744    /// new_selected_range is in terms of UTF-16 characters
1745    fn replace_and_mark_text_in_range(
1746        &mut self,
1747        range_utf16: Option<Range<usize>>,
1748        new_text: &str,
1749        new_selected_range: Option<Range<usize>>,
1750        window: &mut Window,
1751        cx: &mut App,
1752    );
1753
1754    /// Remove the IME 'composing' state from the document
1755    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1756    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1757
1758    /// Insert a platform-initiated paste at the current selection.
1759    ///
1760    /// Platforms that deliver paste as an input event rather than through an
1761    /// application-defined action (e.g. the DOM `paste` event on web) call
1762    /// this with the full clipboard contents. The default implementation
1763    /// inserts only the plain-text portion of the item.
1764    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1765        if let Some(text) = item.text() {
1766            self.replace_text_in_range(None, &text, window, cx);
1767        }
1768    }
1769
1770    /// Get the bounds of the given document range in screen coordinates
1771    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1772    ///
1773    /// This is used for positioning the IME candidate window
1774    fn bounds_for_range(
1775        &mut self,
1776        range_utf16: Range<usize>,
1777        window: &mut Window,
1778        cx: &mut App,
1779    ) -> Option<Bounds<Pixels>>;
1780
1781    /// Get the character offset for the given point in terms of UTF16 characters
1782    ///
1783    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1784    fn character_index_for_point(
1785        &mut self,
1786        point: Point<Pixels>,
1787        window: &mut Window,
1788        cx: &mut App,
1789    ) -> Option<usize>;
1790
1791    /// Set the range of the user's currently selected text.
1792    ///
1793    /// This is the reverse data-flow direction from [`Self::selected_text_range`]:
1794    /// platforms call it when the system text machinery moves the selection on the
1795    /// application's behalf — e.g. the user drags a system selection handle or
1796    /// invokes Select All from system UI (iOS `UITextInput setSelectedTextRange:`,
1797    /// Android `InputConnection.setSelection`).
1798    ///
1799    /// range_utf16 is in terms of UTF-16 characters, from 0 to the length of the document
1800    fn set_selected_text_range(
1801        &mut self,
1802        _range_utf16: Range<usize>,
1803        _window: &mut Window,
1804        _cx: &mut App,
1805    ) {
1806    }
1807
1808    /// Get the bounds of the focused text element in window coordinates, if known.
1809    ///
1810    /// This is the pull counterpart to the [`PlatformWindow::update_ime_position`]
1811    /// push: mobile platforms ask for the focused element's geometry when they
1812    /// need it (e.g. to frame system text-interaction UI overlaid on the focused
1813    /// element).
1814    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1815        None
1816    }
1817
1818    /// Get the length of the document in UTF-16 characters, if known.
1819    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1820        None
1821    }
1822
1823    /// Allows a given input context to opt into getting raw key repeats instead of
1824    /// sending these to the platform.
1825    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1826    /// (which is how iTerm does it) but it doesn't seem to work for me.
1827    #[allow(dead_code)]
1828    fn apple_press_and_hold_enabled(&mut self) -> bool {
1829        true
1830    }
1831
1832    /// Returns whether this handler is accepting text input to be inserted.
1833    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1834        true
1835    }
1836
1837    /// The contiguous range of text, in UTF-16 code units, that platform text
1838    /// input may read and edit around the current selection.
1839    ///
1840    /// Platforms that mirror document text into an IME-editable buffer clamp
1841    /// the mirrored window to this range, so multi-step IME edit gestures
1842    /// (word deletion, autocorrect rewrites, suggestion picks) cannot reach
1843    /// content outside it. The range should contain the current selection;
1844    /// when it cannot (a selection spanning a region boundary), platforms
1845    /// degrade the mirrored IME context rather than widening the range.
1846    /// `None` places no bound.
1847    fn text_input_editable_range(
1848        &mut self,
1849        _window: &mut Window,
1850        _cx: &mut App,
1851    ) -> Option<Range<usize>> {
1852        None
1853    }
1854
1855    /// Returns whether printable keys should be routed to the IME before keybinding
1856    /// matching when a non-ASCII input source (e.g. Japanese, Korean, Chinese IME)
1857    /// is active. This prevents multi-stroke keybindings like `jj` from intercepting
1858    /// keys that the IME should compose.
1859    ///
1860    /// Defaults to `false`. The editor overrides this based on whether it expects
1861    /// character input (e.g. Vim insert mode returns `true`, normal mode returns `false`).
1862    /// The terminal keeps the default `false` so that raw keys reach the terminal process.
1863    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1864        false
1865    }
1866
1867    /// Get this handler's preferences for platform text assistance.
1868    ///
1869    /// GPUI re-queries this every frame and forwards it to the platform window
1870    /// only when it changes, so implementations must be cheap and may vary the
1871    /// result with application state (e.g. with the cursor's position).
1872    fn text_input_configuration(
1873        &mut self,
1874        _window: &mut Window,
1875        _cx: &mut App,
1876    ) -> TextInputConfiguration {
1877        TextInputConfiguration::default()
1878    }
1879}
1880
1881/// Platform text-assistance preferences for the focused text region.
1882///
1883/// Returned by [`InputHandler::text_input_configuration`] and forwarded to the
1884/// platform whenever it changes; the platform maps the fields onto its native
1885/// input-session attributes (on web, DOM attributes of the hidden editable
1886/// element such as `autocorrect` and `enterkeyhint`).
1887///
1888/// The default disables all text assistance and requests no particular action
1889/// key presentation.
1890#[derive(Clone, Debug, Default, PartialEq, Eq)]
1891pub struct TextInputConfiguration {
1892    /// Whether the platform may automatically correct entered text.
1893    pub autocorrect: bool,
1894    /// How software keyboards automatically capitalize entered text.
1895    pub autocapitalize: Autocapitalize,
1896    /// Whether software keyboards may offer word suggestions and spellcheck.
1897    pub suggestions: bool,
1898    /// The action advertised on a software keyboard's confirm ("enter") key.
1899    pub input_action: TextInputAction,
1900}
1901
1902/// Automatic capitalization applied by software keyboards.
1903#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1904pub enum Autocapitalize {
1905    /// No automatic capitalization.
1906    #[default]
1907    None,
1908    /// Capitalize the first letter of each word.
1909    Words,
1910    /// Capitalize the first letter of each sentence.
1911    Sentences,
1912    /// Capitalize every letter.
1913    Characters,
1914}
1915
1916/// The action a software keyboard advertises on its confirm ("enter") key.
1917///
1918/// This affects only how the key is presented (icon or label); pressing it is
1919/// still delivered as ordinary input.
1920///
1921/// The variants are the HTML `enterkeyhint` attribute's value set
1922/// (<https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute>),
1923/// which also maps onto Android's `IME_ACTION_*` constants and iOS's
1924/// `UIReturnKeyType`; [`TextInputAction::Unspecified`] means "emit no hint".
1925#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1926pub enum TextInputAction {
1927    /// Let the platform choose its default presentation.
1928    #[default]
1929    Unspecified,
1930    /// Inserting a line break.
1931    Enter,
1932    /// Committing the field's value.
1933    Done,
1934    /// Navigating to the typed target.
1935    Go,
1936    /// Moving to the next field.
1937    Next,
1938    /// Moving to the previous field.
1939    Previous,
1940    /// Executing a search.
1941    Search,
1942    /// Sending a message.
1943    Send,
1944}
1945
1946/// The variables that can be configured when creating a new window
1947#[derive(Debug)]
1948pub struct WindowOptions {
1949    /// Specifies the state and bounds of the window in screen coordinates.
1950    /// - `None`: Inherit the bounds.
1951    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
1952    pub window_bounds: Option<WindowBounds>,
1953
1954    /// The titlebar configuration of the window
1955    pub titlebar: Option<TitlebarOptions>,
1956
1957    /// Whether the window should be focused when created
1958    pub focus: bool,
1959
1960    /// Whether the window should be shown when created
1961    pub show: bool,
1962
1963    /// The kind of window to create
1964    pub kind: WindowKind,
1965
1966    /// Whether the window can be moved by the user. When `false`, the user cannot drag
1967    /// the window (on macOS this sets `NSWindow.isMovable`, which also disables the
1968    /// Window-menu tiling items); programmatic moves are still allowed.
1969    pub is_movable: bool,
1970
1971    /// Whether the application owns dragging of the (custom) titlebar, rather than
1972    /// AppKit. Only has an effect on macOS.
1973    ///
1974    /// Set this to `true` for windows that draw their own titlebar and move the window
1975    /// themselves via [`Window::start_window_move`]. It marks the whole content view as
1976    /// app-owned titlebar content, so AppKit neither drags the window from the titlebar
1977    /// nor delays titlebar clicks while disambiguating double-clicks (a delay first
1978    /// observed on macOS 27). It is independent of `is_movable`, so such windows stay
1979    /// user-movable (via their own drag) and keep the Window-menu tiling items enabled.
1980    ///
1981    /// Leave this `false` for windows that rely on AppKit's native titlebar dragging.
1982    pub app_owns_titlebar_drag: bool,
1983
1984    /// The minimum interval between animation frames while the window is inactive.
1985    ///
1986    /// Set to `None` to disable inactive-window animation frame throttling.
1987    pub inactive_frame_interval: Option<Duration>,
1988
1989    /// Whether the window should be resizable by the user
1990    pub is_resizable: bool,
1991
1992    /// Whether the window should be minimized by the user
1993    pub is_minimizable: bool,
1994
1995    /// The display to create the window on, if this is None,
1996    /// the window will be created on the main display
1997    pub display_id: Option<DisplayId>,
1998
1999    /// The appearance of the window background.
2000    pub window_background: WindowBackgroundAppearance,
2001
2002    /// Application identifier of the window. Can by used by desktop environments to group applications together.
2003    pub app_id: Option<String>,
2004
2005    /// Window minimum size
2006    pub window_min_size: Option<Size<Pixels>>,
2007
2008    /// Whether to use client or server-side decorations on X11 and Wayland.
2009    /// The platform may ignore requests it cannot satisfy.
2010    pub window_decorations: Option<WindowDecorations>,
2011
2012    /// Icon image (X11 only)
2013    pub icon: Option<Arc<image::RgbaImage>>,
2014
2015    /// 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.
2016    pub tabbing_identifier: Option<String>,
2017}
2018
2019/// The variables that can be configured when creating a new window
2020#[derive(Debug)]
2021#[cfg_attr(
2022    all(
2023        any(target_os = "linux", target_os = "freebsd"),
2024        not(any(feature = "x11", feature = "wayland"))
2025    ),
2026    allow(dead_code)
2027)]
2028#[allow(missing_docs)]
2029pub struct WindowParams {
2030    pub bounds: Bounds<Pixels>,
2031
2032    /// The titlebar configuration of the window
2033    #[cfg_attr(feature = "wayland", allow(dead_code))]
2034    pub titlebar: Option<TitlebarOptions>,
2035
2036    /// The kind of window to create
2037    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2038    pub kind: WindowKind,
2039
2040    /// Whether the window should be movable by the user
2041    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2042    pub is_movable: bool,
2043
2044    /// Whether the application owns dragging of the (custom) titlebar (macOS only)
2045    #[cfg_attr(
2046        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2047        allow(dead_code)
2048    )]
2049    pub app_owns_titlebar_drag: bool,
2050
2051    /// Whether the window should be resizable by the user
2052    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2053    pub is_resizable: bool,
2054
2055    /// Whether the window should be minimized by the user
2056    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2057    pub is_minimizable: bool,
2058
2059    #[cfg_attr(
2060        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2061        allow(dead_code)
2062    )]
2063    pub focus: bool,
2064
2065    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2066    pub show: bool,
2067
2068    /// An image to set as the window icon (x11 only)
2069    #[cfg_attr(feature = "wayland", allow(dead_code))]
2070    pub icon: Option<Arc<image::RgbaImage>>,
2071
2072    #[cfg_attr(feature = "wayland", allow(dead_code))]
2073    pub display_id: Option<DisplayId>,
2074
2075    #[cfg_attr(feature = "wayland", allow(dead_code))]
2076    pub app_id: Option<String>,
2077
2078    pub window_min_size: Option<Size<Pixels>>,
2079
2080    #[cfg(target_os = "macos")]
2081    pub tabbing_identifier: Option<String>,
2082}
2083
2084/// Represents the status of how a window should be opened.
2085#[derive(Debug, Copy, Clone, PartialEq)]
2086pub enum WindowBounds {
2087    /// Indicates that the window should open in a windowed state with the given bounds.
2088    Windowed(Bounds<Pixels>),
2089    /// Indicates that the window should open in a maximized state.
2090    /// The bounds provided here represent the restore size of the window.
2091    Maximized(Bounds<Pixels>),
2092    /// Indicates that the window should open in fullscreen mode.
2093    /// The bounds provided here represent the restore size of the window.
2094    Fullscreen(Bounds<Pixels>),
2095}
2096
2097impl Default for WindowBounds {
2098    fn default() -> Self {
2099        WindowBounds::Windowed(Bounds::default())
2100    }
2101}
2102
2103impl WindowBounds {
2104    /// Retrieve the inner bounds
2105    pub fn get_bounds(&self) -> Bounds<Pixels> {
2106        match self {
2107            WindowBounds::Windowed(bounds) => *bounds,
2108            WindowBounds::Maximized(bounds) => *bounds,
2109            WindowBounds::Fullscreen(bounds) => *bounds,
2110        }
2111    }
2112
2113    /// Creates a new window bounds that centers the window on the screen.
2114    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2115        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2116    }
2117}
2118
2119impl Default for WindowOptions {
2120    fn default() -> Self {
2121        Self {
2122            window_bounds: None,
2123            titlebar: Some(TitlebarOptions {
2124                title: Default::default(),
2125                appears_transparent: Default::default(),
2126                traffic_light_position: Default::default(),
2127            }),
2128            focus: true,
2129            show: true,
2130            kind: WindowKind::Normal,
2131            is_movable: true,
2132            app_owns_titlebar_drag: false,
2133            inactive_frame_interval: Some(Duration::from_micros(33_333)),
2134            is_resizable: true,
2135            is_minimizable: true,
2136            display_id: None,
2137            window_background: WindowBackgroundAppearance::default(),
2138            icon: None,
2139            app_id: None,
2140            window_min_size: None,
2141            window_decorations: None,
2142            tabbing_identifier: None,
2143        }
2144    }
2145}
2146
2147/// The options that can be configured for a window's titlebar
2148#[derive(Debug, Default)]
2149pub struct TitlebarOptions {
2150    /// The initial title of the window
2151    pub title: Option<SharedString>,
2152
2153    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
2154    /// Refer to [`WindowOptions::window_decorations`] on Linux
2155    pub appears_transparent: bool,
2156
2157    /// The position of the macOS traffic light buttons
2158    pub traffic_light_position: Option<Point<Pixels>>,
2159}
2160
2161/// The kind of window to create
2162#[derive(Clone, Debug, PartialEq, Eq)]
2163pub enum WindowKind {
2164    /// A normal application window
2165    Normal,
2166
2167    /// A window that appears above all other windows, usually used for alerts or popups
2168    /// use sparingly!
2169    PopUp,
2170
2171    /// A parent-anchored, platform-native popup window for menus, comboboxes, context menus and
2172    /// tooltips. Unlike [`WindowKind::PopUp`], it is positioned relative to a parent window.
2173    ///
2174    /// The popup's size comes from [`WindowOptions::window_bounds`], whose origin is ignored.
2175    /// See [`popup::PopupOptions`] for the placement options. Platforms without a native
2176    /// implementation reject it with [`popup::PopupNotSupportedError`].
2177    AnchoredPopup(popup::PopupOptions),
2178
2179    /// A floating window that appears on top of its parent window
2180    Floating,
2181
2182    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
2183    /// docks, notifications or wallpapers.
2184    #[cfg(all(target_os = "linux", feature = "wayland"))]
2185    LayerShell(layer_shell::LayerShellOptions),
2186
2187    /// A window that appears on top of its parent window and blocks interaction with it
2188    /// until the modal window is closed
2189    Dialog,
2190}
2191
2192/// The appearance of the window, as defined by the operating system.
2193///
2194/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2195/// values.
2196#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2197pub enum WindowAppearance {
2198    /// A light appearance.
2199    ///
2200    /// On macOS, this corresponds to the `aqua` appearance.
2201    #[default]
2202    Light,
2203
2204    /// A light appearance with vibrant colors.
2205    ///
2206    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
2207    VibrantLight,
2208
2209    /// A dark appearance.
2210    ///
2211    /// On macOS, this corresponds to the `darkAqua` appearance.
2212    Dark,
2213
2214    /// A dark appearance with vibrant colors.
2215    ///
2216    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
2217    VibrantDark,
2218}
2219
2220/// The appearance of the background of the window itself, when there is
2221/// no content or the content is transparent.
2222#[derive(Copy, Clone, Debug, Default, PartialEq)]
2223pub enum WindowBackgroundAppearance {
2224    /// Opaque.
2225    ///
2226    /// This lets the window manager know that content behind this
2227    /// window does not need to be drawn.
2228    ///
2229    /// Actual color depends on the system and themes should define a fully
2230    /// opaque background color instead.
2231    #[default]
2232    Opaque,
2233    /// Plain alpha transparency.
2234    Transparent,
2235    /// Transparency, but the contents behind the window are blurred.
2236    ///
2237    /// Not always supported.
2238    Blurred,
2239    /// The Mica backdrop material, supported on Windows 11.
2240    MicaBackdrop,
2241    /// The Mica Alt backdrop material, supported on Windows 11.
2242    MicaAltBackdrop,
2243}
2244
2245/// The text rendering mode to use for drawing glyphs.
2246#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2247pub enum TextRenderingMode {
2248    /// Use the platform's default text rendering mode.
2249    #[default]
2250    PlatformDefault,
2251    /// Use subpixel (ClearType-style) text rendering.
2252    Subpixel,
2253    /// Use grayscale text rendering.
2254    Grayscale,
2255}
2256
2257/// The options that can be configured for a file dialog prompt
2258#[derive(Clone, Debug)]
2259pub struct PathPromptOptions {
2260    /// Should the prompt allow files to be selected?
2261    pub files: bool,
2262    /// Should the prompt allow directories to be selected?
2263    pub directories: bool,
2264    /// Should the prompt allow multiple files to be selected?
2265    pub multiple: bool,
2266    /// The prompt to show to a user when selecting a path
2267    pub prompt: Option<SharedString>,
2268}
2269
2270/// What kind of prompt styling to show
2271#[derive(Copy, Clone, Debug, PartialEq)]
2272pub enum PromptLevel {
2273    /// A prompt that is shown when the user should be notified of something
2274    Info,
2275
2276    /// A prompt that is shown when the user needs to be warned of a potential problem
2277    Warning,
2278
2279    /// A prompt that is shown when a critical problem has occurred
2280    Critical,
2281}
2282
2283/// Prompt Button
2284#[derive(Clone, Debug, PartialEq)]
2285pub enum PromptButton {
2286    /// Ok button
2287    Ok(SharedString),
2288    /// Cancel button
2289    Cancel(SharedString),
2290    /// Other button
2291    Other(SharedString),
2292}
2293
2294impl PromptButton {
2295    /// Create a button with label
2296    pub fn new(label: impl Into<SharedString>) -> Self {
2297        PromptButton::Other(label.into())
2298    }
2299
2300    /// Create an Ok button
2301    pub fn ok(label: impl Into<SharedString>) -> Self {
2302        PromptButton::Ok(label.into())
2303    }
2304
2305    /// Create a Cancel button
2306    pub fn cancel(label: impl Into<SharedString>) -> Self {
2307        PromptButton::Cancel(label.into())
2308    }
2309
2310    /// Returns true if this button is a cancel button.
2311    #[allow(dead_code)]
2312    pub fn is_cancel(&self) -> bool {
2313        matches!(self, PromptButton::Cancel(_))
2314    }
2315
2316    /// Returns the label of the button
2317    pub fn label(&self) -> &SharedString {
2318        match self {
2319            PromptButton::Ok(label) => label,
2320            PromptButton::Cancel(label) => label,
2321            PromptButton::Other(label) => label,
2322        }
2323    }
2324}
2325
2326impl From<&str> for PromptButton {
2327    fn from(value: &str) -> Self {
2328        match value.to_lowercase().as_str() {
2329            "ok" => PromptButton::Ok("OK".into()),
2330            "cancel" => PromptButton::Cancel("Cancel".into()),
2331            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2332        }
2333    }
2334}
2335
2336/// The style of the cursor (pointer)
2337#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2338pub enum CursorStyle {
2339    /// The default cursor
2340    #[default]
2341    Arrow,
2342
2343    /// A text input cursor
2344    /// corresponds to the CSS cursor value `text`
2345    IBeam,
2346
2347    /// A crosshair cursor
2348    /// corresponds to the CSS cursor value `crosshair`
2349    Crosshair,
2350
2351    /// A closed hand cursor
2352    /// corresponds to the CSS cursor value `grabbing`
2353    ClosedHand,
2354
2355    /// An open hand cursor
2356    /// corresponds to the CSS cursor value `grab`
2357    OpenHand,
2358
2359    /// A pointing hand cursor
2360    /// corresponds to the CSS cursor value `pointer`
2361    PointingHand,
2362
2363    /// A resize left cursor
2364    /// corresponds to the CSS cursor value `w-resize`
2365    ResizeLeft,
2366
2367    /// A resize right cursor
2368    /// corresponds to the CSS cursor value `e-resize`
2369    ResizeRight,
2370
2371    /// A resize cursor to the left and right
2372    /// corresponds to the CSS cursor value `ew-resize`
2373    ResizeLeftRight,
2374
2375    /// A resize up cursor
2376    /// corresponds to the CSS cursor value `n-resize`
2377    ResizeUp,
2378
2379    /// A resize down cursor
2380    /// corresponds to the CSS cursor value `s-resize`
2381    ResizeDown,
2382
2383    /// A resize cursor directing up and down
2384    /// corresponds to the CSS cursor value `ns-resize`
2385    ResizeUpDown,
2386
2387    /// A resize cursor directing up-left and down-right
2388    /// corresponds to the CSS cursor value `nesw-resize`
2389    ResizeUpLeftDownRight,
2390
2391    /// A resize cursor directing up-right and down-left
2392    /// corresponds to the CSS cursor value `nwse-resize`
2393    ResizeUpRightDownLeft,
2394
2395    /// A cursor indicating that the item/column can be resized horizontally.
2396    /// corresponds to the CSS cursor value `col-resize`
2397    ResizeColumn,
2398
2399    /// A cursor indicating that the item/row can be resized vertically.
2400    /// corresponds to the CSS cursor value `row-resize`
2401    ResizeRow,
2402
2403    /// A text input cursor for vertical layout
2404    /// corresponds to the CSS cursor value `vertical-text`
2405    IBeamCursorForVerticalLayout,
2406
2407    /// A cursor indicating that the operation is not allowed
2408    /// corresponds to the CSS cursor value `not-allowed`
2409    OperationNotAllowed,
2410
2411    /// A cursor indicating that the operation will result in a link
2412    /// corresponds to the CSS cursor value `alias`
2413    DragLink,
2414
2415    /// A cursor indicating that the operation will result in a copy
2416    /// corresponds to the CSS cursor value `copy`
2417    DragCopy,
2418
2419    /// A cursor indicating that the operation will result in a context menu
2420    /// corresponds to the CSS cursor value `context-menu`
2421    ContextualMenu,
2422}
2423
2424/// A clipboard item that should be copied to the clipboard
2425#[derive(Clone, Debug, Eq, PartialEq)]
2426pub struct ClipboardItem {
2427    /// The entries in this clipboard item.
2428    pub entries: Vec<ClipboardEntry>,
2429}
2430
2431/// An error produced by [`Platform::read_from_clipboard_async`].
2432///
2433/// Callers surface these failures to users, so the variants distinguish
2434/// conditions that call for different user-facing guidance.
2435#[derive(Clone, Debug, PartialEq, Eq)]
2436pub enum ClipboardReadError {
2437    /// The platform clipboard is not available in this context, e.g. the
2438    /// browser does not expose the async clipboard API or the page is not a
2439    /// secure context.
2440    Unavailable,
2441    /// The platform refused access, e.g. the user declined the browser's
2442    /// clipboard permission prompt or paste confirmation.
2443    Denied(String),
2444    /// The clipboard contents could not be converted into a
2445    /// [`ClipboardItem`].
2446    UnsupportedContent,
2447}
2448
2449impl std::fmt::Display for ClipboardReadError {
2450    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2451        match self {
2452            Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2453            Self::Denied(message) => {
2454                write!(formatter, "clipboard access was denied: {message}")
2455            }
2456            Self::UnsupportedContent => {
2457                formatter.write_str("the clipboard contents are unsupported")
2458            }
2459        }
2460    }
2461}
2462
2463impl std::error::Error for ClipboardReadError {}
2464
2465/// Either a ClipboardString or a ClipboardImage
2466#[derive(Clone, Debug, Eq, PartialEq)]
2467pub enum ClipboardEntry {
2468    /// A string entry
2469    String(ClipboardString),
2470    /// An image entry
2471    Image(Image),
2472    /// A file entry
2473    ExternalPaths(crate::ExternalPaths),
2474}
2475
2476impl ClipboardItem {
2477    /// Create a new ClipboardItem::String with no associated metadata
2478    pub fn new_string(text: String) -> Self {
2479        Self {
2480            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2481        }
2482    }
2483
2484    /// Create a new ClipboardItem::String with the given text and associated metadata
2485    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2486        Self {
2487            entries: vec![ClipboardEntry::String(ClipboardString {
2488                text,
2489                metadata: Some(metadata),
2490            })],
2491        }
2492    }
2493
2494    /// Create a new ClipboardItem::String with the given text and associated metadata
2495    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2496        Self {
2497            entries: vec![ClipboardEntry::String(
2498                ClipboardString::new(text).with_json_metadata(metadata),
2499            )],
2500        }
2501    }
2502
2503    /// Create a new ClipboardItem::Image with the given image with no associated metadata
2504    pub fn new_image(image: &Image) -> Self {
2505        Self {
2506            entries: vec![ClipboardEntry::Image(image.clone())],
2507        }
2508    }
2509
2510    /// Concatenates together all the ClipboardString entries in the item.
2511    /// Returns None if there were no ClipboardString entries.
2512    pub fn text(&self) -> Option<String> {
2513        let mut answer = String::new();
2514
2515        for entry in self.entries.iter() {
2516            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2517                answer.push_str(text);
2518            }
2519        }
2520
2521        if answer.is_empty() {
2522            for entry in self.entries.iter() {
2523                if let ClipboardEntry::ExternalPaths(paths) = entry {
2524                    for path in &paths.0 {
2525                        use std::fmt::Write as _;
2526                        _ = write!(answer, "{}", path.display());
2527                    }
2528                }
2529            }
2530        }
2531
2532        if !answer.is_empty() {
2533            Some(answer)
2534        } else {
2535            None
2536        }
2537    }
2538
2539    /// If this item is one ClipboardEntry::String, returns its metadata.
2540    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2541    pub fn metadata(&self) -> Option<&String> {
2542        match self.entries().first() {
2543            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2544                clipboard_string.metadata.as_ref()
2545            }
2546            _ => None,
2547        }
2548    }
2549
2550    /// Get the item's entries
2551    pub fn entries(&self) -> &[ClipboardEntry] {
2552        &self.entries
2553    }
2554
2555    /// Get owned versions of the item's entries
2556    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2557        self.entries.into_iter()
2558    }
2559}
2560
2561impl From<ClipboardString> for ClipboardEntry {
2562    fn from(value: ClipboardString) -> Self {
2563        Self::String(value)
2564    }
2565}
2566
2567impl From<String> for ClipboardEntry {
2568    fn from(value: String) -> Self {
2569        Self::from(ClipboardString::from(value))
2570    }
2571}
2572
2573impl From<Image> for ClipboardEntry {
2574    fn from(value: Image) -> Self {
2575        Self::Image(value)
2576    }
2577}
2578
2579impl From<ClipboardEntry> for ClipboardItem {
2580    fn from(value: ClipboardEntry) -> Self {
2581        Self {
2582            entries: vec![value],
2583        }
2584    }
2585}
2586
2587impl From<String> for ClipboardItem {
2588    fn from(value: String) -> Self {
2589        Self::from(ClipboardEntry::from(value))
2590    }
2591}
2592
2593impl From<Image> for ClipboardItem {
2594    fn from(value: Image) -> Self {
2595        Self::from(ClipboardEntry::from(value))
2596    }
2597}
2598
2599/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
2600#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2601pub enum ImageFormat {
2602    // Sorted from most to least likely to be pasted into an editor,
2603    // which matters when we iterate through them trying to see if
2604    // clipboard content matches them.
2605    /// .png
2606    Png,
2607    /// .jpeg or .jpg
2608    Jpeg,
2609    /// .webp
2610    Webp,
2611    /// .gif
2612    Gif,
2613    /// .svg
2614    Svg,
2615    /// .bmp
2616    Bmp,
2617    /// .tif or .tiff
2618    Tiff,
2619    /// .ico
2620    Ico,
2621    /// Netpbm image formats (.pbm, .ppm, .pgm).
2622    Pnm,
2623}
2624
2625impl ImageFormat {
2626    /// Returns the mime type for the ImageFormat
2627    pub const fn mime_type(self) -> &'static str {
2628        match self {
2629            ImageFormat::Png => "image/png",
2630            ImageFormat::Jpeg => "image/jpeg",
2631            ImageFormat::Webp => "image/webp",
2632            ImageFormat::Gif => "image/gif",
2633            ImageFormat::Svg => "image/svg+xml",
2634            ImageFormat::Bmp => "image/bmp",
2635            ImageFormat::Tiff => "image/tiff",
2636            ImageFormat::Ico => "image/ico",
2637            ImageFormat::Pnm => "image/x-portable-anymap",
2638        }
2639    }
2640
2641    /// Returns the file extension for this image format (without leading dot).
2642    pub const fn extension(self) -> &'static str {
2643        match self {
2644            ImageFormat::Png => "png",
2645            ImageFormat::Jpeg => "jpg",
2646            ImageFormat::Webp => "webp",
2647            ImageFormat::Gif => "gif",
2648            ImageFormat::Svg => "svg",
2649            ImageFormat::Bmp => "bmp",
2650            ImageFormat::Tiff => "tiff",
2651            ImageFormat::Ico => "ico",
2652            ImageFormat::Pnm => "pnm",
2653        }
2654    }
2655
2656    /// Returns the ImageFormat for the given mime type, including known aliases.
2657    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2658        use strum::IntoEnumIterator;
2659        Self::iter()
2660            .find(|format| format.mime_type() == mime_type)
2661            .or_else(|| Self::from_mime_type_alias(mime_type))
2662    }
2663
2664    /// Non-canonical mime types that some producers use in the wild.
2665    /// Unlike `mime_type()` which returns the single canonical form,
2666    /// these are legacy or shortened variants we still need to recognize.
2667    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2668        match mime_type {
2669            "image/jpg" => Some(Self::Jpeg),
2670            "image/tif" => Some(Self::Tiff),
2671            _ => None,
2672        }
2673    }
2674}
2675
2676/// An image, with a format and certain bytes
2677#[derive(Clone, Debug, PartialEq, Eq)]
2678pub struct Image {
2679    /// The image format the bytes represent (e.g. PNG)
2680    pub format: ImageFormat,
2681    /// The raw image bytes
2682    pub bytes: Vec<u8>,
2683    /// The unique ID for the image
2684    pub id: u64,
2685}
2686
2687pub(crate) fn decode_static_image(
2688    bytes: &[u8],
2689    format: image::ImageFormat,
2690) -> Result<SmallVec<[Frame; 1]>> {
2691    let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2692        .into_decoder()
2693        .context("creating image decoder")?;
2694    decode_static_image_from_decoder(decoder)
2695}
2696
2697pub(crate) fn decode_static_image_from_decoder(
2698    mut decoder: impl image::ImageDecoder,
2699) -> Result<SmallVec<[Frame; 1]>> {
2700    let orientation = decoder
2701        .orientation()
2702        .context("reading decoder's orientation")?;
2703    let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2704    image.apply_orientation(orientation);
2705
2706    let mut data = image.into_rgba8();
2707    for pixel in data.chunks_exact_mut(4) {
2708        pixel.swap(0, 2);
2709    }
2710
2711    Ok(SmallVec::from_elem(Frame::new(data), 1))
2712}
2713
2714impl Hash for Image {
2715    fn hash<H: Hasher>(&self, state: &mut H) {
2716        state.write_u64(self.id);
2717    }
2718}
2719
2720impl Image {
2721    /// An empty image containing no data
2722    pub fn empty() -> Self {
2723        Self::from_bytes(ImageFormat::Png, Vec::new())
2724    }
2725
2726    /// Create an image from a format and bytes
2727    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2728        Self {
2729            id: hash(&bytes),
2730            format,
2731            bytes,
2732        }
2733    }
2734
2735    /// Get this image's ID
2736    pub fn id(&self) -> u64 {
2737        self.id
2738    }
2739
2740    /// Use the GPUI `use_asset` API to make this image renderable
2741    pub fn use_render_image(
2742        self: Arc<Self>,
2743        window: &mut Window,
2744        cx: &mut App,
2745    ) -> Option<Arc<RenderImage>> {
2746        ImageSource::Image(self)
2747            .use_data(None, window, cx)
2748            .and_then(|result| result.ok())
2749    }
2750
2751    /// Use the GPUI `get_asset` API to make this image renderable
2752    pub fn get_render_image(
2753        self: Arc<Self>,
2754        window: &mut Window,
2755        cx: &mut App,
2756    ) -> Option<Arc<RenderImage>> {
2757        ImageSource::Image(self)
2758            .get_data(None, window, cx)
2759            .and_then(|result| result.ok())
2760    }
2761
2762    /// Use the GPUI `remove_asset` API to drop this image, if possible.
2763    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2764        ImageSource::Image(self).remove_asset(cx);
2765    }
2766
2767    /// Check whether this image is present in GPUI's asset cache (loading or
2768    /// loaded), without fetching it.
2769    #[cfg(any(test, feature = "test-support"))]
2770    pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2771        ImageSource::Image(self.clone()).is_asset_cached(cx)
2772    }
2773
2774    /// Convert the clipboard image to an `ImageData` object.
2775    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2776        let frames = match self.format {
2777            ImageFormat::Gif => {
2778                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2779                let mut frames = SmallVec::new();
2780
2781                for frame in decoder.into_frames() {
2782                    match frame {
2783                        Ok(mut frame) => {
2784                            // Convert from RGBA to BGRA.
2785                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2786                                pixel.swap(0, 2);
2787                            }
2788                            frames.push(frame);
2789                        }
2790                        Err(err) => {
2791                            log::debug!("Skipping GIF frame due to decode error: {err}");
2792                        }
2793                    }
2794                }
2795
2796                if frames.is_empty() {
2797                    anyhow::bail!("GIF could not be decoded: all frames failed");
2798                }
2799
2800                frames
2801            }
2802            ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2803            ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2804            ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2805            ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2806            ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2807            ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2808            ImageFormat::Svg => {
2809                return svg_renderer
2810                    .render_single_frame(&self.bytes, 1.0)
2811                    .map_err(Into::into);
2812            }
2813            ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2814        };
2815
2816        Ok(Arc::new(RenderImage::new(frames)))
2817    }
2818
2819    /// Get the format of the clipboard image
2820    pub fn format(&self) -> ImageFormat {
2821        self.format
2822    }
2823
2824    /// Get the raw bytes of the clipboard image
2825    pub fn bytes(&self) -> &[u8] {
2826        self.bytes.as_slice()
2827    }
2828}
2829
2830/// A clipboard item that should be copied to the clipboard
2831#[derive(Clone, Debug, Eq, PartialEq)]
2832pub struct ClipboardString {
2833    /// The text content.
2834    pub text: String,
2835    /// Optional metadata associated with this clipboard string.
2836    pub metadata: Option<String>,
2837}
2838
2839impl ClipboardString {
2840    /// Create a new clipboard string with the given text
2841    pub fn new(text: String) -> Self {
2842        Self {
2843            text,
2844            metadata: None,
2845        }
2846    }
2847
2848    /// Return a new clipboard item with the metadata replaced by the given metadata,
2849    /// after serializing it as JSON.
2850    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2851        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2852        self
2853    }
2854
2855    /// Get the text of the clipboard string
2856    pub fn text(&self) -> &String {
2857        &self.text
2858    }
2859
2860    /// Get the owned text of the clipboard string
2861    pub fn into_text(self) -> String {
2862        self.text
2863    }
2864
2865    /// Get the metadata of the clipboard string, formatted as JSON
2866    pub fn metadata_json<T>(&self) -> Option<T>
2867    where
2868        T: for<'a> Deserialize<'a>,
2869    {
2870        self.metadata
2871            .as_ref()
2872            .and_then(|m| serde_json::from_str(m).ok())
2873    }
2874
2875    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2876    /// Compute a hash of the given text for clipboard change detection.
2877    pub fn text_hash(text: &str) -> u64 {
2878        let mut hasher = SeaHasher::new();
2879        text.hash(&mut hasher);
2880        hasher.finish()
2881    }
2882}
2883
2884impl From<String> for ClipboardString {
2885    fn from(value: String) -> Self {
2886        Self {
2887            text: value,
2888            metadata: None,
2889        }
2890    }
2891}
2892
2893#[cfg(test)]
2894mod image_tests {
2895    use super::*;
2896    use std::sync::Arc;
2897
2898    #[test]
2899    fn test_image_to_image_data_applies_exif_orientation() {
2900        let image = Image::from_bytes(
2901            ImageFormat::Jpeg,
2902            include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2903        );
2904
2905        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2906
2907        assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2908
2909        let bytes = render_image.as_bytes(0).unwrap();
2910        assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2911        assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2912    }
2913
2914    #[test]
2915    fn test_svg_image_to_image_data_converts_to_bgra() {
2916        let image = Image::from_bytes(
2917            ImageFormat::Svg,
2918            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2919<rect width="1" height="1" fill="#38BDF8"/>
2920</svg>"##
2921                .to_vec(),
2922        );
2923
2924        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2925        let bytes = render_image.as_bytes(0).unwrap();
2926
2927        for pixel in bytes.chunks_exact(4) {
2928            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2929        }
2930    }
2931}
2932
2933#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2934mod tests {
2935    use super::*;
2936    use std::collections::HashSet;
2937
2938    #[test]
2939    fn test_window_button_layout_parse_standard() {
2940        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2941        assert_eq!(
2942            layout.left,
2943            [
2944                Some(WindowButton::Close),
2945                Some(WindowButton::Minimize),
2946                None
2947            ]
2948        );
2949        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2950    }
2951
2952    #[test]
2953    fn test_window_button_layout_parse_right_only() {
2954        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2955        assert_eq!(layout.left, [None, None, None]);
2956        assert_eq!(
2957            layout.right,
2958            [
2959                Some(WindowButton::Minimize),
2960                Some(WindowButton::Maximize),
2961                Some(WindowButton::Close)
2962            ]
2963        );
2964    }
2965
2966    #[test]
2967    fn test_window_button_layout_parse_left_only() {
2968        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2969        assert_eq!(
2970            layout.left,
2971            [
2972                Some(WindowButton::Close),
2973                Some(WindowButton::Minimize),
2974                Some(WindowButton::Maximize)
2975            ]
2976        );
2977        assert_eq!(layout.right, [None, None, None]);
2978    }
2979
2980    #[test]
2981    fn test_window_button_layout_parse_with_whitespace() {
2982        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2983        assert_eq!(
2984            layout.left,
2985            [
2986                Some(WindowButton::Close),
2987                Some(WindowButton::Minimize),
2988                None
2989            ]
2990        );
2991        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2992    }
2993
2994    #[test]
2995    fn test_window_button_layout_parse_empty() {
2996        let layout = WindowButtonLayout::parse("").unwrap();
2997        assert_eq!(layout.left, [None, None, None]);
2998        assert_eq!(layout.right, [None, None, None]);
2999    }
3000
3001    #[test]
3002    fn test_window_button_layout_parse_intentionally_empty() {
3003        let layout = WindowButtonLayout::parse(":").unwrap();
3004        assert_eq!(layout.left, [None, None, None]);
3005        assert_eq!(layout.right, [None, None, None]);
3006    }
3007
3008    #[test]
3009    fn test_window_button_layout_parse_invalid_buttons() {
3010        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3011        assert_eq!(
3012            layout.left,
3013            [
3014                Some(WindowButton::Close),
3015                Some(WindowButton::Minimize),
3016                None
3017            ]
3018        );
3019        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3020    }
3021
3022    #[test]
3023    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3024        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3025        assert_eq!(
3026            layout.right,
3027            [
3028                Some(WindowButton::Close),
3029                Some(WindowButton::Minimize),
3030                None
3031            ]
3032        );
3033        assert_eq!(layout.format(), ":close,minimize");
3034    }
3035
3036    #[test]
3037    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3038        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3039        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3040        assert_eq!(
3041            layout.right,
3042            [
3043                Some(WindowButton::Maximize),
3044                Some(WindowButton::Minimize),
3045                None
3046            ]
3047        );
3048
3049        let button_ids: Vec<_> = layout
3050            .left
3051            .iter()
3052            .chain(layout.right.iter())
3053            .flatten()
3054            .map(WindowButton::id)
3055            .collect();
3056        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3057        assert_eq!(unique_button_ids.len(), button_ids.len());
3058        assert_eq!(layout.format(), "close:maximize,minimize");
3059    }
3060
3061    #[test]
3062    fn test_window_button_layout_parse_gnome_style() {
3063        let layout = WindowButtonLayout::parse("close").unwrap();
3064        assert_eq!(layout.left, [None, None, None]);
3065        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3066    }
3067
3068    #[test]
3069    fn test_window_button_layout_parse_elementary_style() {
3070        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3071        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3072        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3073    }
3074
3075    #[test]
3076    fn test_window_button_layout_round_trip() {
3077        let cases = [
3078            "close:minimize,maximize",
3079            "minimize,maximize,close:",
3080            ":close",
3081            "close:",
3082            "close:maximize",
3083            ":",
3084        ];
3085
3086        for case in cases {
3087            let layout = WindowButtonLayout::parse(case).unwrap();
3088            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3089        }
3090    }
3091
3092    #[test]
3093    fn test_window_button_layout_linux_default() {
3094        let layout = WindowButtonLayout::linux_default();
3095        assert_eq!(layout.left, [None, None, None]);
3096        assert_eq!(
3097            layout.right,
3098            [
3099                Some(WindowButton::Minimize),
3100                Some(WindowButton::Maximize),
3101                Some(WindowButton::Close)
3102            ]
3103        );
3104
3105        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3106        assert_eq!(round_tripped, layout);
3107    }
3108
3109    #[test]
3110    fn test_window_button_layout_parse_all_invalid() {
3111        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3112    }
3113}