An Android backend, a platform layer that keeps Wayland out of the runtime, the built-in keyboard for an app's own text input, and APKs packaged with Debian's own tools
ltk only ran on a Wayland compositor: the runtime, the input handlers and the draw paths named smithay-client-toolkit's types throughout, so no other windowing system could be added without touching all of them. Prompt, the Eydos terminal, has to run on an Android phone, with its sessions and its sharing, and needs a toolkit that runs there. The platform layer. Input reaches the runtime as platform-neutral events: `input/event.rs` defines `KeyInput`, `PointerInput` and `TouchInput`, and the pointer, touch and keyboard handlers take them through a few entry points (`pointer_input`, `touch_input`, `key_press`, `key_release`, `key_repeated`, `modifiers_changed`, `keyboard_enter`, `keyboard_leave`, `set_repeat_info`). The sctk handler implementations moved to `platform/wayland/input.rs`, which records the serials later requests need, resolves surfaces and translates. What the runtime asks of the windowing system (moving, resizing, minimising and fullscreening the window, a bare frame request, the cursor's shape, the input method, publishing the clipboard and the primary selection, preparing a canvas, presenting it and its damage) forms the `platform::Platform` trait, with its surface type as `Platform::Surface`. The protocol state moved from `AppData` into `platform::wayland::WaylandPlatform`, held as `AppData::platform`, and each surface's Wayland half (its kind, SHM pool, EGL window surface and what the compositor was last told) from `SurfaceState` into `WaylandSurfaceState`, held as `SurfaceState::platform`. The compositor, shell, seat and output handlers, the data device, the primary selection, the run loop, overlay and subsurface reconciliation, session management, output probing and window fitting moved from `event_loop/` to `platform/wayland/`. Drawing is split from presenting: `draw/paint.rs` paints a surface in full or under its dirty rects, the same for the software and GPU canvases, in place of the four near-identical paths of `draw/software.rs` and `draw/gles.rs`, and the backend presents it (`platform/wayland/present.rs`, which also took the opaque and input regions and `pick_shm_format`). The runtime is generic over the platform, `AppData<A, P: Platform = NativePlatform<A>>` and `SurfaceState<Msg, S = NativeSurface>`, resolved at compile time. The backend-neutral half of a loop iteration is `event_loop/step.rs` (`process_messages`, `draw_due`, `next_wakeup`, `before_draw`, `after_draw`), and `event_loop/setup.rs` builds the state and the event sources every backend has; the accessibility tree and its actions moved to `event_loop/a11y.rs`. `platform/mock.rs` is a platform that logs what is asked of it, and `event_loop/tests.rs` drives the runtime on it through input, update, painting and presentation. The behaviour on Wayland is unchanged, except that a frame whose presentation fails leaves the next one to repaint in full. `ltk::Keysym` now comes from `xkeysym`, the crate sctk re-exported it from, so it is the same type. Features. `wayland`, on by default, brings the Wayland backend and its dependencies, now optional, AT-SPI2 accessibility among them. `android` brings android-activity 0.6, ndk 0.9 and jni 0.21. A new `build.rs` refuses both at once and sets `cfg( backend )` when one is on, which builds the runtime behind `run` and `try_run`. Without a backend only the runtime-free core builds: `UiSurface`, widgets, layout, theming and rendering. The Android backend. `ltk::run` runs inside a NativeActivity once `android_main` has handed its `AndroidApp`, re-exported as `ltk::AndroidApp`, to the new `ltk::set_android_app`; without it `run` fails with `RunError::NoAndroidApp`. The first call in the process sends standard output and error, panics included, to logcat, sets `HOME` to the app's internal storage and, unless set, `SSL_CERT_DIR` to Android's CA certificates and `LANG` to the device's language, and unpacks the bundled resources; every call keeps the latest activity, since Android starts the next one in the same process. The backend draws through EGL on the activity's window, or in software into a locked window buffer, and sizes the main surface at the display's density in whole multiples of 160 dpi. Fingers and styluses become touches, a mouse the pointer, and key events keysyms and text through the device's character map. calloop's descriptor rides on the activity's `ALooper`, so the app's channel and timers keep working. Text is typed on ltk's own keyboard, since a NativeActivity gets only key events from the system's. Through JNI on the activity (`platform/android/java.rs`): copies go to `ClipboardManager` and its text is read back whenever the app gains the focus; back with nothing left to go back to minimises the app with `moveTaskToBack` or closes it, as `App::back_at_root` says, which the new `Platform::back_leaves` decides per backend; and the window's insets come from `getRootWindowInsets()` on Android 11 and later, the status bar and any cutout on top and only bars that take taps on the other edges, so there is no gap above the view nor below it in gesture navigation, and a bar of buttons still keeps its room; earlier releases fall back to the content rect. When the app's last surface closes, ltk calls `finishAndRemoveTask` and waits for the activity to be destroyed, since a second activity started in a process whose `android_main` has returned never came up; an app should end the process after `run` returns. `ltk::keep_running( Some( text ) )` starts a foreground service with `text` on an ongoing notification, so Android does not freeze the app once it leaves the screen, and `None` stops it. Surface insets. `SurfaceState::insets` holds the logical pixels the system covers along each edge; the view is laid out inside them while the background fills the whole surface, and positions stay absolute, so hit testing is unchanged. The built-in keyboard is placed inside them too, above a bar of navigation buttons. On Wayland they stay zero. Resources in a package. `ltk::set_resource_dir( dir )` names an app's own copy of ltk's resources and `ltk::resource_dir()` returns it, so an app also finds its own bundled files there. Its `themes/` is searched after the user's themes and before `/usr/share/ltk/themes`, its `keyboards/` before `/usr/share`, and a font a theme names by an absolute path that does not exist is read from its `fonts/` by file name, so a theme written against Debian's paths works unchanged. On Android `set_android_app` unpacks `assets/ltk/` into the internal storage and uses it as the resource directory; `assets/ltk/MANIFEST` lists the tree, since the asset manager cannot list subdirectories, and its first line, a hash of the contents, decides whether to unpack again. On Android the per-script fallback fonts come from `/system/fonts` and the primary face is the embedded Sora. APKs. `scripts/android-assets.sh` builds that tree from installed themes, without their cursors, with the fonts each `theme.json` names and ltk's keyboard layouts. `scripts/android-apk.sh OUT.apk LIB.so PACKAGE LABEL [ASSETS_DIR]` packages an app built as a `cdylib` with Debian's aapt, zipalign and apksigner, with no Gradle and no SDK download: it writes the manifest, with the library loaded as `libmain.so`, optional permissions and a theme without an action bar, which the content rect would otherwise leave room for; links it against `android-framework-res`; adds the assets and the library; aligns and signs, creating a debug keystore when there is none. `ICON` takes an SVG, rasterised with `rsvg-convert` for every density, or a PNG. `KEEP_ALIVE=1` compiles `java/net/liberux/ltk/KeepAlive.java` with javac and `dx` from art-standalone against the platform 34 `android.jar`, against which aapt then links too, since Debian's framework-res predates the `specialUse` service type, and declares the service with `KEEP_ALIVE_USE` as its reason and the permissions it needs. `examples/android_hello.rs` is the smoke test: `make android-check` typechecks the backend with Debian's rustc, `rust-src` and NDK through `-Zbuild-std`, `make android-apk` and `make android-install` package and install the example, `make deps` and `make android-deps` install what the targets need, and `make install` now copies `examples`, `scripts`, `java` and `build.rs` into the registry. The built-in keyboard for an app's own text input. While `App::external_text_input` asks for text and no ltk text field has the focus, the built-in keyboard shows with `App::external_text_purpose`, so a terminal gets its row of Escape, Tab, Ctrl, Alt and arrows, comes back when the serial changes and hides when the request ends. Its keys reach the app as physical ones, pressed and released through `App::on_raw_key_event` with the panel's Ctrl and Alt held for them. Before, it only served ltk's text fields, so such an app had no keyboard where the compositor offers no input method. Clippy. `make clippy` is clean again: derived `Default`s replace the hand-written ones, `div_ceil`, `then_some`, struct update syntax for strokes, the map entry API in the text renderer, function paths in place of closures in the examples and tests, and fewer casts and explicit lifetimes. `README.md`, `docs/architecture.md` and `CHANGELOG.md` describe the features, the backend, the events and their units, and what stays with a backend. Not done. On Android there are no overlays, secondary surfaces or subsurfaces, no dead keys from a physical keyboard and no saved state for the activity. Why a second activity in the same process never comes up was not found. The keep-alive service does not ask for the notification permission, so on Android 13 and later its notification may not show until it is granted in the app's settings.
This commit is contained in:
+35
-229
@@ -1,83 +1,22 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::
|
||||
{
|
||||
compositor::CompositorState,
|
||||
output::OutputState,
|
||||
registry::RegistryState,
|
||||
seat::SeatState,
|
||||
shell::{ wlr_layer::LayerShell, xdg::XdgShell },
|
||||
shm::Shm,
|
||||
session_lock::SessionLock,
|
||||
subcompositor::SubcompositorState,
|
||||
};
|
||||
use smithay_client_toolkit::reexports::client::
|
||||
{
|
||||
protocol::
|
||||
{
|
||||
wl_keyboard::WlKeyboard,
|
||||
wl_output::WlOutput,
|
||||
wl_pointer::WlPointer,
|
||||
wl_surface::WlSurface,
|
||||
wl_touch::WlTouch,
|
||||
},
|
||||
QueueHandle,
|
||||
};
|
||||
use wayland_protocols::wp::text_input::zv3::client::
|
||||
{
|
||||
zwp_text_input_manager_v3::ZwpTextInputManagerV3,
|
||||
zwp_text_input_v3::ZwpTextInputV3,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::app::{ App, OverlayId, SubsurfaceId };
|
||||
use crate::egl_context::EglContext;
|
||||
use crate::app::{ App, OverlayId };
|
||||
use crate::platform::{ NativePlatform, Platform };
|
||||
use crate::types::Point;
|
||||
|
||||
use super::repeat::{ ButtonRepeatState, KeyRepeatState };
|
||||
use super::subsurface::SubsurfaceSlot;
|
||||
use super::surface::{ SurfaceFocus, SurfaceState };
|
||||
use super::tooltip::{ TooltipPending, TooltipVisible };
|
||||
|
||||
pub struct AppData<A: App>
|
||||
/// The runtime's state for one app, on the platform `P`: the build's backend unless a test supplies another.
|
||||
pub struct AppData<A: App, P: Platform = NativePlatform<A>>
|
||||
{
|
||||
pub app: A,
|
||||
pub registry_state: RegistryState,
|
||||
pub seat_state: SeatState,
|
||||
pub output_state: OutputState,
|
||||
pub compositor_state: CompositorState,
|
||||
/// `wl_subcompositor` binding for [`crate::app::App::subsurfaces`].
|
||||
/// `None` when the compositor does not advertise the protocol.
|
||||
pub subcompositor: Option<SubcompositorState>,
|
||||
pub shm: Shm,
|
||||
pub session_lock: Option<SessionLock>,
|
||||
/// Process-wide EGL context (display + GLES context). `None` when EGL
|
||||
/// failed to initialise or `LTK_FORCE_SOFTWARE=1` — every surface then
|
||||
/// falls back to the SHM path.
|
||||
pub egl_context: Option<Arc<EglContext>>,
|
||||
#[ allow( dead_code ) ]
|
||||
pub xdg_shell: Option<XdgShell>,
|
||||
/// Shared layer-shell binding used for the main surface and every
|
||||
/// overlay. `None` when the compositor does not advertise the protocol.
|
||||
pub layer_shell: Option<LayerShell>,
|
||||
pub keyboard: Option<WlKeyboard>,
|
||||
pub pointer: Option<WlPointer>,
|
||||
pub touch: Option<WlTouch>,
|
||||
pub platform: P,
|
||||
pub pointer_pos: Point,
|
||||
/// Process-wide cursor-shape manager (`wp_cursor_shape_v1`).
|
||||
/// `None` when the compositor does not advertise the protocol —
|
||||
/// the runtime then leaves cursor shape to the compositor's
|
||||
/// defaults.
|
||||
pub cursor_shape_manager: Option<smithay_client_toolkit::seat::pointer::cursor_shape::CursorShapeManager>,
|
||||
/// Per-pointer cursor-shape device, populated when the compositor
|
||||
/// supports cursor-shape AND the seat has a pointer capability.
|
||||
pub cursor_shape_device: Option<smithay_client_toolkit::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
|
||||
/// Last `Enter` serial seen on a pointer event. Required by
|
||||
/// `wp_cursor_shape_device_v1::set_shape` per spec — the
|
||||
/// compositor tags every cursor change with the entry serial.
|
||||
pub last_pointer_enter_serial: u32,
|
||||
/// The cursor shape currently active on the pointer. `None`
|
||||
/// means "we have not pushed any shape since the last
|
||||
/// `wl_pointer.enter`" — the compositor is showing whatever the
|
||||
@@ -86,8 +25,6 @@ pub struct AppData<A: App>
|
||||
/// surface. `Some(s)` lets the dispatch short-circuit when the
|
||||
/// target equals what we already sent.
|
||||
pub current_cursor_shape: Option<crate::types::CursorShape>,
|
||||
pub text_input_manager: Option<ZwpTextInputManagerV3>,
|
||||
pub text_input: Option<ZwpTextInputV3>,
|
||||
/// No input method to ask for: the runtime draws its own keyboard under a focused text field.
|
||||
pub builtin_osk: bool,
|
||||
/// Whether the currently focused text field is `secure` (password).
|
||||
@@ -96,53 +33,11 @@ pub struct AppData<A: App>
|
||||
pub text_input_purpose: crate::osk::Purpose,
|
||||
/// Serial of the [`App::external_text_input`] request the text-input is held for.
|
||||
pub external_text_input: Option<u64>,
|
||||
/// `xdg-activation-v1`. Present when the compositor advertises the
|
||||
/// global. Used on first configure to honour an incoming
|
||||
/// `XDG_ACTIVATION_TOKEN` (a launcher that spawned us wants the
|
||||
/// main surface raised to focus) and exposed via
|
||||
/// [`App::request_activation_token`] for outbound requests.
|
||||
pub activation_state:
|
||||
Option<smithay_client_toolkit::activation::ActivationState>,
|
||||
/// Honour the activation token exactly once — after the first
|
||||
/// successful configure of the main surface. Subsequent configures
|
||||
/// (resizes, scale changes) must not re-activate.
|
||||
pub activation_token_pending: Option<String>,
|
||||
|
||||
/// `xdg-session-management-v1` proxies plus the on-disk store; the
|
||||
/// store is `None` when persistence is off (layer / lock surfaces,
|
||||
/// unusable app_id, concurrent instance, or after `replaced`).
|
||||
pub session: super::session::SessionRuntime,
|
||||
|
||||
/// `wl_data_device_manager` binding. `None` when the compositor
|
||||
/// does not advertise the global, in which case copy / paste stays
|
||||
/// process-local and inbound selections from other clients are
|
||||
/// invisible.
|
||||
pub data_device_manager:
|
||||
Option<smithay_client_toolkit::data_device_manager::DataDeviceManagerState>,
|
||||
/// Per-seat `wl_data_device` handle, created the first time a seat
|
||||
/// with a keyboard or pointer capability appears. Required for
|
||||
/// `set_selection` and to receive inbound `selection` events.
|
||||
pub data_device:
|
||||
Option<smithay_client_toolkit::data_device_manager::data_device::DataDevice>,
|
||||
/// Currently-published outbound selection source. Held alive
|
||||
/// across [`DataSourceHandler::send_request`] so the cached
|
||||
/// clipboard text can be re-served on each paste from the peer.
|
||||
pub clipboard_source:
|
||||
Option<smithay_client_toolkit::data_device_manager::data_source::CopyPasteSource>,
|
||||
/// Sender half of the cross-thread channel used to ferry inbound
|
||||
/// selection bytes (a worker thread drains the read pipe).
|
||||
pub clipboard_inbox_tx: std::sync::mpsc::Sender<String>,
|
||||
/// Receiver half — drained once per run loop iteration.
|
||||
pub clipboard_inbox_rx: std::sync::mpsc::Receiver<String>,
|
||||
pub primary: super::primary_selection::PrimarySelection,
|
||||
|
||||
/// Cross-application drag-and-drop: best mime negotiated on
|
||||
/// `enter` and the last pointer position (in logical surface
|
||||
/// coords). Cleared on `leave` and after `drop_performed`.
|
||||
pub drop_position: Option<( f64, f64 )>,
|
||||
pub drop_mime: Option<String>,
|
||||
pub drop_inbox_tx: std::sync::mpsc::Sender<super::data_device::DropPayload>,
|
||||
pub drop_inbox_rx: std::sync::mpsc::Receiver<super::data_device::DropPayload>,
|
||||
/// The selection the middle button pastes, ours or another application's.
|
||||
pub primary_selection: String,
|
||||
/// The selection of the focused text field when it was last looked at.
|
||||
pub primary_field: Option<String>,
|
||||
|
||||
/// AccessKit / AT-SPI2 adapter. `None` when the platform adapter
|
||||
/// could not be created (no AT-SPI2 daemon on the session bus,
|
||||
@@ -152,16 +47,6 @@ pub struct AppData<A: App>
|
||||
/// the per-frame tree update and the per-iteration action
|
||||
/// inbox drain.
|
||||
pub a11y: Option<crate::a11y::A11yState>,
|
||||
/// Client-side handle to `ext-foreign-toplevel-list-v1`. Holds the
|
||||
/// global proxy plus the live list of open toplevels; SCTK fans
|
||||
/// events through this object into our `ForeignToplevelListHandler`
|
||||
/// impl in `handlers.rs`, which then routes them to
|
||||
/// `App::on_toplevel_event`. Present on every session, even when
|
||||
/// the compositor does not advertise the global — internally the
|
||||
/// list holds a `GlobalProxy` that simply yields no toplevels in
|
||||
/// that case.
|
||||
pub foreign_toplevel_list:
|
||||
smithay_client_toolkit::foreign_toplevel_list::ForeignToplevelList,
|
||||
pub shift_pressed: bool,
|
||||
pub ctrl_pressed: bool,
|
||||
pub alt_pressed: bool,
|
||||
@@ -187,12 +72,9 @@ pub struct AppData<A: App>
|
||||
/// long-press promotion.
|
||||
pub button_repeat: Option<ButtonRepeatState>,
|
||||
/// Clipboard buffer. Copy / Cut store the selected text here;
|
||||
/// Paste retrieves from here. Synchronised with the Wayland
|
||||
/// selection through `wl_data_device_manager` when the compositor
|
||||
/// advertises the global: outbound copies publish a
|
||||
/// `CopyPasteSource`, and inbound selections from other clients
|
||||
/// land here through a worker thread (see
|
||||
/// [`super::data_device`]).
|
||||
/// Paste retrieves from here. Outbound copies are offered to other
|
||||
/// applications through [`Platform::publish_clipboard`]; the backend
|
||||
/// writes their selections here.
|
||||
pub clipboard: String,
|
||||
/// Timestamp of the previous press (pointer or touch). Combined
|
||||
/// with [`Self::last_press_pos`] it lets the press handler
|
||||
@@ -211,32 +93,16 @@ pub struct AppData<A: App>
|
||||
pub pending_drag_inits: Vec<Point>,
|
||||
pub tooltip_pending: Option<TooltipPending>,
|
||||
pub tooltip_visible: Option<TooltipVisible>,
|
||||
pub qh: QueueHandle<Self>,
|
||||
/// Last pointer serial (needed for interactive move).
|
||||
pub last_pointer_serial: u32,
|
||||
/// Last serial from any input event. Required by `xdg_popup.grab`,
|
||||
/// which only honours serials from recent input events on the same
|
||||
/// seat.
|
||||
pub last_input_serial: u32,
|
||||
/// Set to true when the app has accepted a close request.
|
||||
pub exit_requested: bool,
|
||||
/// First-configure latch for `App::start_fullscreen`.
|
||||
pub pending_fullscreen: bool,
|
||||
/// Last value of `App::fullscreen` sent to the compositor.
|
||||
pub applied_fullscreen: Option<bool>,
|
||||
/// First-configure latch: clears `max_size` after the compositor
|
||||
/// has honoured the pinned initial size.
|
||||
pub pending_size_hint_unpin: bool,
|
||||
/// First-configure latch: check the size hint against the screen.
|
||||
pub pending_size_hint_fit: bool,
|
||||
/// The size hint once shrunk to fit the screen; replaces the app's.
|
||||
pub fitted_size_hint: Option<( u32, u32 )>,
|
||||
/// Main application surface.
|
||||
pub main: SurfaceState<A::Message>,
|
||||
pub main: SurfaceState<A::Message, P::Surface>,
|
||||
/// Auxiliary layer-shell surfaces keyed by their stable [`OverlayId`].
|
||||
/// Populated and reconciled by the run loop from [`App::overlays`]. Always
|
||||
/// empty until overlay diffing is wired up.
|
||||
pub overlays: HashMap<OverlayId, SurfaceState<A::Message>>,
|
||||
pub overlays: HashMap<OverlayId, SurfaceState<A::Message, P::Surface>>,
|
||||
/// Surfaces covering every output beyond the first, paired with the
|
||||
/// output each one belongs to: session-lock surfaces while locked (the
|
||||
/// compositor blanks any output without one), background layer surfaces
|
||||
@@ -244,20 +110,7 @@ pub struct AppData<A: App>
|
||||
/// extended outputs). These render [`App::secondary_view`] and take no
|
||||
/// input (the input handlers find no focus for them, and keyboard falls
|
||||
/// through to `Main`).
|
||||
pub secondaries: Vec<( WlOutput, SurfaceState<A::Message> )>,
|
||||
/// Output the main surface was created on, so a later `new_output` for
|
||||
/// the same output does not add a duplicate secondary surface.
|
||||
pub main_output: Option<WlOutput>,
|
||||
/// Input-transparent child surfaces keyed by their stable
|
||||
/// [`SubsurfaceId`]. Reconciled each frame from [`App::subsurfaces`].
|
||||
pub subsurfaces: HashMap<SubsurfaceId, SubsurfaceSlot>,
|
||||
/// Shared GLES canvas reused across every subsurface raster. Held here
|
||||
/// (not per-slot) so a subsurface that is dropped and re-created — e.g. a
|
||||
/// lazily-emitted sliding panel — does not recompile the ~14 shader
|
||||
/// programs (hundreds of ms on a mobile GPU) every time it reappears.
|
||||
pub subsurface_gles_canvas: Option<crate::render::Canvas>,
|
||||
/// GLES canvases of destroyed overlays, reused by the next overlay.
|
||||
pub canvas_pool: Vec<crate::render::Canvas>,
|
||||
pub secondaries: Vec<( P::Output, SurfaceState<A::Message, P::Surface> )>,
|
||||
/// Surface currently receiving pointer events (updated on each pointer
|
||||
/// event via its `surface` field).
|
||||
pub pointer_focus: SurfaceFocus,
|
||||
@@ -296,61 +149,31 @@ pub struct AppData<A: App>
|
||||
pub perf: super::perf::PerfState,
|
||||
}
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
impl<A: App, P: Platform> AppData<A, P>
|
||||
{
|
||||
/// Forward a change of [`App::fullscreen`] to the main toplevel.
|
||||
/// Whether the main surface is an application window the user can move, resize and minimise.
|
||||
pub( crate ) fn main_is_toplevel( &self ) -> bool
|
||||
{
|
||||
self.platform.is_toplevel( &self.main.platform )
|
||||
}
|
||||
|
||||
/// Forward a change of [`App::fullscreen`] to the main window.
|
||||
pub( crate ) fn sync_fullscreen( &mut self )
|
||||
{
|
||||
let Some( wanted ) = self.app.fullscreen() else { return };
|
||||
if self.applied_fullscreen == Some( wanted ) { return; }
|
||||
let super::SurfaceKind::Window( ref window ) = self.main.surface else { return };
|
||||
if wanted
|
||||
if self.platform.set_fullscreen( &self.main.platform, wanted )
|
||||
{
|
||||
window.set_fullscreen( None );
|
||||
} else {
|
||||
window.unset_fullscreen();
|
||||
self.applied_fullscreen = Some( wanted );
|
||||
}
|
||||
self.applied_fullscreen = Some( wanted );
|
||||
}
|
||||
|
||||
/// Find which [`SurfaceFocus`] owns the given `WlSurface`, or `None` if it
|
||||
/// does not correspond to any tracked surface. Safe even when the main
|
||||
/// surface is still `Pending` (returns `None`).
|
||||
pub( crate ) fn focus_for_surface(
|
||||
&self,
|
||||
wl: &WlSurface,
|
||||
) -> Option<SurfaceFocus>
|
||||
{
|
||||
if self.main.surface.try_wl_surface() == Some( wl )
|
||||
{
|
||||
return Some( SurfaceFocus::Main );
|
||||
}
|
||||
for ( id, ss ) in self.overlays.iter()
|
||||
{
|
||||
if ss.surface.try_wl_surface() == Some( wl )
|
||||
{
|
||||
return Some( SurfaceFocus::Overlay( *id ) );
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Whether `wl` is one of the secondary per-output surfaces. Pointer and
|
||||
/// touch events landing on them must be dropped rather than falling back
|
||||
/// to `Main` — their coordinates would hit-test the main surface's
|
||||
/// widgets. Keyboard keeps the `Main` fallback so typing works wherever
|
||||
/// the compositor puts the focus.
|
||||
pub( crate ) fn is_secondary_surface( &self, wl: &WlSurface ) -> bool
|
||||
{
|
||||
self.secondaries.iter().any( |( _, ss )| ss.surface.try_wl_surface() == Some( wl ) )
|
||||
}
|
||||
|
||||
/// Borrow the [`SurfaceState`] identified by `focus`. Panics if `focus`
|
||||
/// refers to an overlay that is not currently registered — callers must
|
||||
/// only pass focus values obtained from [`focus_for_surface`] or from the
|
||||
/// only pass focus values obtained from the backend's surface lookup or from the
|
||||
/// per-device focus fields, which the run loop keeps in sync.
|
||||
#[ allow( dead_code ) ]
|
||||
pub( crate ) fn surface( &self, focus: SurfaceFocus ) -> &SurfaceState<A::Message>
|
||||
pub( crate ) fn surface( &self, focus: SurfaceFocus ) -> &SurfaceState<A::Message, P::Surface>
|
||||
{
|
||||
match focus
|
||||
{
|
||||
@@ -363,7 +186,7 @@ impl<A: App> AppData<A>
|
||||
/// Non-panicking variant of [`surface`]. Returns `None` when `focus`
|
||||
/// refers to an overlay that has already been removed — callers on
|
||||
/// async dispatch paths (IME Done, tooltip arm) must use this.
|
||||
pub( crate ) fn try_surface( &self, focus: SurfaceFocus ) -> Option<&SurfaceState<A::Message>>
|
||||
pub( crate ) fn try_surface( &self, focus: SurfaceFocus ) -> Option<&SurfaceState<A::Message, P::Surface>>
|
||||
{
|
||||
match focus
|
||||
{
|
||||
@@ -374,7 +197,7 @@ impl<A: App> AppData<A>
|
||||
|
||||
/// Mutable counterpart of [`surface`].
|
||||
#[ allow( dead_code ) ]
|
||||
pub( crate ) fn surface_mut( &mut self, focus: SurfaceFocus ) -> &mut SurfaceState<A::Message>
|
||||
pub( crate ) fn surface_mut( &mut self, focus: SurfaceFocus ) -> &mut SurfaceState<A::Message, P::Surface>
|
||||
{
|
||||
match focus
|
||||
{
|
||||
@@ -385,7 +208,7 @@ impl<A: App> AppData<A>
|
||||
}
|
||||
|
||||
/// Non-panicking variant of [`surface_mut`].
|
||||
pub( crate ) fn try_surface_mut( &mut self, focus: SurfaceFocus ) -> Option<&mut SurfaceState<A::Message>>
|
||||
pub( crate ) fn try_surface_mut( &mut self, focus: SurfaceFocus ) -> Option<&mut SurfaceState<A::Message, P::Surface>>
|
||||
{
|
||||
match focus
|
||||
{
|
||||
@@ -447,37 +270,20 @@ impl<A: App> AppData<A>
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the main surface size, (re)allocate its rendering target, and
|
||||
// request a redraw. Routes to the GPU or SHM path inside `SurfaceState`
|
||||
// according to whether `self.egl_context` is available.
|
||||
pub( crate ) fn on_configure( &mut self, w: u32, h: u32 )
|
||||
/// Tell the app and the accessibility tree the main surface is now `w × h` logical pixels at its scale, once the backend has resized it.
|
||||
pub( crate ) fn on_main_resized( &mut self, w: u32, h: u32 )
|
||||
{
|
||||
self.main.on_configure( &self.shm, self.egl_context.as_ref(), w, h );
|
||||
if let Some( ref mut a ) = self.a11y
|
||||
{
|
||||
let sf = self.main.scale_factor.max( 1 ) as f64;
|
||||
a.set_window_bounds( ( w as f64 ) * sf, ( h as f64 ) * sf );
|
||||
a.set_window_focus( true );
|
||||
}
|
||||
// First-configure latch: honour an incoming
|
||||
// `XDG_ACTIVATION_TOKEN` exactly once, after the surface has
|
||||
// been mapped (`xdg_activation_v1.activate` is only meaningful
|
||||
// against a configured surface). Compositors that don't
|
||||
// advertise the global leave `activation_state` as `None` and
|
||||
// the call drops the token silently.
|
||||
if let Some( token ) = self.activation_token_pending.take()
|
||||
{
|
||||
if let ( Some( ref activation ), Some( wl ) ) =
|
||||
( self.activation_state.as_ref(), self.main.surface.try_wl_surface() )
|
||||
{
|
||||
activation.activate::<Self>( &wl, token );
|
||||
}
|
||||
}
|
||||
// `on_resize` is documented to deliver **physical** pixels, matching the
|
||||
// coordinate space that the layout passes and the pointer/touch
|
||||
// callbacks (`on_drag_move`, `on_drop`) work in. Wayland's
|
||||
// configure.new_size is surface-local (logical), so multiply by the
|
||||
// current buffer scale before handing the dimensions to the app.
|
||||
// callbacks (`on_drag_move`, `on_drop`) work in. A configure's size is
|
||||
// logical, so multiply by the current buffer scale before handing the
|
||||
// dimensions to the app.
|
||||
let sf = self.main.scale_factor.max( 1 ) as u32;
|
||||
crate::types::set_viewport_size( w * sf, h * sf );
|
||||
crate::types::set_density( sf as f32 );
|
||||
|
||||
Reference in New Issue
Block a user