// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. pub use smithay_client_toolkit::seat::keyboard::Keysym; pub use calloop::channel::Sender as ChannelSender; use crate::widget::Element; /// Wayland `ext-foreign-toplevel-list-v1` event delivered to apps via /// [`App::on_toplevel_event`]. `Opened` fires after the compositor /// commits the first `done` for a new handle (so `app_id` is the value /// in effect at that point — the protocol allows the compositor to /// re-commit later, but most don't). `Closed` fires when the /// compositor sends `closed` and the runtime is about to destroy the /// handle proxy. Both carry the same `id`, the Wayland protocol id of /// the handle, unique within the session and stable for the handle's /// lifetime. #[ derive( Debug, Clone ) ] pub enum ToplevelEvent { Opened { id: u32, app_id: String }, Closed { id: u32 }, } /// Wayland shell mode for the application surface. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ShellMode { /// Normal application window using xdg-shell protocol. /// This is the default for regular applications. Window, /// Layer-shell surface at the specified layer. /// Used for shell components like panels, backgrounds, overlays. Layer( Layer ), /// `ext-session-lock-v1` lock surface. The compositor blanks the outputs /// and shows only this surface until the app requests exit (see /// [`App::requested_exit`]). Used by the screen locker. SessionLock, } /// Layer-shell layer position. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Layer { /// Below normal windows (wallpapers, desktop backgrounds). Background, /// Below normal windows but above background. Bottom, /// Above normal windows (panels, docks). Top, /// Above everything (notifications, on-screen displays). Overlay, } impl Layer { /// Convert to smithay's wlr-layer Layer type. pub( crate ) fn to_wlr_layer( self ) -> smithay_client_toolkit::shell::wlr_layer::Layer { use smithay_client_toolkit::shell::wlr_layer::Layer as WlrLayer; match self { Layer::Background => WlrLayer::Background, Layer::Bottom => WlrLayer::Bottom, Layer::Top => WlrLayer::Top, Layer::Overlay => WlrLayer::Overlay, } } } /// Layer-shell anchor edges. /// Determines which screen edges the surface is attached to. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Anchor { pub top: bool, pub bottom: bool, pub left: bool, pub right: bool, } impl Anchor { /// Anchor to all edges (fill screen). pub const ALL: Self = Self { top: true, bottom: true, left: true, right: true }; /// Anchor to top edge only (horizontal bar at top). pub const TOP: Self = Self { top: true, bottom: false, left: true, right: true }; /// Anchor to bottom edge only (horizontal bar at bottom). pub const BOTTOM: Self = Self { top: false, bottom: true, left: true, right: true }; /// Anchor to left edge only (vertical bar at left). pub const LEFT: Self = Self { top: true, bottom: true, left: true, right: false }; /// Anchor to right edge only (vertical bar at right). pub const RIGHT: Self = Self { top: true, bottom: true, left: false, right: true }; /// Convert to smithay's Anchor bitflags. pub( crate ) fn to_wlr_anchor( self ) -> smithay_client_toolkit::shell::wlr_layer::Anchor { use smithay_client_toolkit::shell::wlr_layer::Anchor as WlrAnchor; let mut anchor = WlrAnchor::empty(); if self.top { anchor |= WlrAnchor::TOP; } if self.bottom { anchor |= WlrAnchor::BOTTOM; } if self.left { anchor |= WlrAnchor::LEFT; } if self.right { anchor |= WlrAnchor::RIGHT; } anchor } } /// Stable identifier for an overlay surface. The runtime uses it to diff the /// overlay list between frames: if the same `OverlayId` is returned from /// [`App::overlays`] on consecutive frames the underlying Wayland surface /// and its internal state are kept; if it disappears the surface is destroyed. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord )] pub struct OverlayId( pub u32 ); /// Stable identifier for a subsurface, used to diff the list returned by /// [`App::subsurfaces`] between frames the same way [`OverlayId`] diffs /// overlays. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord )] pub struct SubsurfaceId( pub u32 ); /// Which surface a [`SubsurfaceSpec`] is composited as a child of. `Main` /// (the default position) parents to the app's main surface; `Overlay(id)` /// parents to one of the [`App::overlays`] surfaces, so a slide can ride /// above app windows the way an overlay panel does. An `Overlay` parent that /// is absent or not yet configured is skipped for that frame. #[derive( Debug, Clone, Copy, PartialEq, Eq )] pub enum SubsurfaceParent { Main, Overlay( OverlayId ), } /// One of the surfaces an [`App`] can target with an invalidation. Used inside /// [`InvalidationScope::Only`] to name the affected surfaces. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash )] pub enum SurfaceTarget { /// The application's main surface (the one returned by [`App::view`]). Main, /// The overlay with the given stable [`OverlayId`]. Overlay( OverlayId ), } /// Which surfaces a given [`App::update`] mutation can affect. /// /// Returned by [`App::invalidate_after`] (default [`InvalidationScope::All`]) /// to let the runtime skip redraws on surfaces whose contents could not /// possibly have changed by the message in question. On a shell with many /// overlays most messages only touch one of them, so the savings are large. #[derive( Debug, Clone )] pub enum InvalidationScope { /// Treat every surface as potentially affected (safe default). All, /// Only the listed surfaces may have changed; others can be skipped this /// iteration. An empty list means "nothing visible to redraw" — useful /// for messages that only touch internal bookkeeping. Only( Vec ), } impl InvalidationScope { /// Combine two scopes. `All` absorbs any other scope; two `Only` scopes /// merge into a deduped list of targets. Used by the run loop to fold the /// per-message scopes from a batch of pending messages into a single /// decision before applying it. pub fn union( self, other: Self ) -> Self { match ( self, other ) { ( Self::All, _ ) | ( _, Self::All ) => Self::All, ( Self::Only( mut a ), Self::Only( b ) ) => { for t in b { if !a.contains( &t ) { a.push( t ); } } Self::Only( a ) } } } } /// Description of an additional layer-shell surface rendered on top of the /// main application surface. /// /// Apps return a list of these from [`App::overlays`] each frame. The runtime /// creates one Wayland layer-shell surface per active overlay, renders its /// [`view`](Self::view) to its own canvas, and dispatches input to it /// independently of the main surface. Overlays share the same message type as /// the main app — messages emitted by widgets inside an overlay are delivered /// to [`App::update`] exactly like messages from the main view. /// /// Overlays are useful for building shells whose main surface sits behind /// regular app windows (e.g. a homescreen on [`Layer::Background`]) while /// still exposing on-demand panels above everything (launcher, quick /// settings, power menu…). pub struct OverlaySpec { /// Stable identifier, used to diff overlays between frames. pub id: OverlayId, /// Wayland layer for this overlay. Typically [`Layer::Overlay`] or /// [`Layer::Top`]. pub layer: Layer, /// Screen edges to anchor to. pub anchor: Anchor, /// Desired size `( width, height )` in logical pixels. `0` in either /// component means "fill available space in that dimension". pub size: ( u32, u32 ), /// Exclusive zone in pixels reserved from the anchored edge. /// `-1` requests focus without reserving space, `0` is the default for /// transient overlays that should not push other surfaces around. pub exclusive_zone: i32, /// When `true`, the compositor sends keyboard events to this overlay /// without requiring a click first. pub keyboard_exclusive: bool, /// Interactive input region as a list of rects (logical pixels). Only /// these areas receive pointer/touch input; the rest passes through. /// `None` means the full surface receives input. pub input_region: Option>, /// Widget tree for this overlay. pub view: Element, /// Message sent when the overlay should be dismissed. The runtime /// fires it in three situations: /// /// 1. The compositor sends `xdg_popup.popup_done` (xdg-popup mode) /// — typically when the user clicks outside the popup with the /// grab fully active. /// 2. The user presses pointer / touch on the main surface while /// the overlay is still mapped, and the press does not fall on /// the trigger rect identified by [`Self::anchor_widget_id`]. /// This covers compositors (notably Mutter) that route the /// button to the parent surface instead of breaking the popup /// grab when the cursor was already over the parent. /// 3. The user presses Escape while at least one xdg-popup overlay /// is open. /// /// The application is expected to flip its `is_open` flag (or /// equivalent) to `false` in `update()` so the next frame stops /// returning the spec from [`App::overlays`]. The runtime is /// idempotent if the message arrives more than once for the same /// open / close cycle. pub on_dismiss: Option, /// When set, the overlay is rendered as a Wayland **xdg-popup** /// anchored to the rect of the widget tagged with this /// [`crate::types::WidgetId`] in the previous frame's layout — the /// standard mechanism for combo / context-menu / tooltip popups in /// `xdg-shell` applications. The compositor positions the popup /// adjacent to the anchor and is allowed to flip it (drop-up /// instead of drop-down) when there is not enough room. /// /// When this field is `None`, the overlay is rendered as a /// [`wlr-layer-shell`](Layer) surface — the path used by shells, /// panels, lock screens and overlays that need full-surface /// coverage. In that mode `layer` / `anchor` / `exclusive_zone` / /// `keyboard_exclusive` carry the placement; for the popup mode /// they are ignored. pub anchor_widget_id: Option, } /// An input-transparent child surface composited over the main surface and /// moved by the compositor. /// /// Returned from [`App::subsurfaces`]. The runtime creates one `wl_subsurface` /// per active spec, full-size over the main surface, with an **empty input /// region** — all pointer/touch falls through to the main surface, so the host /// keeps a single input/gesture model. The win is in the move: the content /// buffer is rasterised only when [`content_version`](Self::content_version) /// (or the surface size) changes, while [`x`](Self::x) / [`y`](Self::y) /// changes only emit `wl_subsurface.set_position` + a parent commit — no /// re-raster, no buffer re-upload. This makes an animated reveal/slide track /// the finger at the compositor's compositing cost rather than the client's /// raster cost. pub struct SubsurfaceSpec { /// Stable identifier, used to diff subsurfaces between frames. pub id: SubsurfaceId, /// Surface this subsurface is composited as a child of. Sized to that /// parent; [`x`](Self::x) / [`y`](Self::y) are relative to its top-left. pub parent: SubsurfaceParent, /// Widget tree for this subsurface. Should paint its own opaque /// background if it must cover the main surface beneath it. pub view: Element, /// Position of the subsurface relative to the parent's top-left, in /// layout (physical) pixels — the same space as [`App::on_resize`] and /// widget rects. The runtime divides by the surface scale to obtain the /// logical position it hands to the compositor. Animate this for a cheap /// compositor-side move. pub x: i32, pub y: i32, /// Content revision. Bump it whenever [`view`](Self::view) would paint /// differently; leave it unchanged for position-only updates so the /// runtime skips the re-raster and only repositions. pub content_version: u64, /// Render through GLES (so `backdrop-filter` glass works) rather than the /// software rasteriser. GLES is only worth it for content that actually /// uses the blur: it pays a per-surface EGL-surface creation (and a /// one-time shader compile) the cheap software path avoids, so a panel /// without glass should leave this `false`. pub gpu: bool, } /// Trait that application types must implement to integrate with ltk. pub trait App: 'static { /// The message type produced by this application. type Message: Clone + 'static; /// Build the widget tree for the current frame. fn view( &self ) -> Element; /// Apply a message to the application state. fn update( &mut self, msg: Self::Message ); /// Tell the runtime which surfaces *could* have changed visibly as a /// result of [`update`](Self::update) being called with this message. /// /// Default is [`InvalidationScope::All`] — every surface is redrawn. /// A shell that knows, for example, that a `SetVolume` message only /// affects its quick-settings overlay can return /// `InvalidationScope::Only( vec![ SurfaceTarget::Overlay( quick_id ) ] )` /// to skip pointless work on other surfaces. /// /// Called *before* [`update`](Self::update) (so it sees the message but /// not the post-update state). Side-effect free: must not mutate `self`. fn invalidate_after( &self, _msg: &Self::Message ) -> InvalidationScope { InvalidationScope::All } /// Describe the auxiliary overlay surfaces that should be active this /// frame. Each entry becomes its own Wayland layer-shell surface /// independent from the main surface returned by [`view`](Self::view). /// /// Return an empty `Vec` (the default) to disable multi-surface support — /// the app then renders only onto its main surface. /// /// The runtime diffs the list across frames using [`OverlaySpec::id`]: /// stable IDs are kept alive, new IDs cause a surface to be created, and /// IDs that disappear cause the surface to be destroyed. fn overlays( &self ) -> Vec> { Vec::new() } /// Describe the input-transparent child surfaces composited over the main /// surface this frame. Each becomes a `wl_subsurface` the compositor moves /// by position; see [`SubsurfaceSpec`]. Diffed across frames by /// [`SubsurfaceSpec::id`]. Default empty. fn subsurfaces( &self ) -> Vec> { Vec::new() } /// Return any pending messages from external sources (timers, async, etc.). fn poll_external( &mut self ) -> Vec { vec![] } /// Tags for which the app wants a fresh `xdg-activation-v1` token this /// iteration. Drained once per loop; each tag is echoed back verbatim /// through [`App::on_activation_token`] once the compositor issues the /// token, so the app can hand it to a child it is about to spawn via the /// `$XDG_ACTIVATION_TOKEN` launch convention. Default none. fn take_activation_requests( &mut self ) -> Vec { Vec::new() } /// Deliver a token issued for an earlier [`App::take_activation_requests`] /// tag. `tag` is the string the app handed out; `token` is the opaque /// activation token to place in the launched child's environment. fn on_activation_token( &mut self, _tag: String, _token: String ) -> Option { None } /// Called once, immediately after the first buffer for the main surface /// has been rendered and committed to the compositor. Note: this is the /// client-side commit, not the compositor's present — under VT switching /// the actual present can be deferred (no DRM master yet), so a handoff /// signal that fires here is enough to tell a parent "ready to be /// presented as soon as my VT becomes active". fn on_first_frame_committed( &mut self ) {} /// Called when the surface is asked to close (compositor request, titlebar button, /// layer-shell closed event). Return `true` to allow the application to exit, /// or `false` to cancel. /// /// Default: `true` (always close). fn on_close_requested( &mut self ) -> bool { true } /// Called when tapping/clicking outside any widget. fn on_tap( &mut self ) -> Option { None } /// Called on key press when no text input is focused. fn on_key( &mut self, _keysym: Keysym ) -> Option { None } /// Called on key press with modifier state. Override this instead of /// `on_key` when you need Ctrl/Shift/Alt awareness. fn on_key_with_modifiers( &mut self, keysym: Keysym, _ctrl: bool, _shift: bool ) -> Option { self.on_key( keysym ) } /// Called when a sufficient upward swipe gesture is detected. /// /// Return a message to handle the swipe (e.g., opening an overview). /// The gesture is recognized when the user drags upward by at least /// `swipe_threshold()` × screen height and then releases. fn on_swipe_up( &mut self ) -> Option { None } /// Called during an upward swipe gesture with progress ≥ 0.0. /// /// Use this to create follow-the-finger animations. `progress` starts at 0.0 /// when the drag begins and increases as the finger moves upward, reaching 1.0 /// when the drag distance equals `swipe_threshold()` × screen height. It is /// **not** clamped at 1.0 — the value keeps growing if the finger drags further /// so that follow-the-finger panels can continue tracking the finger all the way /// up the screen. Clamp inside the handler if you need a bounded `[0, 1]` range /// for a visual indicator. /// /// When the user releases without completing the gesture, this method is called /// once more with `progress = 0.0` to signal cancellation. fn on_swipe_progress( &mut self, _progress: f32 ) {} /// Called when a sufficient downward swipe gesture is detected. /// /// Return a message to handle the swipe (e.g., toggling quick settings). /// The gesture is recognized when the user drags downward by at least /// `swipe_down_threshold()` × screen height and then releases. fn on_swipe_down( &mut self ) -> Option { None } /// Called during a downward swipe gesture with progress ≥ 0.0. /// /// Use this to create follow-the-finger animations. `progress` starts at 0.0 /// when the drag begins and increases as the finger moves downward, reaching /// 1.0 when the drag distance equals `swipe_down_threshold()` × screen height. /// It is **not** clamped at 1.0 — the value keeps growing if the finger drags /// further so that follow-the-finger panels can continue tracking the finger /// all the way down the screen. Clamp inside the handler if you need a /// bounded `[0, 1]` range for a visual indicator. /// /// When the user releases without completing the gesture, this method is called /// once more with `progress = 0.0` to signal cancellation. fn on_swipe_down_progress( &mut self, _progress: f32 ) {} /// Fraction of screen height required to trigger [`on_swipe_up`](Self::on_swipe_up). /// /// Default: `0.6` (60% of screen height). Lower values make the gesture easier /// to trigger; higher values require more vertical travel. fn swipe_threshold( &self ) -> f32 { 0.6 } /// Fraction of screen height required to trigger [`on_swipe_down`](Self::on_swipe_down). /// /// Default: `0.15` (15% of screen height). Typically lower than `swipe_threshold()` /// because downward swipes are often initiated from a small UI element like a top bar. fn swipe_down_threshold( &self ) -> f32 { 0.15 } /// Top-edge band where a downward swipe may originate, as a fraction of /// screen height. Presses below this band never fire /// [`on_swipe_down`](Self::on_swipe_down) nor /// [`on_swipe_down_progress`](Self::on_swipe_down_progress). /// /// Default: `1.0` (entire screen — any starting point is accepted). /// Set to a small value like `0.05` to restrict the gesture to the top /// edge, matching the usual system-panel pull-down UX. fn swipe_down_edge( &self ) -> f32 { 1.0 } /// Called when a sufficient leftward swipe gesture is detected. /// /// Return a message to handle the swipe (e.g., paging to the next /// homescreen). Fires on release when the user dragged left by at least /// `swipe_horizontal_threshold()` × screen width. Unlike vertical swipes, /// horizontal swipes survive starting the press on an interactive widget: /// once the drag distance crosses an 8 px threshold the press is promoted /// to a horizontal drag and no tap will fire at release. fn on_swipe_left( &mut self ) -> Option { None } /// Called when a sufficient rightward swipe gesture is detected. /// Mirror of [`on_swipe_left`](Self::on_swipe_left) for the other direction. fn on_swipe_right( &mut self ) -> Option { None } /// Called during a horizontal swipe with signed progress. Negative values /// mean the finger has moved left, positive values mean right. The value /// is `dx / (threshold * width)`, so ±1.0 marks the commit threshold; /// it is **not** clamped so follow-the-finger panels can keep tracking /// past the threshold. When the user releases without completing the /// gesture this method is called once more with `0.0` to signal /// cancellation. fn on_swipe_horizontal_progress( &mut self, _progress: f32 ) {} /// Fraction of screen width required to commit [`App::on_swipe_left`] / /// [`App::on_swipe_right`]. Default: `0.5` (half the screen). A shell that /// wants easier paging can lower this; one that wants to avoid /// accidental pages can raise it. fn swipe_horizontal_threshold( &self ) -> f32 { 0.5 } /// Duration a press must remain stationary (within tolerance) before the /// widget's long-press message fires and the gesture transitions into /// drag mode. Default: `500 ms`. fn long_press_duration( &self ) -> std::time::Duration { std::time::Duration::from_millis( 500 ) } /// Called on every pointer motion once a long-press has fired and the /// gesture has entered drag mode. `x`, `y` are in physical pixels of the /// surface that owned the original press. fn on_drag_move( &mut self, _x: f32, _y: f32 ) {} /// Called on release once a long-press has fired. Return a message to /// handle the drop (e.g., commit the drop target). `x`, `y` are in /// physical pixels of the surface that owned the original press. The /// regular tap / swipe handling is suppressed for this gesture. fn on_drop( &mut self, _x: f32, _y: f32 ) -> Option { None } /// Called when a text-input widget gains or loses focus. fn on_text_input_focused( &mut self, _active: bool ) {} /// Translate a Wayland `ext-foreign-toplevel-list-v1` event into an /// app message. The runtime binds the protocol globally and forwards /// every open / close it sees here; apps that ignore window state /// leave the default (return `None`) and pay nothing. /// /// The `id` field is the Wayland object id of the toplevel handle — /// unique within the session, stable for the handle's lifetime, the /// same value coming in on `Closed` as the matching `Opened`. /// `app_id` on `Opened` is the toplevel's `app_id` event (may be /// empty if the compositor never sent one before the first `done`). fn on_toplevel_event( &self, _event: ToplevelEvent ) -> Option { None } /// Return `Some(id)` once to focus the widget with that [`WidgetId`](crate::types::WidgetId). /// The app must return `None` after the first call. fn take_focus_request( &mut self ) -> Option { None } /// Called on every pointer motion (mouse move, button press, button /// release). `x`, `y` are in physical pixels of the main surface, /// the same coordinate space as widget rects. Default: no-op. /// /// Useful for embedding components that need to know the live /// pointer position without going through a full /// [`crate::widget::external::ExternalSource`] / `WidgetHandlers` /// dispatch path — for example forwarding clicks into an embedded /// WPE WebView. fn on_pointer_move( &mut self, _x: f32, _y: f32 ) {} /// Called on a wheel / touchpad scroll event whose position does /// not fall inside any LTK [`scroll`](crate::scroll) viewport. `x`, /// `y` are in physical pixels (same coordinate space as /// [`Self::on_pointer_move`]); `dx`, `dy` are scroll deltas in the /// raw axis units the compositor delivered (`AxisSource::Wheel` /// gives ~10 px ticks, touchpads give continuous values). Default: /// no-op. /// /// Useful for embeddings that take over scrolling for their own /// content — for example forwarding the event to a WPE view that /// owns a scrollable web page. fn on_pointer_axis( &mut self, _x: f32, _y: f32, _dx: f32, _dy: f32 ) {} /// Raw multi-touch callbacks. Default: no-op. /// /// The first finger to land becomes the *primary slot* and is fed /// through the regular gesture machine — `on_pointer_*`, swipe, /// scroll, long-press, drag-and-drop all run from that slot. Every /// additional finger fires `on_touch_down` / `on_touch_move` / /// `on_touch_up` instead, with `id` matching the `wl_touch.id` /// the compositor delivered. /// /// Use this for app-defined multi-finger gestures (pinch-zoom, /// two-finger pan in a canvas, parallel keyboard keys) that the /// built-in single-slot machine cannot model. `(x, y)` are in /// physical pixels, matching the coordinate space of /// [`Self::on_pointer_move`]. fn on_touch_down( &mut self, _id: i64, _x: f32, _y: f32 ) {} /// See [`Self::on_touch_down`]. fn on_touch_move( &mut self, _id: i64, _x: f32, _y: f32 ) {} /// See [`Self::on_touch_down`]. `x`, `y` are the last known /// position of the finger (`wl_touch.up` does not carry one). fn on_touch_up( &mut self, _id: i64, _x: f32, _y: f32 ) {} /// Pointer of a cross-application drag-and-drop entered or moved /// inside the main surface. `(x, y)` is in logical pixels. fn on_drop_motion( &mut self, _x: f32, _y: f32 ) {} /// The drag left without dropping. fn on_drop_leave( &mut self ) {} /// External drop payload landed on the surface. `mime` is the /// best mime the source offered (text/uri-list, text/plain, …) /// and `text` is the decoded payload (UTF-8). `(x, y)` is the /// release position in logical pixels. fn on_drop_received( &mut self, _x: f32, _y: f32, _mime: &str, _text: &str ) {} /// Called on every left-button mouse press / release before the /// regular gesture machine fires. `pressed = true` for press, /// `false` for release. `(x, y)` are in physical pixels (same /// coordinate space as [`Self::on_pointer_move`]). Default: no-op. /// /// Lets embeddings see the raw button transitions without going /// through LTK's tap/long-press/drag classification — needed for /// forwarding clicks AND drags to an embedded view, since the tap /// classifier collapses press+release into a single event. fn on_pointer_button( &mut self, _x: f32, _y: f32, _pressed: bool ) {} /// Called on every keyboard event (press *and* release) before the /// regular focus-aware dispatch ([`Self::on_key`] / /// [`Self::on_key_with_modifiers`]) runs. Default: no-op. /// /// `keycode` is the raw hardware scancode straight from the /// compositor (`wl_keyboard.key`'s `key` argument); `keysym` is /// the xkb-translated keysym the user effectively pressed. Most /// consumers only care about `keysym`, but embeddings that /// forward into an inner window system (a WPE web view, an X11 /// emulator, …) typically need both — the inner system does its /// own keycode → keysym translation for layout-aware shortcuts. /// /// This bypass exists for those embeddings: it fires *in addition* /// to the normal callbacks; consume or ignore at will. fn on_raw_key( &mut self, _keysym: Keysym, _keycode: u32, _pressed: bool, _ctrl: bool, _shift: bool ) {} /// Called when the surface is (re-)configured with a new size. /// /// `width` and `height` are in **physical pixels** (the Wayland surface /// size multiplied by the current buffer scale). They therefore match the /// coordinate space used for layout and the pointer/touch callbacks /// (`on_drag_move`, `on_drop`, widget hit-testing). Also fired when the /// buffer scale changes, so apps can refresh any state keyed off the old /// physical dimensions. fn on_resize( &mut self, _width: u32, _height: u32 ) {} /// Called when the compositor reports a new integer buffer scale for the /// main surface. `scale` is the integer buffer scale (1 = standard DPI, /// 2 = HiDPI ×2); physical pixels equal logical pixels × scale. The default /// is inert so apps that only care about physical pixels can keep using /// [`on_resize`](Self::on_resize). fn on_scale_changed( &mut self, _scale: u32 ) {} /// Called once at startup with a channel sender that can be used from any /// thread to deliver messages into the event loop. Sending a message /// immediately wakes the event loop — no polling delay. fn set_channel_sender( &mut self, _sender: ChannelSender ) {} /// How often the event loop should call [`poll_external`](Self::poll_external) for /// periodic work such as clock ticks. Return `None` (the default) to disable the /// timer — `poll_external` will still be called after every Wayland event. fn poll_interval( &self ) -> Option { None } /// Delay before the runtime starts repeating a held-down key. /// /// `Some(d)` overrides whatever the compositor advertises through /// `wl_keyboard.repeat_info`; `None` (the default) means "use the /// compositor's setting, or fall back to 500 ms when the compositor /// did not provide one". /// /// Returning `Some(Duration::ZERO)` effectively disables the wait /// before the first repeat — useful only for diagnostic builds. fn key_repeat_delay( &self ) -> Option { None } /// Interval between successive synthetic key events while a key is /// held down past the initial [`Self::key_repeat_delay`]. /// /// `Some(d)` overrides the compositor's `wl_keyboard.repeat_info` /// rate; `None` (the default) means "use the compositor's setting, /// or fall back to 33 ms (~30 Hz) when the compositor did not /// provide one". Pass `Some(Duration::ZERO)` to disable repeats /// entirely (or override the per-key gate via /// [`Self::key_repeats`]). fn key_repeat_interval( &self ) -> Option { None } /// Override the pointer cursor shape globally. When `Some(shape)`, /// the runtime sends that shape to the compositor regardless of /// which widget the pointer is over — useful for "the app is busy" /// states (return [`crate::CursorShape::Wait`]) or while the app /// is doing a long synchronous operation. Returning `None` (the /// default) lets per-widget defaults take effect. /// /// The runtime calls this every frame, so flipping a `loading` /// boolean in [`Self::update`] propagates to the cursor on the /// next iteration without any extra wiring. fn cursor_override( &self ) -> Option { None } /// Decide whether a given key participates in held-key repeat. /// /// Default: every non-modifier key repeats *except* `Escape`, `Tab` /// and `ISO_Left_Tab` — those drive one-shot UI semantics /// (dismiss / focus cycle) where a held key would multiply the /// effect in surprising ways. Override to widen or narrow the gate /// (a chess clock app, for example, might want even Tab to /// repeat). fn key_repeats( &self, keysym: Keysym ) -> bool { !matches!( keysym, Keysym::Escape | Keysym::Tab | Keysym::ISO_Left_Tab, ) } /// Return `true` while a frame-by-frame animation is running. /// The event loop will keep requesting redraws at ~60 fps until this returns `false`. fn is_animating( &self ) -> bool { false } /// Return `true` when a finger-tracked swipe or an [`Self::is_animating`] /// slide only repositions the app's input-transparent subsurfaces /// ([`Self::subsurfaces`]) while the main surface buffer stays unchanged. /// The runtime then skips the per-frame main re-raster that /// [`Self::on_swipe_progress`] and `is_animating` would otherwise force, /// keeping the animation pumped (motion events during the drag, a bare /// frame callback while `is_animating`) and letting the per-frame /// subsurface reconcile carry the motion. Use it for a slide-to-reveal /// panel over a static main surface. fn subsurface_motion_only( &self ) -> bool { false } /// Return `true` while the next frame should swap the expensive /// Glass passes for cheap fallbacks — currently the /// `backdrop-filter` blur drops from a 41-tap kernel to a 9-tap /// kernel, with the snapshot region shrunk to match. The event /// loop sets this on the renderer right before drawing through an /// internal low-quality-paint flag. /// /// Default: matches [`Self::is_animating`], so a settle animation /// automatically downgrades. Override to include other "in motion" /// states the runtime cannot observe — e.g. a finger-tracked /// Background color for the canvas. Override to make the surface /// transparent or to deviate from the theme. Default: the active /// theme's `bg` token, so the window matches the rest of the /// shell out of the box. fn background_color( &self ) -> crate::types::Color { crate::theme::palette().bg } /// Return the interactive input region as a list of rects (logical pixels). /// Only these areas receive pointer/touch input; the rest passes through. /// Return `None` (default) to receive input everywhere. fn input_region( &self ) -> Option> { None } /// Return `Some(( title, app_id ))` to force an XDG toplevel window instead of /// layer-shell overlay. The compositor will display the title in the title bar /// and use the app_id for taskbar/icon matching. Return `None` (default) to /// use layer-shell when available. /// /// **Deprecated**: Use [`shell_mode`](Self::shell_mode) instead. fn window_config( &self ) -> Option<( &str, &str )> { None } /// Specify the Wayland shell mode for this application. /// /// - [`ShellMode::Window`]: Normal application window (xdg-shell). **Default.** /// - [`ShellMode::Layer`]: System component at a specific layer (layer-shell). /// - [`ShellMode::SessionLock`]: `ext-session-lock-v1` lock surface (screen locker). /// /// For regular applications, use the default `Window` mode. /// For shell components (panels, backgrounds, overlays), use `Layer`. /// For a screen locker, use `SessionLock` and request the unlock by /// returning `true` from [`requested_exit`](Self::requested_exit). fn shell_mode( &self ) -> ShellMode { ShellMode::Window } /// Return `true` to tear the surface down and exit the event loop. For a /// [`ShellMode::SessionLock`] surface the runtime calls `unlock` first, so /// the compositor lifts the lock instead of leaving the outputs blanked. /// Polled after every batch of `update`s. fn requested_exit( &self ) -> bool { false } /// Suggest an initial size for an xdg-shell window in logical pixels. /// /// Returning `Some(( w, h ))` makes ltk call both /// `xdg_toplevel.set_min_size( w, h )` and /// `xdg_toplevel.set_max_size( w, h )` before the first commit, which /// most compositors honour as an exact size for the configure they /// send back. The window is still resizable from the application's /// point of view — if the compositor sends a different configure, the /// runtime adopts that size on the next frame. /// /// Only applies to [`ShellMode::Window`] surfaces and to the /// xdg-shell fallback path used when `wlr-layer-shell` is missing. /// Ignored for layer-shell surfaces, which use [`Self::layer_size`] /// instead. /// /// **Default**: `None` (let the compositor pick). fn window_size_hint( &self ) -> Option<( u32, u32 )> { None } /// Request fullscreen on the toplevel before its first commit. /// /// When `true`, ltk calls `xdg_toplevel.set_fullscreen( None )` so /// the compositor picks an output and maps the surface fullscreen /// from the start (no flicker through a windowed configure first). /// The compositor's own decorations are suppressed for fullscreen /// surfaces; the built-in titlebar from [`Self::window_config`] is /// still painted by ltk on top of the surface unless the app opts /// out. /// /// Only applies to [`ShellMode::Window`] surfaces and to the /// xdg-shell fallback path used when `wlr-layer-shell` is missing. /// /// **Default**: `false`. fn start_fullscreen( &self ) -> bool { false } /// Specify the exclusive zone for layer-shell surfaces. /// /// The exclusive zone reserves screen space for this surface. /// For example, a top panel with height 50 should return `50` to prevent /// other windows from overlapping it. /// /// - `> 0`: Reserve this many pixels from the anchored edge /// - `0`: No exclusive zone (default for overlays) /// - `-1`: Don't reserve space but request focus /// /// Only applies to [`ShellMode::Layer`] surfaces. Ignored for windows. fn exclusive_zone( &self ) -> i32 { -1 } /// Specify which screen edges the layer-shell surface is anchored to. /// /// Anchoring determines the position and size of the surface: /// - [`Anchor::TOP`]: Top bar (anchored to top, left, right) /// - [`Anchor::BOTTOM`]: Bottom bar (anchored to bottom, left, right) /// - [`Anchor::LEFT`]: Left sidebar (anchored to left, top, bottom) /// - [`Anchor::RIGHT`]: Right sidebar (anchored to right, top, bottom) /// - [`Anchor::ALL`]: Full screen (anchored to all edges) /// /// **Default**: `Anchor::ALL` (full screen) /// /// Only applies to [`ShellMode::Layer`] surfaces. Ignored for windows. fn layer_anchor( &self ) -> Anchor { Anchor::ALL } /// Specify the desired size for layer-shell surfaces. /// /// Returns `(width, height)` where: /// - `0` means "fill available space in that dimension" /// - `> 0` means "use this exact size in pixels" /// /// **Examples:** /// - Top bar: `(0, 50)` - full width, 50px height /// - Bottom bar: `(0, 40)` - full width, 40px height /// - Side panel: `(300, 0)` - 300px width, full height /// - Full screen: `(0, 0)` - fill everything (default) /// /// **Default**: `(0, 0)` (fill all available space) /// /// Only applies to [`ShellMode::Layer`] surfaces. Ignored for windows. fn layer_size( &self ) -> ( u32, u32 ) { ( 0, 0 ) } /// Request exclusive keyboard focus for this layer-shell surface. /// /// When `true`, the compositor sends keyboard events to this surface /// without requiring a pointer click first. Useful for greeters, /// lock screens, and other surfaces that must capture input immediately. /// /// **Default**: `false` (on-demand keyboard interactivity) /// /// Only applies to [`ShellMode::Layer`] surfaces. Ignored for windows. fn keyboard_exclusive( &self ) -> bool { false } } /// Run the application. Blocks until the window is closed. /// /// Panics on init failure (no Wayland compositor, missing protocol, etc.). /// Embedders that need to recover gracefully — e.g. fall back to a /// software TTY UI when no compositor is reachable — should call /// [`try_run`] instead and match on the returned [`RunError`]. pub fn run( app: A ) { crate::event_loop::run( app ); } /// Run the application, returning a typed error on init failure. /// /// Same as [`run`] but recoverable: every fatal init step (Wayland /// connection, registry, calloop event loop, `wl_compositor` / `wl_shm` /// / `xdg_wm_base` bindings) is converted into a [`RunError`] variant /// the caller can match on. Once init succeeds the function blocks /// until the surface is closed and returns `Ok(())`. Errors from the /// dispatch loop itself (already on screen) still panic — they are /// non-recoverable since the surface state machine cannot be unwound /// cleanly from this entry point. /// /// Use this when: /// /// * the application needs a graceful fallback path on systems without /// a Wayland session (CI runners, minimal containers, X11-only /// environments where ltk has no backend), /// * an embedder wants to log the specific protocol that's missing /// instead of panicking with a generic stack trace, /// * the caller wants to retry / wait for a compositor to come up /// instead of aborting. /// /// The standard `run( app )` remains the simpler entry point for /// applications that always run on a known-good Wayland session. /// /// ```rust,no_run /// # use ltk::{ button, App, Element, RunError }; /// # #[ derive( Clone ) ] enum Msg {} /// # struct MyApp; /// # impl App for MyApp { /// # type Message = Msg; /// # fn view( &self ) -> Element { button( "x" ).into() } /// # fn update( &mut self, _: Msg ) {} /// # } /// match ltk::try_run( MyApp ) /// { /// Ok( () ) => {} /// Err( RunError::NoWaylandConnection( _ ) ) => /// { /// eprintln!( "no compositor — falling back to stdio" ); /// // run a CLI fallback… /// } /// Err( RunError::MissingProtocol { name, .. } ) => /// { /// eprintln!( "compositor lacks `{name}` — refusing to start" ); /// std::process::exit( 1 ); /// } /// Err( e ) => /// { /// eprintln!( "ltk init failed: {e}" ); /// std::process::exit( 1 ); /// } /// } /// ``` pub fn try_run( app: A ) -> Result<(), RunError> { crate::event_loop::try_run( app ) } pub use crate::event_loop::RunError;