Skip to main content

incular_platform/
lib.rs

1//! Platform-owned window data and normalized native events.
2//!
3//! Layout stays in logical pixels. This crate is the single conversion boundary
4//! between those coordinates and physical surface/window coordinates.
5
6use directories::ProjectDirs;
7use incular_config::ApplicationDefaults;
8use incular_core::{Color, InputEvent, Offset, Rect, Size};
9use std::{fmt, path::PathBuf};
10
11mod application_activation;
12mod application_shell;
13mod capabilities;
14mod content_sensitivity;
15mod data_transfer;
16mod display;
17mod file_dialog;
18mod operation;
19mod pointer;
20mod system_environment;
21mod window_control;
22
23pub use application_activation::{
24    ApplicationActivation, GlobalShortcutChord, GlobalShortcutChordError, GlobalShortcutError,
25    GlobalShortcutId, LaunchActivation, SingleInstancePolicy, SingleInstancePolicyError,
26    UrlActivation,
27};
28pub use application_shell::{
29    ApplicationBadge, ApplicationShellError, ApplicationShellFeature, NotificationAction,
30    NotificationActionId, NotificationId, NotificationPresentation, TaskbarDockState,
31    TaskbarProgress, TaskbarProgressError, TrayItemId, TrayItemPresentation,
32};
33pub use capabilities::{
34    AdvancedInputCapabilities, ApplicationServiceCapabilities, CapabilitySupport,
35    DataTransferCapabilities, DisplayPlacementCapabilities, NativeMenuCapabilities,
36    PlatformCapabilities, TransientSurfaceCapabilities, WindowControlCapabilities,
37};
38pub use content_sensitivity::{
39    ContentSensitivityBackend, ContentSensitivityCapability, ContentSensitivityNoOpReason,
40    ContentSensitivityOutcome, MemoryContentSensitivityBackend, NoopContentSensitivityBackend,
41};
42pub use data_transfer::{
43    Clipboard, ClipboardCapabilities, ClipboardError, ClipboardWriteReport, DataTransfer,
44    ExternalDragEvent, ExternalDragPhase, ExternalDragResponse, MediaType, MemoryClipboard,
45    TransferData, TransferDataError, TransferFormat, TransferFormatCapabilities, TransferImage,
46    TransferItem, TransferOperation, TransferOperations, TransferReadRequest,
47    TransferRepresentation,
48};
49pub use display::{
50    DisplayId, DisplayPlacementArea, DisplaySnapshot, LogicalDisplayPosition,
51    LogicalScreenPosition, LogicalScreenRect, PhysicalDisplayPosition, PhysicalScreenPosition,
52    PhysicalScreenRect,
53};
54pub use file_dialog::{
55    DocumentActivation, DocumentDescriptor, FileContentType, FileContentTypeKind,
56    FileDialogCapabilities, FileDialogError, FileDialogFilter, FileDialogFilterId, FileDialogKind,
57    FileDialogOption, FileDialogOptions, FileDialogOutcome, FileDialogRequest, FileDialogRequestId,
58    FileDialogSelection, FileDialogValidationError, FileExtension,
59};
60pub use incular_config::{TransparencyMode, WindowSizePolicy};
61pub use operation::{
62    NativeOperationCompletion, NativeRequestId, PlatformOperationError, PlatformOperationErrorKind,
63    PlatformOperationResult,
64};
65pub use pointer::{
66    CursorGrabMode, LogicalWindowPosition, LogicalWindowPositionError, NativePointerSample,
67    PointerMetadata,
68};
69pub use system_environment::{
70    MemorySystemEnvironmentProvider, SystemEnvironmentPreferences, SystemEnvironmentProvider,
71    canonicalize_system_locales,
72};
73pub use window_control::{
74    LogicalSizeLimits, LogicalSizeLimitsError, UserAttentionType, WindowIcon, WindowIconError,
75    WindowLevel, WindowObservedState, WindowRequestedState,
76};
77
78/// Resolves the persistent, local application-data directory through the
79/// operating system's standard project-directory conventions.
80///
81/// The caller supplies a stable application identifier (normally a reverse
82/// domain ID).  Incular intentionally never derives it from a window title,
83/// executable path, current directory, or transient window ID.  Restoration
84/// snapshots belong in local data rather than the regenerable cache directory.
85#[must_use]
86pub fn application_data_local_directory(application_id: &str) -> Option<PathBuf> {
87    (!application_id.trim().is_empty())
88        .then(|| ProjectDirs::from("org", "Incular", application_id))
89        .flatten()
90        .map(|directories| directories.data_local_dir().to_path_buf())
91}
92
93/// Stable identity for an Incular window.
94///
95/// A [`WindowId`] is deliberately unrelated to a native window handle. Window
96/// registries allocate an index and advance its generation whenever that slot
97/// is reused, so a command retained for a closed window cannot affect the next
98/// window occupying the same slot.
99#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
100pub struct WindowId {
101    index: u32,
102    generation: u32,
103}
104
105impl WindowId {
106    /// Creates an ID from a registry slot and its current generation.
107    ///
108    /// This is public for platform/runtime registries, not for representing a
109    /// native ID. Application code normally obtains IDs from `WindowHandle`.
110    #[must_use]
111    pub const fn from_parts(index: u32, generation: u32) -> Self {
112        Self { index, generation }
113    }
114
115    /// Returns the stable slot index selected by the owning window registry.
116    #[must_use]
117    pub const fn index(self) -> u32 {
118        self.index
119    }
120
121    /// Returns the slot generation used to reject stale handles and commands.
122    #[must_use]
123    pub const fn generation(self) -> u32 {
124        self.generation
125    }
126}
127
128impl fmt::Debug for WindowId {
129    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(formatter, "WindowId({}, {})", self.index, self.generation)
131    }
132}
133
134impl fmt::Display for WindowId {
135    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(formatter, "Window#{}@{}", self.index + 1, self.generation)
137    }
138}
139
140/// The portable subset of fullscreen behaviour supported by Incular.
141///
142/// Borderless fullscreen intentionally avoids exposing platform monitor/video
143/// mode handles. Native adapters may use the monitor containing the window.
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum Fullscreen {
146    Borderless,
147}
148
149/// Configuration used when creating an Incular desktop window.
150///
151/// All sizes are logical pixels. A transparent window is a strict presentation
152/// request: a backend must either configure compatible compositor alpha or
153/// report that the request is unsupported rather than silently presenting an
154/// opaque/black surface.
155#[derive(Clone, Debug, PartialEq)]
156pub struct WindowOptions {
157    pub title: String,
158    pub initial_logical_size: Size,
159    /// Selects whether native viewport metrics or retained content own size.
160    ///
161    /// In [`WindowSizePolicy::Content`] mode `initial_logical_size` is also the
162    /// minimum content viewport. The framework follows the retained root's
163    /// layout size and can shrink back when primary content becomes smaller.
164    /// Paint-only overflow such as shadows and transient overlays does not
165    /// resize the native host. This is deliberately independent of window
166    /// decorations.
167    pub size_policy: WindowSizePolicy,
168    pub minimum_logical_size: Option<Size>,
169    pub maximum_logical_size: Option<Size>,
170    pub resizable: bool,
171    pub visible: bool,
172    pub decorations: bool,
173    pub transparency_mode: TransparencyMode,
174    /// Base scene color. This does not change native compositor transparency.
175    pub background_color: Color,
176    pub maximized: bool,
177    pub fullscreen: Option<Fullscreen>,
178    pub window_level: WindowLevel,
179    pub window_icon: Option<WindowIcon>,
180}
181
182impl Default for WindowOptions {
183    fn default() -> Self {
184        let defaults = ApplicationDefaults::DEFAULT;
185        Self {
186            title: defaults.window_title.to_owned(),
187            initial_logical_size: defaults.initial_window_size,
188            size_policy: defaults.window_size_policy,
189            minimum_logical_size: None,
190            maximum_logical_size: None,
191            resizable: defaults.resizable,
192            visible: defaults.visible,
193            decorations: defaults.decorations,
194            transparency_mode: defaults.transparency_mode,
195            background_color: defaults.background_color,
196            maximized: defaults.maximized,
197            fullscreen: None,
198            window_level: WindowLevel::Normal,
199            window_icon: None,
200        }
201    }
202}
203
204impl WindowOptions {
205    /// Creates options with the documented desktop defaults and a chosen title.
206    #[must_use]
207    pub fn new(title: impl Into<String>) -> Self {
208        Self {
209            title: title.into(),
210            ..Self::default()
211        }
212    }
213
214    /// Checks size constraints before a native adapter attempts window creation.
215    pub fn validate(&self) -> Result<(), WindowOptionsError> {
216        if !is_positive_size(self.initial_logical_size) {
217            return Err(WindowOptionsError::InvalidInitialSize);
218        }
219        if self
220            .minimum_logical_size
221            .is_some_and(|size| !is_positive_size(size))
222        {
223            return Err(WindowOptionsError::InvalidMinimumSize);
224        }
225        if self
226            .maximum_logical_size
227            .is_some_and(|size| !is_positive_size(size))
228        {
229            return Err(WindowOptionsError::InvalidMaximumSize);
230        }
231        if let (Some(minimum), Some(maximum)) =
232            (self.minimum_logical_size, self.maximum_logical_size)
233            && (minimum.width > maximum.width || minimum.height > maximum.height)
234        {
235            return Err(WindowOptionsError::MinimumExceedsMaximum);
236        }
237        if self
238            .minimum_logical_size
239            .is_some_and(|minimum| !size_is_at_least(self.initial_logical_size, minimum))
240        {
241            return Err(WindowOptionsError::InitialSizeBelowMinimum);
242        }
243        if self
244            .maximum_logical_size
245            .is_some_and(|maximum| !size_is_at_least(maximum, self.initial_logical_size))
246        {
247            return Err(WindowOptionsError::InitialSizeAboveMaximum);
248        }
249        Ok(())
250    }
251
252    pub fn requested_state(&self) -> Result<WindowRequestedState, LogicalSizeLimitsError> {
253        let size_limits =
254            LogicalSizeLimits::new(self.minimum_logical_size, self.maximum_logical_size)?;
255        Ok(WindowRequestedState {
256            visible: self.visible,
257            minimized: false,
258            maximized: self.maximized,
259            fullscreen: self.fullscreen,
260            resizable: self.resizable,
261            decorations: self.decorations,
262            size_limits,
263            level: self.window_level,
264        })
265    }
266}
267
268fn is_positive_size(size: Size) -> bool {
269    size.width.is_finite() && size.height.is_finite() && size.width > 0.0 && size.height > 0.0
270}
271
272fn size_is_at_least(left: Size, right: Size) -> bool {
273    left.width >= right.width && left.height >= right.height
274}
275
276/// A contradiction in portable window-creation constraints.
277#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
278pub enum WindowOptionsError {
279    #[error("initial logical size must be finite and positive")]
280    InvalidInitialSize,
281    #[error("minimum logical size must be finite and positive")]
282    InvalidMinimumSize,
283    #[error("maximum logical size must be finite and positive")]
284    InvalidMaximumSize,
285    #[error("minimum logical size cannot exceed maximum logical size")]
286    MinimumExceedsMaximum,
287    #[error("initial logical size cannot be smaller than the minimum logical size")]
288    InitialSizeBelowMinimum,
289    #[error("initial logical size cannot be larger than the maximum logical size")]
290    InitialSizeAboveMaximum,
291}
292
293/// A UI-thread operation requested for one Incular window.
294///
295/// Commands are data only: Tokio workers can enqueue them through the runtime
296/// UI dispatcher, while native adapters apply them from their event-loop turn.
297#[derive(Clone, Debug, PartialEq)]
298pub struct WindowCommand {
299    pub window_id: WindowId,
300    pub operation: WindowOperation,
301    pub request_id: Option<NativeRequestId>,
302}
303
304impl WindowCommand {
305    #[must_use]
306    pub const fn new(window_id: WindowId, operation: WindowOperation) -> Self {
307        Self {
308            window_id,
309            operation,
310            request_id: None,
311        }
312    }
313
314    /// Associates this command with one result-bearing runtime request.
315    #[must_use]
316    pub const fn with_request(
317        window_id: WindowId,
318        request_id: NativeRequestId,
319        operation: WindowOperation,
320    ) -> Self {
321        Self {
322            window_id,
323            operation,
324            request_id: Some(request_id),
325        }
326    }
327}
328
329/// Operations available through an Incular `WindowHandle`.
330///
331/// `Close` is an accepted application close operation. A native close gesture
332/// is reported first as [`PlatformEvent::CloseRequested`] in a [`WindowEvent`]
333/// so applications can accept or cancel it.
334#[derive(Clone, Debug, PartialEq)]
335pub enum WindowOperation {
336    SetTitle(String),
337    SetVisible(bool),
338    SetLogicalSize(Size),
339    SetOuterPosition(PhysicalScreenPosition),
340    BeginMoveDrag,
341    BeginResizeDrag(incular_core::WindowResizeDirection),
342    SetMinimized(bool),
343    SetMaximized(bool),
344    SetFullscreen(Option<Fullscreen>),
345    SetResizable(bool),
346    SetDecorations(bool),
347    SetLogicalSizeLimits(LogicalSizeLimits),
348    SetWindowLevel(WindowLevel),
349    SetWindowIcon(Option<WindowIcon>),
350    RequestUserAttention(Option<UserAttentionType>),
351    SetCursorGrab(CursorGrabMode),
352    SetCursorVisible(bool),
353    SetCursorPosition(LogicalWindowPosition),
354    /// Applies the retained tree's effective capture-protection policy.
355    SetContentSensitivity(incular_config::ContentSensitivity),
356    RequestFocus,
357    RequestRedraw,
358    Close,
359}
360
361/// Native lifecycle state for one window, independent of application lifecycle.
362#[derive(Clone, Copy, Debug, PartialEq, Eq)]
363pub enum WindowLifecycle {
364    Creating,
365    Visible,
366    Hidden,
367    Focused,
368    Unfocused,
369    Closing,
370    Closed,
371}
372
373/// A native event routed to one normalized Incular window.
374///
375/// Existing [`PlatformEvent`] values remain unchanged and are carried by the
376/// `Platform` variant. This lets a single-window runtime keep using
377/// `PlatformEvent` while a multi-window runtime associates every input,
378/// metrics update, and close request with a [`WindowId`].
379#[derive(Clone, Debug, PartialEq)]
380pub struct WindowEvent {
381    pub window_id: WindowId,
382    pub kind: WindowEventKind,
383}
384
385impl WindowEvent {
386    #[must_use]
387    pub const fn new(window_id: WindowId, kind: WindowEventKind) -> Self {
388        Self { window_id, kind }
389    }
390
391    #[must_use]
392    pub fn platform(window_id: WindowId, event: PlatformEvent) -> Self {
393        Self::new(window_id, WindowEventKind::Platform(event))
394    }
395
396    #[must_use]
397    pub const fn lifecycle(window_id: WindowId, lifecycle: WindowLifecycle) -> Self {
398        Self::new(window_id, WindowEventKind::Lifecycle(lifecycle))
399    }
400
401    #[must_use]
402    pub const fn redraw_requested(window_id: WindowId) -> Self {
403        Self::new(window_id, WindowEventKind::RedrawRequested)
404    }
405
406    #[must_use]
407    pub const fn state_changed(window_id: WindowId, state: WindowObservedState) -> Self {
408        Self::new(window_id, WindowEventKind::StateChanged(state))
409    }
410
411    /// Returns the wrapped legacy event when this is a platform event.
412    #[must_use]
413    pub fn platform_event(&self) -> Option<&PlatformEvent> {
414        match &self.kind {
415            WindowEventKind::Platform(event) => Some(event),
416            WindowEventKind::Lifecycle(_)
417            | WindowEventKind::RedrawRequested
418            | WindowEventKind::StateChanged(_) => None,
419        }
420    }
421}
422
423/// Window event data that is not part of the legacy [`PlatformEvent`] stream.
424#[derive(Clone, Debug, PartialEq)]
425pub enum WindowEventKind {
426    /// Existing input, metrics, application lifecycle, or close-request data.
427    Platform(PlatformEvent),
428    /// A state transition of the native window itself.
429    Lifecycle(WindowLifecycle),
430    /// The native surface is ready for the target window to present a frame.
431    RedrawRequested,
432    /// Snapshot of native state that the active backend can actually observe.
433    StateChanged(WindowObservedState),
434}
435
436#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
437pub struct PhysicalSize {
438    pub width: u32,
439    pub height: u32,
440}
441impl PhysicalSize {
442    #[must_use]
443    pub const fn new(width: u32, height: u32) -> Self {
444        Self { width, height }
445    }
446    #[must_use]
447    pub const fn is_zero(self) -> bool {
448        self.width == 0 || self.height == 0
449    }
450}
451#[derive(Clone, Copy, Debug, PartialEq)]
452pub struct WindowMetrics {
453    pub physical_size: PhysicalSize,
454    pub scale_factor: f64,
455}
456impl WindowMetrics {
457    #[must_use]
458    pub fn new(physical_size: PhysicalSize, scale_factor: f64) -> Self {
459        assert!(
460            scale_factor.is_finite() && scale_factor > 0.0,
461            "scale factor must be positive"
462        );
463        Self {
464            physical_size,
465            scale_factor,
466        }
467    }
468    #[must_use]
469    pub fn logical_size(self) -> Size {
470        Size::new(
471            self.physical_size.width as f32 / self.scale_factor as f32,
472            self.physical_size.height as f32 / self.scale_factor as f32,
473        )
474    }
475    #[must_use]
476    pub fn logical_to_physical(self, offset: Offset) -> Offset {
477        Offset::new(
478            offset.x * self.scale_factor as f32,
479            offset.y * self.scale_factor as f32,
480        )
481    }
482    #[must_use]
483    pub fn physical_to_logical(self, offset: Offset) -> Offset {
484        Offset::new(
485            offset.x / self.scale_factor as f32,
486            offset.y / self.scale_factor as f32,
487        )
488    }
489}
490
491/// Stable identity for the text-input client currently attached to a window.
492/// It is generated by the retained tree and is independent of native handles.
493#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
494pub struct TextInputClientId(pub u64);
495
496impl TextInputClientId {
497    #[must_use]
498    pub const fn new(value: u64) -> Self {
499        Self(value)
500    }
501
502    #[must_use]
503    pub const fn get(self) -> u64 {
504        self.0
505    }
506}
507
508/// Hints supplied to a native IME or soft-keyboard adapter.
509#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
510pub enum TextInputType {
511    #[default]
512    Text,
513    Multiline,
514    Number,
515    Phone,
516    Email,
517    Url,
518    Password,
519}
520
521#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
522pub enum TextInputAction {
523    #[default]
524    Unspecified,
525    None,
526    Done,
527    Go,
528    Search,
529    Send,
530    Next,
531    Previous,
532    Newline,
533}
534
535/// Flutter's `TextCapitalization`: the casing a soft keyboard applies
536/// automatically.
537#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
538pub enum TextCapitalization {
539    /// Every word starts with a capital letter.
540    Words,
541    /// Every sentence starts with a capital letter.
542    Sentences,
543    /// Every character is capitalized.
544    Characters,
545    /// The keyboard does not change the case.
546    #[default]
547    None,
548}
549
550/// The keyboard hints of Flutter's `TextInputConfiguration` beyond the
551/// input type and action. The defaults are Flutter's.
552#[derive(Clone, Debug, PartialEq, Eq)]
553pub struct TextInputTraits {
554    /// Whether the platform corrects spelling as the user types.
555    pub autocorrect: bool,
556    /// Whether the keyboard shows suggestions.
557    pub enable_suggestions: bool,
558    /// Whether the user may move the selection and use the clipboard.
559    pub enable_interactive_selection: bool,
560    pub text_capitalization: TextCapitalization,
561    /// The keyboard colour scheme; iOS only.
562    pub keyboard_appearance: incular_config::Brightness,
563    /// Flutter `AutofillHints` names. `None` disables autofill for the
564    /// field (`AutofillConfiguration.disabled`); an empty list enables it
565    /// without hints.
566    pub autofill_hints: Option<Vec<String>>,
567    /// Whether the IME may learn from what the user types (Android's
568    /// `IME_FLAG_NO_PERSONALIZED_LEARNING` when false).
569    pub enable_ime_personalized_learning: bool,
570}
571
572impl Default for TextInputTraits {
573    fn default() -> Self {
574        Self {
575            autocorrect: true,
576            enable_suggestions: true,
577            enable_interactive_selection: true,
578            text_capitalization: TextCapitalization::None,
579            keyboard_appearance: incular_config::Brightness::Light,
580            autofill_hints: None,
581            enable_ime_personalized_learning: true,
582        }
583    }
584}
585
586#[derive(Clone, Debug, PartialEq, Eq)]
587pub struct TextInputConfiguration {
588    pub client: TextInputClientId,
589    pub input_type: TextInputType,
590    pub action: TextInputAction,
591    pub multiline: bool,
592    pub enabled: bool,
593    pub read_only: bool,
594    pub obscure_text: bool,
595    pub traits: TextInputTraits,
596}
597
598#[derive(Clone, Debug, Default, PartialEq, Eq)]
599pub struct TextInputState {
600    pub text: String,
601    pub selection_start: usize,
602    pub selection_end: usize,
603    pub composing: Option<(usize, usize)>,
604}
605
606/// Commands emitted by the runtime and consumed by a native text-input
607/// adapter. They contain no window handles and can be queued safely at the
608/// runtime boundary.
609#[derive(Clone, Debug, PartialEq)]
610pub enum TextInputCommand {
611    SetClient {
612        configuration: TextInputConfiguration,
613        state: TextInputState,
614        caret_rect: Rect,
615    },
616    Update {
617        client: TextInputClientId,
618        state: TextInputState,
619    },
620    SetCaretRect {
621        client: TextInputClientId,
622        rect: Rect,
623    },
624    Hide {
625        client: TextInputClientId,
626    },
627    Clear {
628        client: TextInputClientId,
629    },
630}
631
632/// Minimal adapter seam for embedders that own their own text-input bridge.
633pub trait TextInputAdapter {
634    fn apply(&mut self, command: &TextInputCommand);
635}
636
637/// In-memory adapter useful for tests and headless hosts.
638#[derive(Clone, Debug, Default, PartialEq)]
639pub struct MemoryTextInputAdapter {
640    commands: Vec<TextInputCommand>,
641}
642
643impl MemoryTextInputAdapter {
644    #[must_use]
645    pub fn commands(&self) -> &[TextInputCommand] {
646        &self.commands
647    }
648
649    pub fn clear(&mut self) {
650        self.commands.clear();
651    }
652}
653
654impl TextInputAdapter for MemoryTextInputAdapter {
655    fn apply(&mut self, command: &TextInputCommand) {
656        self.commands.push(command.clone());
657    }
658}
659
660#[derive(Clone, Debug, PartialEq)]
661pub enum PlatformEvent {
662    Input(InputEvent),
663    /// Action chosen by a native software keyboard (for example Done, Next,
664    /// or Search). Desktop backends normally express the same behavior as a
665    /// physical Enter key, while mobile hosts can forward the IME action
666    /// without manufacturing a keyboard event.
667    TextInputAction(TextInputAction),
668    Metrics(WindowMetrics),
669    /// Complete normalized environment replacement produced by the native
670    /// shell. Runtime dependency tracking decides which consumers invalidate.
671    Environment(incular_config::RuntimeEnvironment),
672    Lifecycle(PlatformLifecycle),
673    ExternalDrag(ExternalDragEvent),
674    CloseRequested,
675}
676
677/// Lifecycle states native adapters can report without depending on the
678/// runtime crate. Adapters may emit only the states their OS exposes.
679#[derive(Clone, Copy, Debug, PartialEq, Eq)]
680pub enum PlatformLifecycle {
681    Active,
682    Inactive,
683    Suspended,
684    /// The OS reports that a previously suspended application is runnable
685    /// again. Runtime maps this to `ApplicationLifecycle::Active` while keeping
686    /// the native transition explicit at the platform boundary.
687    Resumed,
688    Stopping,
689}
690/// Native desktop window-system family for capability refinement.
691///
692/// This is a backend diagnostic/capability value, not a public native handle.
693#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
694pub enum NativeWindowSystem {
695    Win32,
696    AppKit,
697    X11,
698    Wayland,
699    /// A canvas element in a browser page (`wasm32` web host).
700    Web,
701    Other,
702}