Touch at the window border: no GNOME Shell crash, resize and move by touch #8

Closed
asxpi wants to merge 0 commits from asxpi/anotherim-js:fix/touch-resize into develop
First-time contributor

Fixes the GNOME Shell crash on touch at the window's border (#7), and makes resizing and moving the window by touch work (closes #9).

The crash

On GNOME (Wayland, mutter 50.2), a finger near the edge of the window with the app's own title bar could crash GNOME Shell and end the session. A Wayland protocol log of the app showed the cause:

wl_touch.down(313, …)
-> xdg_toplevel.resize(wl_seat, 313, 8)
-> xdg_toplevel.resize(wl_seat, 313, 8)   ← GNOME Shell segfaults
-> xdg_toplevel.resize(wl_seat, 313, 8)

For a window without the system's frame, both tao (on the GtkWindow) and tauri-runtime-wry (on the webview) connect a touch-event handler that calls begin_resize_drag_for_device on every touch event within 5px of the border: the press, each move and the release, all with the same touch serial. The first resize makes mutter cancel the touch; the next one dereferences the cancelled touch's NULL focus_surface in touch_can_grab_surface():

#0 touch_can_grab_surface
#1 meta_wayland_touch_find_grab_sequence
#2 meta_wayland_seat_get_grab_info
#3 xdg_toplevel_resize

The root cause is in mutter (main still has the unchecked dereference; a one-line NULL check fixes it), but until GNOME ships a fix, the app has to send only one grab request per touch.

What changes

  • The page's resize edges ignore fingers (ui/src/views/window-controls.ts): they listen for pointerdown and skip pointerType === 'touch', so WebKit's emulated mousedown no longer becomes a resize with a touch's serial.
  • Tauri's touch handlers are blocked on the GtkWindow and the webview (g_signal_handlers_block_matched on touch-event); neither crate has a switch for them. tao connects its handler only later, from its event loop, so the block runs once the main loop is idle after setup and again whenever a touch begins, sparing ours. WebKit's own touch handling is the view's class handler and is not affected.
  • Our own touch handler replaces them (src-tauri/src/window_frame.rs, linux::touch_resize), on both widgets:
    • Resize: a touch that begins on the page's resize edges (frame_logic::resize_edge: the same band and corners the page draws, with or without the frame) starts one begin_resize_drag_for_device for the touch's device. The rest of that touch is held back from the page and the window, so the request goes out once, with a serial mutter has not cancelled.
    • Move: the first finger down is remembered while it is down. A finger dragging a title-bar region past the usual 4px asks the new window_touch_move command once per touch, and the shell calls begin_move_drag_for_device for that touch; a second ask for the same touch is dropped. tao's drag_window() could not do this: it always moves by the pointer device. The page cancels the pan its root would start, at the first touchmove, so a tap still clicks.
  • Mouse resizing and moving are unchanged.

Commits

  • 0f6a348 window-controls: don't resize from the edges by touch
  • 4089b20 app: block Tauri's touch resizing at the window border
  • fdec5c6 window_frame: resize and move by touch, one request per touch (replaces the blocking-only step of 4089b20 with the handler above)
  • bb9ef5f window_frame: block Tauri's touch handlers once they are connected (the block in 4089b20/fdec5c6 ran before tao had connected its handler, so tao's still fired)

Tested

  • frame_logic unit tests (rustc --test, as make test-shell): 13/13, two new for resize_edge.
  • ui/test/window-frame.test.ts: 42/42, new cases for a finger on the resize edges and a finger dragging the title bar; tsc clean.
  • Flatpak of bb9ef5f on GNOME 50 / mutter 50.2 (carrying the !5375 NULL check, so the log, not the absence of a crash, is the test) with a touchscreen, WAYLAND_DEBUG=client, 22 touches over the edges, a corner, the title bar and the page: every touch that grabs sends exactly one xdg_toplevel.resize or move, with that touch's serial, followed by the compositor's wl_touch.cancel; nothing repeated, taps and scrolling in the page unchanged. The build before bb9ef5f still sent two resizes for a finger moving along the bottom edge.
  • The same Flatpak on stock mutter 50.2 (no NULL check; booted into the previous NixOS generation): 0.4.5 crashed gnome-shell at the first finger on its edge (touch_can_grab_surface, as before), this build did not. The logged touches there: title-bar moves and a corner resize, one request each, then wl_touch.cancel, nothing repeated.

