A terminal drawn by the application itself needed four things ltk did not give it: the selection the middle button pastes, a way to read the clipboard, the text a key types once dead keys are applied, and a text cursor over its text area that does not hide the window's resize cursors. Text fields gain the primary selection along the way, and the symbol fallback and a missed field in the previous commit are fixed. The primary selection. `event_loop/primary_selection.rs` binds `zwp_primary_selection_device_manager_v1` when the compositor offers it, creates the device with the data device in `new_seat`, and holds the manager, the device, our source while we own the selection, its text and the inbox the reader threads post to. A selection offered by another client is read on a thread of its own as the clipboard is; a peer asking for ours is served from the cached text, and a cancelled source is dropped. `data_device.rs` gives both the pieces they share: `pick_text_mime`, `read_selection` and `write_selection`, so the clipboard behaves as before. Text fields. Once a press ends, `sync_text_field` publishes a new selection in the focused text field as the primary selection; password fields never publish theirs. It remembers what it last published rather than comparing with the current primary selection, so a selection made in another application is not taken back. A middle-button press on a text field focuses it, puts the cursor where it landed and inserts the primary selection there. The application's side. `App::take_primary_request` makes a text the primary selection, as `take_clipboard_request` does for the clipboard. `App::take_paste_request` names a `SelectionKind`, `Clipboard` or `Primary`, and the runtime answers with `App::on_paste` and that selection's text, never with an empty one. `App::on_pointer_middle` reports a middle-button press anywhere but on a text field. All default to doing nothing. Raw keys. `App::on_raw_key_with_alt` gives way to `App::on_raw_key_event`, which takes a `RawKey` with the keysym, the keycode, the press, Ctrl, Shift and Alt, and `text`: what sctk reports the key types once dead keys and compose sequences are applied, `None` on release, halfway through a sequence and for keys that type nothing. A terminal that took the character from the keysym typed `a` for an acute accent followed by `a`. The default still forwards to `on_raw_key`, and prompt was the only application overriding the old hook. Every key press now also records its serial in `last_input_serial`, so a selection set from a keyboard shortcut carries a serial a strict compositor accepts rather than the last click's. The cursor. `App::cursor_at( x, y )` is the cursor the application wants at a point of the main surface. It comes after the resize edges and `cursor_override`, and before the cursor of the widget under the pointer, so a terminal shows the text cursor over its cells and the resize cursors still show at the window's edges, which a `cursor_override` would hide. Fonts. Noto Sans Symbols 2 and Noto Sans Symbols follow DejaVu Sans in the fallback chain, for the media controls, arrows and shapes DejaVu lacks, which drew as the missing glyph. `TextEdit::map_msg` now carries `pad_h` over: the previous commit added the field but left it out there, and ltk did not build.
1409 lines
63 KiB
Rust
1409 lines
63 KiB
Rust
// SPDX-License-Identifier: LGPL-2.1-only
|
||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||
|
||
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,
|
||
/// Not a layer-shell surface at all: an ordinary `xdg_toplevel`
|
||
/// window stacked among the applications, decorated and listed by
|
||
/// the compositor like any other. [`OverlaySpec::size`] is its fixed
|
||
/// size (`0` falls back to a default, it cannot "fill"); anchor,
|
||
/// exclusive zone and keyboard exclusivity are ignored. The
|
||
/// compositor's close button delivers [`OverlaySpec::on_dismiss`].
|
||
Window,
|
||
/// 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,
|
||
// Never reaches layer-shell; the reconciler creates a window first.
|
||
Layer::Window => WlrLayer::Top,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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<SurfaceTarget> ),
|
||
}
|
||
|
||
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<Message: Clone>
|
||
{
|
||
/// 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 )`, resolved to logical pixels against
|
||
/// the main surface (output) when the overlay is materialized. A
|
||
/// [`Length::px`](crate::Length::px) keeps a fixed size; a
|
||
/// [`Length::widget`](crate::Length::widget) / `vmin` / `dp` makes the
|
||
/// overlay surface scale with the display like any other sized element.
|
||
/// A component that resolves to `0` (e.g. `Length::px( 0.0 )`) means
|
||
/// "fill available space in that dimension".
|
||
pub size: ( crate::types::Length, crate::types::Length ),
|
||
|
||
/// 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<Vec<crate::types::Rect>>,
|
||
|
||
/// Widget tree for this overlay.
|
||
pub view: Element<Message>,
|
||
|
||
/// 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<Message>,
|
||
|
||
/// 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<crate::types::WidgetId>,
|
||
}
|
||
|
||
/// 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<Message: Clone>
|
||
{
|
||
/// 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<Message>,
|
||
|
||
/// 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,
|
||
}
|
||
|
||
/// What an app did with a back request. Returned by [`App::on_back`].
|
||
#[ derive( Debug, Clone, PartialEq ) ]
|
||
pub enum BackResponse<Message>
|
||
{
|
||
/// There was something to go back from: deliver this message, which
|
||
/// undoes the top-most piece of navigation state.
|
||
Message( Message ),
|
||
/// The request was handled outside the message loop — for hosts whose
|
||
/// back stack lives elsewhere (an embedded Android runtime, a guest
|
||
/// toolkit) and cannot be inspected ahead of time.
|
||
Consumed,
|
||
/// Nothing to go back to. The runtime applies [`App::back_at_root`].
|
||
Unhandled,
|
||
}
|
||
|
||
/// What a back request does once the app has nothing left to go back to.
|
||
/// Returned by [`App::back_at_root`].
|
||
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
|
||
pub enum BackAtRoot
|
||
{
|
||
/// Minimise the window (`xdg_toplevel.set_minimized`); the app keeps
|
||
/// running and the compositor shows whatever was behind it.
|
||
Minimize,
|
||
/// Close the window, through [`App::on_close_requested`].
|
||
Close,
|
||
/// Do nothing.
|
||
Ignore,
|
||
}
|
||
|
||
/// A key press or release as [`App::on_raw_key_event`] reports it.
|
||
#[ derive( Debug, Clone ) ]
|
||
pub struct RawKey
|
||
{
|
||
pub keysym: Keysym,
|
||
pub keycode: u32,
|
||
pub pressed: bool,
|
||
pub ctrl: bool,
|
||
pub shift: bool,
|
||
pub alt: bool,
|
||
/// What the key types once dead keys and compose sequences are applied; `None` on release, halfway through a sequence and for keys that type nothing.
|
||
pub text: Option<String>,
|
||
}
|
||
|
||
/// Which of the two selections an app reads, through [`App::take_paste_request`].
|
||
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
|
||
pub enum SelectionKind
|
||
{
|
||
/// What Copy put there, pasted with Ctrl+V.
|
||
Clipboard,
|
||
/// The text last selected, pasted with the middle button.
|
||
Primary,
|
||
}
|
||
|
||
/// 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<Self::Message>;
|
||
|
||
/// Apply a message to the application state.
|
||
fn update( &mut self, msg: Self::Message );
|
||
|
||
/// Stable application identifier, reverse-DNS style
|
||
/// (`"net.liberux.settings"`, `"io.eydos.Calls"`), constant for the
|
||
/// life of the process.
|
||
///
|
||
/// The runtime uses it in three places:
|
||
///
|
||
/// * `xdg_toplevel.set_app_id` on the main window, so the compositor,
|
||
/// the dock and the task switcher can match the window to its
|
||
/// `.desktop` entry (use the desktop file's name minus `.desktop`,
|
||
/// or its `StartupWMClass`);
|
||
/// * the accessibility tree — AccessKit / AT-SPI2 report it as the
|
||
/// application name;
|
||
/// * the on-disk session directory `$XDG_STATE_HOME/<app_id>/`
|
||
/// where [`save_state`](Self::save_state) is persisted.
|
||
///
|
||
/// It must not be empty and must not contain `/`; the runtime then
|
||
/// disables session persistence and logs once. Layer-shell and
|
||
/// session-lock components still return one (it names them in the
|
||
/// a11y tree), even though they never get a toplevel. The `app_id`
|
||
/// element of the deprecated [`window_config`](Self::window_config)
|
||
/// tuple is ignored in favour of this method — keep both pointing at
|
||
/// the same `const`.
|
||
///
|
||
/// ```rust
|
||
/// # use ltk::{ App, Element, text };
|
||
/// # #[ derive( Clone ) ] enum Msg {}
|
||
/// # struct Notes;
|
||
/// const APP_ID: &str = "net.example.Notes";
|
||
///
|
||
/// impl App for Notes
|
||
/// {
|
||
/// # type Message = Msg;
|
||
/// # fn view( &self ) -> Element<Msg> { text( "" ).into() }
|
||
/// # fn update( &mut self, _: Msg ) {}
|
||
/// # fn save_state( &self ) -> Option<Vec<u8>> { None }
|
||
/// # fn restore_state( &mut self, _: Vec<u8> ) {}
|
||
/// fn app_id( &self ) -> &str { APP_ID }
|
||
/// }
|
||
/// ```
|
||
fn app_id( &self ) -> &str;
|
||
|
||
/// Serialize the part of the state worth surviving a restart.
|
||
///
|
||
/// Return the bytes the runtime should persist, or `None` when there
|
||
/// is nothing to bring back (a shell panel, a lock screen, a
|
||
/// stateless tool). The encoding is the app's own — JSON, CBOR, a
|
||
/// hand-rolled line format — ltk never inspects it and puts no serde
|
||
/// bound on the trait. Keep it small and cheap: the runtime calls
|
||
/// this
|
||
///
|
||
/// * every ~30 s while the app runs, touching the disk only when
|
||
/// the bytes differ from the last save (a UI-only change that
|
||
/// round-trips to identical bytes costs one comparison);
|
||
/// * when the window closes — after
|
||
/// [`on_close_requested`](Self::on_close_requested) allowed it or
|
||
/// [`requested_exit`](Self::requested_exit) returned `true`;
|
||
/// * on `SIGTERM` / `SIGINT`, which the runtime turns into a clean
|
||
/// exit of the event loop instead of letting the process die.
|
||
///
|
||
/// The bytes are written atomically (temp file + rename, mode
|
||
/// `0600`) to `$XDG_STATE_HOME/<app_id>/state.bin`
|
||
/// (`~/.local/state/<app_id>/state.bin` when the variable is unset)
|
||
/// next to a `session.json` holding the compositor's
|
||
/// `xdg-session-management-v1` session id and a clean-exit marker.
|
||
/// Returning `None` removes a stale `state.bin`. Only
|
||
/// [`ShellMode::Window`] apps (and the deprecated
|
||
/// [`window_config`](Self::window_config) path) are persisted;
|
||
/// layer and session-lock surfaces are skipped silently, so shell
|
||
/// components simply return `None`.
|
||
///
|
||
/// The file is plain on disk: never put secrets in it (passwords
|
||
/// belong to the secure-mode `text_edit` and its wiped buffers),
|
||
/// and keep large blobs (images, caches) elsewhere. The signal
|
||
/// handler is installed process-wide when [`run`](crate::run) is
|
||
/// entered, so the clean exit holds no matter which thread receives
|
||
/// the signal or when it was started.
|
||
///
|
||
/// [`restore_state`](Self::restore_state) explains when — and when
|
||
/// not — the bytes come back.
|
||
///
|
||
/// ```rust
|
||
/// use ltk::{ App, Element, text };
|
||
/// use serde::{ Deserialize, Serialize };
|
||
///
|
||
/// #[ derive( Clone ) ]
|
||
/// enum Msg { Tab( usize ) }
|
||
///
|
||
/// #[ derive( Serialize, Deserialize ) ]
|
||
/// struct Persisted { version: u32, tab: usize, draft: String }
|
||
///
|
||
/// struct Editor { tab: usize, draft: String, cursor_blink: bool }
|
||
///
|
||
/// impl App for Editor
|
||
/// {
|
||
/// type Message = Msg;
|
||
///
|
||
/// fn app_id( &self ) -> &str { "net.example.Editor" }
|
||
///
|
||
/// fn save_state( &self ) -> Option<Vec<u8>>
|
||
/// {
|
||
/// // Only what a relaunch needs — the blink phase stays out.
|
||
/// let p = Persisted { version: 1, tab: self.tab, draft: self.draft.clone() };
|
||
/// serde_json::to_vec( &p ).ok()
|
||
/// }
|
||
///
|
||
/// fn restore_state( &mut self, state: Vec<u8> )
|
||
/// {
|
||
/// // Tolerate an older or corrupt file: keep the defaults.
|
||
/// if let Ok( p ) = serde_json::from_slice::<Persisted>( &state )
|
||
/// {
|
||
/// self.tab = p.tab;
|
||
/// self.draft = p.draft;
|
||
/// }
|
||
/// }
|
||
/// # fn view( &self ) -> Element<Msg> { text( self.draft.clone() ).into() }
|
||
/// # fn update( &mut self, msg: Msg ) { let Msg::Tab( t ) = msg; self.tab = t; }
|
||
/// }
|
||
/// ```
|
||
fn save_state( &self ) -> Option<Vec<u8>>;
|
||
|
||
/// Re-apply bytes previously returned by [`save_state`](Self::save_state).
|
||
///
|
||
/// Called at most once per process, synchronously inside
|
||
/// [`run`](crate::run) / [`try_run`](crate::try_run), before the
|
||
/// window is created and before the first [`view`](Self::view) — the
|
||
/// first frame already shows the restored state. It is **not**
|
||
/// called on an ordinary launch. The runtime restores app state only
|
||
/// when
|
||
///
|
||
/// * the process is relaunched as part of a session restore: the
|
||
/// shell sets `LTK_SESSION_RESTORE=1` in the environment (the
|
||
/// runtime removes the variable before the app can spawn
|
||
/// children); or
|
||
/// * the previous run of this `app_id` did not exit cleanly (crash,
|
||
/// `SIGKILL`, power loss): its `session.json` still says
|
||
/// `clean_exit: false`.
|
||
///
|
||
/// This mirrors Android's saved-instance-state contract: opening the
|
||
/// app from the launcher gives a fresh instance, coming back after
|
||
/// the system killed it lands where the user left off. Window
|
||
/// geometry (size, position, workspace) is separate — the compositor
|
||
/// restores it through `xdg-session-management-v1` on *every*
|
||
/// launch, plain ones included, using the session id ltk stores.
|
||
///
|
||
/// `state` is exactly what `save_state` produced, possibly by an
|
||
/// older build: validate, version, and fall back to defaults instead
|
||
/// of panicking. Nothing is called when no state file exists or the
|
||
/// app returned `None` last time. Shell components never receive
|
||
/// this call and implement it as a no-op.
|
||
///
|
||
/// See [`save_state`](Self::save_state) for the file layout and a
|
||
/// worked example.
|
||
fn restore_state( &mut self, state: Vec<u8> );
|
||
|
||
/// 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<OverlaySpec<Self::Message>> { Vec::new() }
|
||
|
||
/// Title of a [`Layer::Window`] overlay, shown in its decoration and
|
||
/// in the compositor's window lists. Default: the [`app_id`](Self::app_id).
|
||
fn overlay_title( &self, _id: OverlayId ) -> Option<String> { None }
|
||
|
||
/// wlr-layer-shell namespace of a layer overlay, so the compositor can
|
||
/// tell it apart from the rest. Read when the surface is created.
|
||
/// Default: `"ltk-overlay"`.
|
||
fn overlay_namespace( &self, _id: OverlayId ) -> Option<&'static str> { None }
|
||
|
||
/// 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<SubsurfaceSpec<Self::Message>> { Vec::new() }
|
||
|
||
/// Return any pending messages from external sources (timers, async, etc.).
|
||
fn poll_external( &mut self ) -> Vec<Self::Message> { 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<String> { 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<Self::Message> { 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 every time a buffer for the main surface has been rendered and
|
||
/// committed, before the subsurfaces are reconciled for the same
|
||
/// iteration. Lets a host sequence a subsurface change after the main
|
||
/// frame it depends on: a subsurface retired here goes away only once
|
||
/// the main frame that replaces it has been sent.
|
||
fn on_main_frame_committed( &mut self ) {}
|
||
|
||
/// Called every time a buffer for the overlay `id` has been rendered and
|
||
/// committed.
|
||
fn on_overlay_frame_committed( &mut self, _id: OverlayId ) {}
|
||
|
||
/// Called every time the content of the subsurface `id` has been
|
||
/// rasterised and committed (first raster, resize or a new
|
||
/// `content_version`), before the parent commit that maps it.
|
||
fn on_subsurface_committed( &mut self, _id: SubsurfaceId ) {}
|
||
|
||
/// 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<Self::Message> { None }
|
||
|
||
/// Called on key press when no text input is focused.
|
||
fn on_key( &mut self, _keysym: Keysym ) -> Option<Self::Message> { 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::Message>
|
||
{
|
||
self.on_key( keysym )
|
||
}
|
||
|
||
/// Called on a back request — the shell's back button or gesture
|
||
/// (delivered as the `XF86Back` key) or a mouse's back button — once
|
||
/// the runtime has nothing of its own left to close.
|
||
///
|
||
/// Back peels one layer per request, top-most first:
|
||
///
|
||
/// 1. transient UI the runtime owns — the built-in context menu, open
|
||
/// xdg-popup overlays, a text selection, the top-most pressable
|
||
/// carrying an `on_escape` (a [`dialog`](crate::dialog)'s cancel);
|
||
/// 2. text-input focus — dropping it also retires the on-screen
|
||
/// keyboard on compositors that did not close it themselves;
|
||
/// 3. **this hook** — the app's own navigation state;
|
||
/// 4. [`Self::on_key_with_modifiers`] with `Keysym::XF86_Back`, for
|
||
/// apps that predate this hook;
|
||
/// 5. [`Self::back_at_root`].
|
||
///
|
||
/// Return [`BackResponse::Message`] with the message that undoes the
|
||
/// top-most piece of *navigation state* — the part of the model that
|
||
/// says where the user is: the open panel, the current screen, the
|
||
/// position in a history. Derive the answer from the model every time
|
||
/// instead of keeping a separate list of things to undo: a list drifts
|
||
/// the first time the user leaves a screen some other way (its own
|
||
/// close button, a link), while the model cannot disagree with itself.
|
||
///
|
||
/// Navigation state is also what [`Self::save_state`] should persist.
|
||
/// Keeping the two the same is what makes back behave identically
|
||
/// after a session restore or a crash: the restored model yields the
|
||
/// same answers here, with nothing extra to serialise.
|
||
/// [`NavStack`](crate::NavStack) packages that pattern for the common
|
||
/// multi-screen app. `docs/navigation.md` covers the model at length.
|
||
///
|
||
/// `Escape` is a different request: it only runs step 1, drops the
|
||
/// focus and reaches [`Self::on_key_with_modifiers`]. It never
|
||
/// navigates and never leaves the app.
|
||
///
|
||
/// ```rust
|
||
/// # use ltk::{ App, BackResponse, Element, text };
|
||
/// # #[ derive( Clone, Copy, PartialEq ) ] enum Screen { List, Detail }
|
||
/// #[ derive( Clone ) ]
|
||
/// enum Msg { CloseSearch, Show( Screen ) }
|
||
///
|
||
/// struct Notes { screen: Screen, searching: bool }
|
||
///
|
||
/// impl App for Notes
|
||
/// {
|
||
/// # type Message = Msg;
|
||
/// # fn app_id( &self ) -> &str { "net.example.Notes" }
|
||
/// # fn save_state( &self ) -> Option<Vec<u8>> { None }
|
||
/// # fn restore_state( &mut self, _: Vec<u8> ) {}
|
||
/// # fn view( &self ) -> Element<Msg> { text( "" ).into() }
|
||
/// # fn update( &mut self, _: Msg ) {}
|
||
/// fn on_back( &mut self ) -> BackResponse<Msg>
|
||
/// {
|
||
/// if self.searching { return BackResponse::Message( Msg::CloseSearch ); }
|
||
/// match self.screen
|
||
/// {
|
||
/// Screen::Detail => BackResponse::Message( Msg::Show( Screen::List ) ),
|
||
/// Screen::List => BackResponse::Unhandled,
|
||
/// }
|
||
/// }
|
||
/// }
|
||
/// ```
|
||
fn on_back( &mut self ) -> BackResponse<Self::Message> { BackResponse::Unhandled }
|
||
|
||
/// What a back request does when [`Self::on_back`] had nothing to go
|
||
/// back to. Default: [`BackAtRoot::Minimize`] — the window leaves the
|
||
/// screen and the app keeps its state, the way a root activity behaves
|
||
/// on Android.
|
||
///
|
||
/// Only applies to [`ShellMode::Window`] surfaces; layer-shell and
|
||
/// session-lock surfaces never react to back at the root.
|
||
fn back_at_root( &self ) -> BackAtRoot { BackAtRoot::Minimize }
|
||
|
||
/// 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<Self::Message> { 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<Self::Message> { 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 while a vertical `scroll` is pinned at
|
||
/// its top and the finger keeps pulling down. `progress` is the
|
||
/// accumulated overscroll as a fraction of the surface height (≥ 0.0,
|
||
/// unbounded). Use it to drive a pull-from-top-to-dismiss panel that
|
||
/// tracks the finger.
|
||
fn on_overscroll_down_progress( &mut self, _progress: f32 ) {}
|
||
|
||
/// Called when the finger releases after a top overscroll pull-down.
|
||
/// `committed` is `true` when the pull passed
|
||
/// [`overscroll_dismiss_threshold`](Self::overscroll_dismiss_threshold).
|
||
fn on_overscroll_down( &mut self, _committed: bool ) -> Option<Self::Message> { None }
|
||
|
||
/// Fraction of surface height a top overscroll pull-down must exceed to
|
||
/// commit a dismiss on release. Default: `0.25`.
|
||
fn overscroll_dismiss_threshold( &self ) -> f32 { 0.25 }
|
||
|
||
/// 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<Self::Message> { 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<Self::Message> { 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<Self::Message> { None }
|
||
|
||
/// Called when a text-input widget gains or loses focus.
|
||
fn on_text_input_focused( &mut self, _active: bool ) {}
|
||
|
||
/// `Some( serial )` holds the input method for a text field ltk does not
|
||
/// own (an editable in an embedded web view) while no ltk text widget is
|
||
/// focused; a new `serial` renews the request, e.g. on a press on the field.
|
||
fn external_text_input( &self ) -> Option<u64> { None }
|
||
|
||
/// What the field held by [`Self::external_text_input`] is for; `Terminal` gives the on-screen keyboard its row of terminal keys.
|
||
fn external_text_purpose( &self ) -> crate::osk::Purpose { crate::osk::Purpose::Normal }
|
||
|
||
/// Text committed by the input method while [`Self::external_text_input`] holds it.
|
||
fn on_external_text_commit( &mut self, _text: &str ) {}
|
||
|
||
/// Delete around the external field's cursor, in UTF-8 bytes as `zwp_text_input_v3` counts.
|
||
fn on_external_text_delete( &mut self, _before: u32, _after: u32 ) {}
|
||
|
||
/// `true` to draw ltk's own on-screen keyboard under a focused text field even when the compositor offers an input method; it is drawn regardless when none is offered, or with `LTK_BUILTIN_OSK=1`.
|
||
fn builtin_keyboard( &self ) -> bool { false }
|
||
|
||
/// 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<Self::Message>
|
||
{
|
||
None
|
||
}
|
||
|
||
/// Return `Some(id)` once to focus the widget with that [`WidgetId`](crate::types::WidgetId).
|
||
/// The app must return `None` after the first call.
|
||
///
|
||
/// The runtime waits a few drawn frames for the widget to appear, so a
|
||
/// request made from the `update` that adds the widget to the view is
|
||
/// honoured, and a target scrolled out of a viewport is brought into
|
||
/// view before it is focused. A widget that never appears is dropped.
|
||
fn take_focus_request( &mut self ) -> Option<crate::types::WidgetId> { None }
|
||
|
||
/// Return `true` once to drop the keyboard focus, which folds the on-screen keyboard; a [`take_focus_request`](Self::take_focus_request) in the same cycle wins.
|
||
fn take_blur_request( &mut self ) -> bool { false }
|
||
|
||
/// Return `Some( text )` once to put it in the clipboard, for other applications as well.
|
||
fn take_clipboard_request( &mut self ) -> Option<String> { None }
|
||
|
||
/// Return `Some( text )` once to make it the primary selection, which the middle button pastes, here and in other applications.
|
||
fn take_primary_request( &mut self ) -> Option<String> { None }
|
||
|
||
/// Return `Some( kind )` once to have [`Self::on_paste`] called with the text that selection holds.
|
||
fn take_paste_request( &mut self ) -> Option<SelectionKind> { None }
|
||
|
||
/// The text of the selection [`Self::take_paste_request`] asked for; not called when it is empty.
|
||
fn on_paste( &mut self, _text: &str ) {}
|
||
|
||
/// Called on every pointer motion (mouse move, button press, button
|
||
/// release) and on every primary-finger touch down and motion. `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 — or for wheel-driven view state such
|
||
/// as a stepped carousel. The runtime rebuilds and repaints after
|
||
/// the hook returns, so state mutated here shows immediately.
|
||
fn on_pointer_axis( &mut self, _x: f32, _y: f32, _dx: f32, _dy: f32 ) {}
|
||
|
||
/// Called before [`Self::on_pointer_axis`] for a scroll event from a
|
||
/// wheel or any device that reports discrete detents, with the number
|
||
/// of detents on each axis (fractional on high-resolution wheels;
|
||
/// positive is right / down). Return `true` to consume it; `false`, the
|
||
/// default, lets the same event reach `on_pointer_axis` in raw units.
|
||
fn on_pointer_wheel( &mut self, _x: f32, _y: f32, _notches_x: f32, _notches_y: f32 ) -> bool { false }
|
||
|
||
/// 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`].
|
||
///
|
||
/// When [`Self::claims_raw_touch`] returns `true` the primary
|
||
/// finger arrives here as well, instead of driving the gesture
|
||
/// machine.
|
||
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 ) {}
|
||
|
||
/// Return `true` to receive the *primary* finger through the raw
|
||
/// [`Self::on_touch_down`] / move / up stream as well, bypassing
|
||
/// the built-in single-slot gesture machine entirely — widget
|
||
/// presses, taps and swipes stop working on touch. For apps whose
|
||
/// surface is one self-contained input consumer (an embedded
|
||
/// WebView, a game canvas) that needs every finger verbatim.
|
||
fn claims_raw_touch( &self ) -> bool { false }
|
||
|
||
/// Per-press variant of [`Self::claims_raw_touch`]: return `true` to
|
||
/// take the finger landing at `(x, y)` (physical pixels of the main
|
||
/// surface) through the raw [`Self::on_touch_down`] / move / up stream,
|
||
/// while fingers landing elsewhere keep driving the widget gesture
|
||
/// machine. For apps that embed a self-contained input consumer (a
|
||
/// WebView) next to regular widgets. A claimed press clears the widget
|
||
/// focus. Only consulted for the main surface.
|
||
fn claims_raw_touch_at( &self, _x: f32, _y: f32 ) -> bool { false }
|
||
|
||
/// 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, and on every
|
||
/// primary-finger touch down / up, 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 ) {}
|
||
|
||
/// A secondary-button press that no widget of ltk's own answered, in physical pixels. Default: no-op.
|
||
fn on_pointer_secondary( &mut self, _x: f32, _y: f32 ) {}
|
||
|
||
/// A middle-button press, in physical pixels. Default: no-op.
|
||
fn on_pointer_middle( &mut self, _x: f32, _y: f32 ) {}
|
||
|
||
/// 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.
|
||
///
|
||
/// `XF86_Back` is not reported here: it is a navigation request, not
|
||
/// input for the embedded system, and goes through [`Self::on_back`].
|
||
fn on_raw_key( &mut self, _keysym: Keysym, _keycode: u32, _pressed: bool, _ctrl: bool, _shift: bool ) {}
|
||
|
||
/// [`Self::on_raw_key`] with the Alt state and the typed text too; the runtime calls this one, which forwards to `on_raw_key` unless overridden. Override it instead when Alt or dead keys matter, as in a terminal.
|
||
fn on_raw_key_event( &mut self, key: &RawKey )
|
||
{
|
||
self.on_raw_key( key.keysym, key.keycode, key.pressed, key.ctrl, key.shift );
|
||
}
|
||
|
||
/// `true` while every key belongs to [`Self::on_raw_key`], as in a terminal: ltk then neither dispatches keys to its widgets, so Enter and Space do not press the focused or hovered one, nor repeats them. `XF86_Back` still goes through [`Self::on_back`].
|
||
fn claims_keyboard( &self ) -> bool { false }
|
||
|
||
/// 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<Self::Message> ) {}
|
||
|
||
/// 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<std::time::Duration> { 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<std::time::Duration> { 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<std::time::Duration> { 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<crate::types::CursorShape> { None }
|
||
|
||
/// The cursor the app wants at `( x, y )`, in physical pixels of the main surface: below the window's resize edges and [`Self::cursor_override`], above the cursor of the widget under it. `None`, the default, leaves the widget's.
|
||
fn cursor_at( &self, _x: f32, _y: f32 ) -> Option<crate::types::CursorShape> { None }
|
||
|
||
/// Decide whether a given key participates in held-key repeat.
|
||
///
|
||
/// Default: every non-modifier key repeats *except* `Escape`,
|
||
/// `XF86_Back`, `Tab` and `ISO_Left_Tab` — those drive one-shot UI
|
||
/// semantics (dismiss / go back / 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::XF86_Back | 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 }
|
||
|
||
/// Cap [`Self::is_animating`] redraws to ~30 Hz when the **software**
|
||
/// renderer is active. Default `true`: 60 fps software rasterization is a
|
||
/// common battery sink on mobile (no GPU offload), and halving it is
|
||
/// usually imperceptible for the animations software fallback can afford.
|
||
/// The GLES backend is never capped. Return `false` to keep the full
|
||
/// display rate on software.
|
||
fn cap_software_animation( &self ) -> bool { true }
|
||
|
||
/// 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 }
|
||
|
||
/// 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<Vec<crate::types::Rect>> { None }
|
||
|
||
/// Rects (physical layout pixels) of overlay `id` that are fully opaque
|
||
/// this frame, so the compositor can skip blending them and repainting
|
||
/// what lies underneath. `None` (default) declares nothing opaque.
|
||
fn overlay_opaque_region( &self, _id: OverlayId ) -> Option<Vec<crate::types::Rect>> { 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. The `app_id` element is ignored:
|
||
/// [`app_id`](Self::app_id) names the window.
|
||
///
|
||
/// **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 }
|
||
|
||
/// Content for the surfaces the runtime creates on every output beyond
|
||
/// the first. In [`ShellMode::SessionLock`] these are lock surfaces —
|
||
/// the compositor blanks any output without one, so each is covered. In
|
||
/// [`ShellMode::Layer`] on [`Layer::Background`] they are background
|
||
/// layer surfaces, so a shell's wallpaper reaches extended outputs
|
||
/// (typically without the grid or any chrome). They take no input.
|
||
/// `width` / `height` are that output's surface size in physical pixels.
|
||
/// Default `None`: the surface is filled with
|
||
/// [`background_color`](Self::background_color).
|
||
fn secondary_view( &self, _width: u32, _height: u32 ) -> Option<Element<Self::Message>>
|
||
{
|
||
None
|
||
}
|
||
|
||
/// 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.
|
||
///
|
||
/// A hint larger than the screen is shrunk on the first configure,
|
||
/// keeping its aspect ratio: to the compositor's
|
||
/// `xdg_toplevel.configure_bounds` when it sends them, otherwise to
|
||
/// 90 % of the output. A phone-shaped 720x1440 hint therefore opens as
|
||
/// 486x972 on a 1080p desktop instead of running off its bottom edge.
|
||
///
|
||
/// 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 }
|
||
|
||
/// Whether the user may resize an xdg-shell window.
|
||
///
|
||
/// When [`Self::window_size_hint`] is set, ltk pins
|
||
/// `min_size == max_size` before the first commit so the compositor's
|
||
/// first configure honours the requested size, and then releases
|
||
/// both from the first configure so the surface becomes
|
||
/// user-resizable; a minimum left in place would make a mobile
|
||
/// compositor shrink the window's scale to honour it. Returning
|
||
/// `false` skips that release: the pin stays
|
||
/// in place for the lifetime of the toplevel, declaring a fixed-size
|
||
/// window that compositors must not resize or restore a remembered
|
||
/// geometry over.
|
||
///
|
||
/// Ignored when [`Self::window_size_hint`] returns `None`.
|
||
///
|
||
/// **Default**: `true`.
|
||
fn window_resizable( &self ) -> bool { true }
|
||
|
||
/// 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 }
|
||
|
||
/// Desired fullscreen state of the toplevel while the app runs.
|
||
///
|
||
/// Polled after every batch of `update`s: when the returned value
|
||
/// changes, ltk calls `xdg_toplevel.set_fullscreen` /
|
||
/// `unset_fullscreen`. While the compositor reports the window as
|
||
/// fullscreen the built-in titlebar is not painted. `None` (the
|
||
/// default) leaves the window state alone.
|
||
///
|
||
/// Only applies to [`ShellMode::Window`] surfaces.
|
||
fn fullscreen( &self ) -> Option<bool> { None }
|
||
|
||
/// 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<A: App>( 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 app_id( &self ) -> &str { "net.example.MyApp" }
|
||
/// # fn save_state( &self ) -> Option<Vec<u8>> { None }
|
||
/// # fn restore_state( &mut self, _: Vec<u8> ) {}
|
||
/// # fn view( &self ) -> Element<Msg> { 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<A: App>( app: A ) -> Result<(), RunError>
|
||
{
|
||
crate::event_loop::try_run( app )
|
||
}
|
||
|
||
pub use crate::event_loop::RunError;
|