evidence · 5.4 KiB

README.md

Committed at docs/evidence/keyboard-drag-usable-area-gap/README.md. This is what grounds the requirements that cite it.

Open raw source

Keyboard-drag exclusive-zone gap: before/after

Headless QEMU, IPC-injected touch only. Not a board, panel, or real-finger observation. This is qemu-riscv64-static running the real sway-unwrapped-riscv64-unknown-linux-gnu-1.12 card-shell compositor, the real wvkbd-mobintl keyboard, and the synthetic card-composition-probe-client, driven via card_shell test-touch IPC.

The reported bug

Dragging the keyboard down (or up) resizes the ordinary-maximized app’s usable area every ~16ms animation tick (card_shell_keyboard_adjust_usable/ordinary_resize in nix/card-shell/adapter.c). A real client cannot redraw and commit a matching buffer that fast on this CPU, so its old-sized buffer can still be attached when the container box has already grown — the margin between the old buffer and the keyboard’s new (lower) position had nothing painted in it, exposing whatever sits behind the app.

Reproduction and fix

tests/keyboard_drag_backdrop_qemu.py starts the app while the keyboard is already shown (so the client’s committed geometry starts at the shrunk 756px height), then holds a one-finger grip-drag partway through hiding the keyboard without releasing, and samples a pixel in the theoretical gap region. The synthetic client’s own animation loop redraws every frame regardless of size, which never reproduces the bug on its own, so the client was given a --stall-resize-ms test-only flag (nix/card-composition-probe-client/card-composition-probe-client.c): for a bounded window after any resize it keeps its old, wrong-sized buffer attached instead of redrawing, modeling a slow real app.

The fix (nix/card-shell/adapter.c, ordinary_backdrop_sync) adds a single card-coloured wlr_scene_rect in output->layers.tiling, directly behind ordinary-maximized cards, resized to the output’s current usable_area on every ordinary_sync_usable() call. Repositioning a scene rect is a pure compositor-side operation with no client round trip, so it never lags the animated reservation the way the client’s own buffer can.

Before fixAfter fix
Drag start (keyboard shown, app at its committed 756px height)before-fix-drag-start.pngafter-fix-drag-start.png
Mid-drag, client stalled 4s, sampled at (284, 891)before-fix-mid-drag-gap.pngafter-fix-mid-drag-backdrop.png

At (284, 891) — inside the exposed margin between the stalled app buffer (756px) and the compositor’s already-updated usable area (1026px) — the before-fix sample is (0, 0, 0) (nothing painted; the mid-drag screenshot shows flat black behind the keyboard). The after-fix sample is (36, 73, 90), which is exactly card_color = {.141, .286, .353, 1} (unthemed default) at 8-bit precision. Full numeric results: before-fix-result.json, after-fix-result.json.

This harness has no wallpaper renderer (no Rust launcher is running under it), so “nothing painted” reads as black rather than the desktop wallpaper photo the user described; the mechanism — an unpainted margin between a stale client buffer and the compositor’s already-moved keyboard — is the same one, and the fix closes it regardless of what is stacked behind the app on the real image.

Regression check

tests/test_keyboard_gestures_runtime.py continues to pass unchanged against the fixed build (its held_height/grip_height assertions rely on the same continuous, per-frame usable_area tracking that the fix does not touch — only the backdrop’s own presence is new). Exact command and store paths used for this checkpoint:

nix build .#card-shell --max-jobs 1 --cores 4 --no-link --print-out-paths
# -> /nix/store/zk7g16sasa5vkqqfcd70n5kbnn9jzang-k230-card-shell
#    sway: /nix/store/wwsspnnab2q0gp47fcqb8qyh0l6w2mkd-sway-unwrapped-riscv64-unknown-linux-gnu-1.12
python3 tests/test_keyboard_gestures_runtime.py \
  --sway /nix/store/wwsspnnab2q0gp47fcqb8qyh0l6w2mkd-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway \
  --client /nix/store/bxiz9zkb33m4v97gkgzx91aswfm7cf9f-card-composition-probe-client-riscv64-unknown-linux-gnu-0.1/bin/card-composition-probe-client \
  --keyboard /nix/store/n7p6qfbyw30yany09x8kr95j2ar1fh7b-wvkbd-riscv64-unknown-linux-gnu-0.20/bin/wvkbd-mobintl \
  --output /tmp/k230-ux-2
# PASS native Sway/wvkbd chord, held/reversed pixels, grip and usable area; no physical proof

Gap capture commands (before build from a throwaway worktree pinned to the pre-fix commit a68014de, plus the stall fixture cherry-picked on top; after build from this change):

python3 tests/keyboard_drag_backdrop_qemu.py \
  --sway <pre-fix sway-unwrapped>/bin/sway \
  --client <card-composition-probe-client with --stall-resize-ms> \
  --keyboard <wvkbd-mobintl> --output /tmp/k230-ux-gap-before
python3 tests/keyboard_drag_backdrop_qemu.py \
  --sway <this change's sway-unwrapped>/bin/sway \
  --client <card-composition-probe-client with --stall-resize-ms> \
  --keyboard <wvkbd-mobintl> --output /tmp/k230-ux-gap-after

Remaining gate

This is headless-QEMU injected-touch evidence with a synthetic client and no wallpaper renderer. It does not establish panel touch reachability, real-finger feel, or what the actual Rust launcher’s wallpaper looks like through the gap on real hardware/timing. See the top-level report for the specific real-glass check this still needs.