Reported upstream: tao (https://github.com/tauri-apps/tao/issues/1380) and tauri-runtime-wry (https://github.com/tauri-apps/tauri/issues/16276), to start a touch resize on TouchBegin only; the mutter fix is https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/5375.

🤖 Generated with Claude Code

Fixes the GNOME Shell crash on touch at the window's border (#7), and makes resizing and moving the window by touch work (closes #9). ## The crash On GNOME (Wayland, mutter 50.2), a finger near the edge of the window with the app's own title bar could crash GNOME Shell and end the session. A Wayland protocol log of the app showed the cause: ``` wl_touch.down(313, …) -> xdg_toplevel.resize(wl_seat, 313, 8) -> xdg_toplevel.resize(wl_seat, 313, 8) ← GNOME Shell segfaults -> xdg_toplevel.resize(wl_seat, 313, 8) ``` For a window without the system's frame, both tao (on the GtkWindow) and tauri-runtime-wry (on the webview) connect a `touch-event` handler that calls `begin_resize_drag_for_device` on **every** touch event within 5px of the border: the press, each move and the release, all with the same touch serial. The first resize makes mutter cancel the touch; the next one dereferences the cancelled touch's NULL `focus_surface` in `touch_can_grab_surface()`: ``` #0 touch_can_grab_surface #1 meta_wayland_touch_find_grab_sequence #2 meta_wayland_seat_get_grab_info #3 xdg_toplevel_resize ``` The root cause is in mutter (`main` still has the unchecked dereference; a one-line NULL check fixes it), but until GNOME ships a fix, the app has to send only one grab request per touch. ## What changes - **The page's resize edges ignore fingers** (`ui/src/views/window-controls.ts`): they listen for `pointerdown` and skip `pointerType === 'touch'`, so WebKit's emulated `mousedown` no longer becomes a resize with a touch's serial. - **Tauri's touch handlers are blocked** on the GtkWindow and the webview (`g_signal_handlers_block_matched` on `touch-event`); neither crate has a switch for them. tao connects its handler only later, from its event loop, so the block runs once the main loop is idle after setup and again whenever a touch begins, sparing ours. WebKit's own touch handling is the view's class handler and is not affected. - **Our own touch handler replaces them** (`src-tauri/src/window_frame.rs`, `linux::touch_resize`), on both widgets: - **Resize:** a touch that begins on the page's resize edges (`frame_logic::resize_edge`: the same band and corners the page draws, with or without the frame) starts one `begin_resize_drag_for_device` for the touch's device. The rest of that touch is held back from the page and the window, so the request goes out once, with a serial mutter has not cancelled. - **Move:** the first finger down is remembered while it is down. A finger dragging a title-bar region past the usual 4px asks the new `window_touch_move` command once per touch, and the shell calls `begin_move_drag_for_device` for that touch; a second ask for the same touch is dropped. tao's `drag_window()` could not do this: it always moves by the pointer device. The page cancels the pan its root would start, at the first `touchmove`, so a tap still clicks. - Mouse resizing and moving are unchanged. ## Commits - `0f6a348` window-controls: don't resize from the edges by touch - `4089b20` app: block Tauri's touch resizing at the window border - `fdec5c6` window_frame: resize and move by touch, one request per touch (replaces the blocking-only step of `4089b20` with the handler above) - `bb9ef5f` window_frame: block Tauri's touch handlers once they are connected (the block in `4089b20`/`fdec5c6` ran before tao had connected its handler, so tao's still fired) ## Tested - `frame_logic` unit tests (`rustc --test`, as `make test-shell`): 13/13, two new for `resize_edge`. - `ui/test/window-frame.test.ts`: 42/42, new cases for a finger on the resize edges and a finger dragging the title bar; `tsc` clean. - Flatpak of `bb9ef5f` on GNOME 50 / mutter 50.2 (carrying the !5375 NULL check, so the log, not the absence of a crash, is the test) with a touchscreen, `WAYLAND_DEBUG=client`, 22 touches over the edges, a corner, the title bar and the page: every touch that grabs sends exactly one `xdg_toplevel.resize` or `move`, with that touch's serial, followed by the compositor's `wl_touch.cancel`; nothing repeated, taps and scrolling in the page unchanged. The build before `bb9ef5f` still sent two resizes for a finger moving along the bottom edge. - The same Flatpak on **stock** mutter 50.2 (no NULL check; booted into the previous NixOS generation): 0.4.5 crashed gnome-shell at the first finger on its edge (`touch_can_grab_surface`, as before), this build did not. The logged touches there: title-bar moves and a corner resize, one request each, then `wl_touch.cancel`, nothing repeated. Reported upstream: tao (https://github.com/tauri-apps/tao/issues/1380) and tauri-runtime-wry (https://github.com/tauri-apps/tauri/issues/16276), to start a touch resize on `TouchBegin` only; the mutter fix is https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/5375. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
WebKitGTK turns a touch into a mousedown, so a finger on a resize
edge made Tauri/GTK send xdg_toplevel.resize with the touch's serial.
When mutter has already cancelled that touch, touch_can_grab_surface()
dereferences its NULL focus_surface and GNOME Shell segfaults, taking
the session down (mutter 50.2; main has the same code).

Listen for pointerdown instead and ignore pointerType 'touch'. A 6px
edge is no target for a finger anyway; mouse resizing is unchanged.

Refs: #7
Assisted-by: Claude:opus-5
For an undecorated window, tao (on the GtkWindow) and
tauri-runtime-wry (on the webview) each connect a touch-event handler
that calls begin_resize_drag_for_device on every touch event within
5px of the border: the press, each move and the release, all with the
touch's serial. The first resize makes mutter cancel the touch; the
next one dereferences the cancelled touch's NULL focus_surface in
touch_can_grab_surface() and GNOME Shell segfaults (mutter 50.2).
A Wayland log of the test build showed three resize requests with the
same touch serial right before the crash.

Neither crate has a switch for it, so block the connected touch-event
handlers on both widgets once they exist. WebKit's own touch handling
is the view's class handler and is not affected, so the page keeps its
touches; mouse resizing goes through button-press and is unchanged.

Refs: #7
Assisted-by: Claude:opus-5
Blocking Tauri's touch handlers stopped the GNOME Shell crashes but
also took resizing by touch away where it works, such as on a mutter
with the NULL check or on other compositors. And the window could
never be moved by touch: the page started moves from mouse events
only, and tao's drag_window() always moves by the pointer device.

Keep tao's and wry's touch-event handlers blocked, and connect our own
on the GtkWindow and the webview instead:

- Resize: a touch that begins on the page's resize edges
  (frame_logic::resize_edge, the same band and corners the page draws,
  with or without the frame) starts one begin_resize_drag_for_device
  for the touch's device, and the rest of that touch is held back, so
  the request goes out once, with a serial mutter has not cancelled.
- Move: the first finger down is remembered while it is down. A finger
  dragging a title-bar region past the usual 4px asks the new
  window_touch_move command, once per touch, and the shell calls
  begin_move_drag_for_device for that touch; a second ask for the same
  touch is dropped. The page holds back the pan its root would start,
  at the first touchmove, so a tap still clicks.

frame_logic gains resize_edge with unit tests; window-frame.test.ts
gains a touch-drag case.

Refs: #7
Closes: #9
Assisted-by: Claude:opus-5
asxpi changed title from Don't resize the window from its edges by touch to Touch at the window border: no GNOME Shell crash, resize and move by touch 2026-10-13 03:50:14 +00:00
The touch-event handlers of tao and tauri-runtime-wry were blocked in
setup, but tao connects its own only later, from its event loop
(WindowRequest::WireUpEvents), so it was never blocked: a finger moving
within 5px of the bottom edge still sent two xdg_toplevel.resize with
the same touch serial, which crashes GNOME Shell on an unpatched
mutter 50.

Connect our handler first and keep its id, then block every other
touch-event handler on the window and the webview once the main loop
is idle after setup, and again whenever a touch begins, letting ours
go each time. Ours on the window now runs before tao's, so it blocks
tao's even within the first touch. Say so on stderr if no handler of
Tauri's was found.

With WAYLAND_DEBUG=client over 22 touches (edges, a corner, the title
bar, taps in the page): one resize or move per grabbing touch, each
with the touch's serial, none repeated.

Refs: #7
Assisted-by: Claude:opus-5
Owner

Merged into develop by hand as c942779b (feat/0.5.2), released in 0.5.2 (release 101, tag v0.5.2 = 6b73fbc9). Reviewed: one grab per touch on the shell side, the page's touch path beside the mouse one; the only conflicts were the two command lists (window_open from the start-screen work beside window_touch_move).

Merged into develop by hand as c942779b (feat/0.5.2), released in 0.5.2 (release 101, tag v0.5.2 = 6b73fbc9). Reviewed: one grab per touch on the shell side, the page's touch path beside the mouse one; the only conflicts were the two command lists (window_open from the start-screen work beside window_touch_move).
ann closed this pull request 2026-10-15 18:02:32 +00:00
ann referenced this pull request from a commit 2026-10-27 11:52:18 +00:00
Android lock fixes from the final audit of 2026-10-27 (findings 1, 3, 4, 7, 8, 9, 10, 11): every lock now runs on DeviceLock's one worker and looks at the store itself (storage info) when its turn comes, so a screen-off heard while the silent reopen is still opening the tier closes it again instead of being dropped (H1), and a reopen or a fingerprint open that ends with the phone already off or locked is closed again at once (DeviceLock.opened, called from MainActivity.presence after Relock.keep); behind a secure lock an open split store closes at every lock whether or not a copy of K2 is held, the copy deciding only whether the unlock is silent, and with no copy the page is told __anotherDeviceLocked('fingerprint') and the gate says why it asks (M3, new string in en ru uk et es); a close libtacky holds back for a call or a transfer (close_main closed 0) sets DeviceLock.closeHeld, the bridge's deviceClosePending(status) makes the page take that unlocked store as call (shellStatus in store.begin and recheck), so a fresh window after a swipe during a call gets the fingerprint, and open-main over the held close releases K2 through the prompt and calls the close off, or opens the tier with it where the close went meanwhile (M4); the page publishes its drafts and answers deviceLockFlushed after a request round trip before close_main goes (at most a second, #11); the gate offers the fingerprint after RELOCK_WAIT_MS (15 s) of a reopen that never comes (#10); Relock.take sleeps for its retry outside the object's lock (#9); EnrolmentProbe counts an unexpected Keystore exception on a made probe as a change, fail closed (#8); the headers of DeviceLock, Relock and docs/push.md now say K2 passes through JVM strings that are released, not zeroed (#7, K2 over JNI as bytes left for later). Ringing still counts as busy in libtacky's MainTierBusy (libtacky's, not changed here). Tests: RelockTest 8 new (screen off during the reopen with the keyguard locked and within the lock delay, both failing on the old code; an open ending behind the lock; a held close keeping a fresh window at the fingerprint and called off by the prompt, or opened by it where it went; the close waiting for the page's drafts; a take's retry not holding drop), DeviceLockTest and WatchesTest updated for the close without a copy, KeystoreSealersTest for the fail-closed probe; device-lock.test.ts and storage-gate.test.ts 6 new; JVM free 485, gms 488, all passing; make check STRICT=1 clean; make test-ui 380 files, 4168 passed, 8 skipped; make test-shell ok.

Pull request closed

Sign in to join this conversation.
No description provided.