App: claim raw touch per press with claims_raw_touch_at, and toggle fullscreen at runtime with fullscreen
Two additions to the `App` trait, both needed by an application that embeds a self-contained input consumer next to regular widgets. The case at hand is trust, the Eydos browser: an ltk-webkit `WebView` below a toolbar built from stock widgets, with an F11 fullscreen toggle. Raw touch per press. `App::claims_raw_touch` is all or nothing: once it returns `true` every finger, the primary included, bypasses the gesture machine, so widget presses, taps and swipes stop working on touch. That suits an app whose whole surface is one WebView (the eydos-settings portal), but not one that shows the view under its own chrome: claiming raw touch made the address box and the toolbar buttons dead to the finger, and not claiming it fed the page a mouse-like primary slot, so a drag selected text instead of scrolling and pinch-zoom was impossible. The new `App::claims_raw_touch_at( x, y )` is consulted on `wl_touch.down` with the press position in physical pixels of the main surface; a finger landing where it returns `true` goes through the raw `on_touch_down` / `on_touch_move` / `on_touch_up` stream while fingers landing elsewhere keep driving the gesture machine. The default is `false`, so existing apps are unaffected, and it is only consulted for the main surface: overlays have their own coordinate space and the hook carries no surface argument. The implementation reuses the auxiliary-slot path instead of adding a second notion of raw ownership. In `TouchHandler::down` the position is now resolved before the takeover check; a claimed finger is recorded in `touch_slots` without becoming `primary_touch_id`, which is precisely the state of a second finger today, so `motion`, `up` and `reset_touch_state` already route it to the app and report its release point with no change. A claimed press skips the plain-press takeover (it must not commit a tap in progress on the chrome) and calls `set_focus( focus, None, qh )`, mirroring what a pointer press outside any widget does: without it a focused text field would keep the text-input protocol, the on-screen keyboard and the key stream after the user had moved on to the page. `pointer_pos` is still assigned after the takeover, since `release_primary` uses it as the release point of the finger being committed. Runtime fullscreen. `App::start_fullscreen` only covers the first commit; there was no way to enter or leave fullscreen afterwards. `App::fullscreen() -> Option<bool>` declares the desired state and is polled after every batch of `update`s, next to `requested_exit`. `AppData::sync_fullscreen` compares it with `applied_fullscreen`, the last value sent, and calls `xdg_toplevel.set_fullscreen( None )` or `unset_fullscreen` only on a change; `None`, the default, leaves the window state alone. `applied_fullscreen` is seeded with `start_fullscreen()`, so an app that starts fullscreen and reports `Some( true )` does not send a redundant request, and one that reports `Some( false )` is not pushed out of a fullscreen it asked for a moment earlier. It is a no-op for layer-shell and session-lock surfaces, which have no toplevel. The `configure` handler now also zeroes `titlebar_height` while `WindowConfigure::is_fullscreen()` holds, as it already did for server-side decorations. The built-in titlebar was otherwise painted over a fullscreen surface, which is what the `start_fullscreen` doc comment described as happening "unless the app opts out" without there being a way to opt out. The height follows the compositor's reported state rather than the app's request, so it stays correct when the compositor refuses the request or leaves fullscreen on its own. Both hooks are documented on the trait and listed in the changelog under Unreleased.
This commit is contained in:
@@ -6,6 +6,8 @@ All notable changes to `ltk` are documented here. The format is based on [Keep a
|
||||
|
||||
### Added
|
||||
|
||||
- **`App::claims_raw_touch_at( x, y )`** — per-press variant of `claims_raw_touch`: a finger landing where the app returns `true` rides the raw `on_touch_*` stream through the auxiliary-slot path and clears the widget focus, while fingers landing elsewhere keep driving the gesture machine. Lets an app embed a self-contained input consumer (a WebView) next to regular widgets. Only consulted for the main surface; default `false`.
|
||||
- **`App::fullscreen()`** — desired fullscreen state of the toplevel at runtime, polled after every batch of `update`s; a change is forwarded as `xdg_toplevel.set_fullscreen` / `unset_fullscreen`. The built-in titlebar is not painted while the compositor reports the window as fullscreen. Default `None` (window state left alone).
|
||||
- **`PatternGrid::size( impl Into<Length> )`** — explicit side for the square grid, resolved through `Canvas::resolve_geom` like the other geometry overrides; without it the theme's fluid default still applies. A lock screen can now size the grid from the surface (a share of the width in portrait, of the free height in landscape) instead of being capped at the default.
|
||||
- **`xdg-session-management-v1` (client)** — before the first commit of a `ShellMode::Window` toplevel the runtime binds `xdg_session_manager_v1`, issues `get_session( reason, stored_id )` and `restore_toplevel( toplevel, "main" )`, so a supporting compositor restores window geometry on every launch. Bindings are generated in-tree from the vendored XML under `protocols/` with `wayland-scanner` (`src/protocol/`).
|
||||
- **Runtime session persistence** — `$XDG_STATE_HOME/<app_id>/session.json` (compositor session id, clean-exit marker, pid) plus `state.bin` (the bytes from `App::save_state`), written atomically with mode `0600`; saved every 30 s when the bytes changed, on close and on signal; handed back through `App::restore_state` before the first frame only on a session restore (`LTK_SESSION_RESTORE=1`) or after an unclean exit. Module `src/session_state.rs`.
|
||||
|
||||
+20
@@ -811,6 +811,15 @@ pub trait App: 'static
|
||||
/// 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 ) {}
|
||||
@@ -1058,6 +1067,17 @@ pub trait App: 'static
|
||||
/// **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.
|
||||
|
||||
@@ -215,6 +215,8 @@ pub struct AppData<A: App>
|
||||
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,
|
||||
@@ -283,6 +285,21 @@ pub struct AppData<A: App>
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Forward a change of [`App::fullscreen`] to the main toplevel.
|
||||
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
|
||||
{
|
||||
window.set_fullscreen( None );
|
||||
} else {
|
||||
window.unset_fullscreen();
|
||||
}
|
||||
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`).
|
||||
|
||||
@@ -292,7 +292,7 @@ impl<A: App> WindowHandler for AppData<A>
|
||||
window.set_max_size( None );
|
||||
self.pending_size_hint_unpin = false;
|
||||
}
|
||||
let tb = if configure.decoration_mode == DecorationMode::Server { 0.0 }
|
||||
let tb = if configure.decoration_mode == DecorationMode::Server || configure.is_fullscreen() { 0.0 }
|
||||
else { self.main.titlebar_base };
|
||||
if self.main.titlebar_height != tb
|
||||
{
|
||||
|
||||
@@ -347,6 +347,7 @@ pub( crate ) fn try_run<A: App>( mut app: A ) -> Result<(), RunError>
|
||||
last_input_serial: 0,
|
||||
exit_requested: false,
|
||||
pending_fullscreen,
|
||||
applied_fullscreen: Some( pending_fullscreen ),
|
||||
pending_size_hint_unpin,
|
||||
main: SurfaceState::<A::Message>::new( surface_kind, titlebar_height, titlebar_title ),
|
||||
overlays: std::collections::HashMap::new(),
|
||||
@@ -599,6 +600,8 @@ pub( crate ) fn try_run<A: App>( mut app: A ) -> Result<(), RunError>
|
||||
data.exit_requested = true;
|
||||
}
|
||||
|
||||
data.sync_fullscreen();
|
||||
|
||||
// Seed `on_drag_move` with the long-press origin for any drag that
|
||||
// just started. Must run *after* `update()` so the app's drag state
|
||||
// has already been set up by the paired long-press message — the
|
||||
|
||||
+13
-2
@@ -67,7 +67,9 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
// A second finger landing on a plain press means the first was a
|
||||
// tap: commit it at its last position and hand the slot over, so
|
||||
// alternating thumbs never lose a key.
|
||||
if !self.app.claims_raw_touch()
|
||||
let pos = self.surface( focus ).to_physical( position.0, position.1 );
|
||||
let raw_here = matches!( focus, SurfaceFocus::Main ) && self.app.claims_raw_touch_at( pos.x, pos.y );
|
||||
if !self.app.claims_raw_touch() && !raw_here
|
||||
{
|
||||
let takeover =
|
||||
{
|
||||
@@ -80,7 +82,6 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
}
|
||||
}
|
||||
|
||||
let pos = self.surface( focus ).to_physical( position.0, position.1 );
|
||||
self.pointer_pos = pos;
|
||||
|
||||
// Raw-touch apps take every finger verbatim; the gesture
|
||||
@@ -93,6 +94,16 @@ impl<A: App> TouchHandler for AppData<A>
|
||||
return;
|
||||
}
|
||||
|
||||
// Region claimed through `claims_raw_touch_at`: the finger rides the
|
||||
// auxiliary-slot path, so `motion` / `up` need no special casing.
|
||||
if raw_here
|
||||
{
|
||||
self.set_focus( focus, None, qh );
|
||||
self.surface_mut( focus ).touch_slots.insert( id, pos );
|
||||
self.app.on_touch_down( id as i64, pos.x, pos.y );
|
||||
return;
|
||||
}
|
||||
|
||||
// Compositor re-grab after the origin surface died mid-drag: this
|
||||
// slot is already our primary and the drag state was migrated here,
|
||||
// so treat the redundant `down` as a continuation, not a restart.
|
||||
|
||||
Reference in New Issue
Block a user