feat(host+inject): pen P1 — per-session uinput virtual tablet, HOST_CAP_PEN live

The Linux injection leg of design/pen-tablet-input.md: 0xCC/0x05 pen batches now
route through the per-session PenTracker into a lazily-created 'Punktfunk Pen'
uinput tablet (BTN_TOOL_PEN/RUBBER, pressure, tilt-from-polar, ABS_Z barrel
roll, hover distance, INPUT_PROP_DIRECT) — compositors pick it up via libinput
and hand apps zwp_tablet_v2 with full fidelity. The host now advertises
HOST_CAP_PEN when /dev/uinput is accessible (PUNKTFUNK_PEN=0 kill-switch);
transitions group into SYN frames so proximity-enter carries its position.
Stroke failsafe: clients heartbeat ≤100ms while in range (documented wire
contract — capture APIs are silent for a stationary pen); 200ms of silence
force-releases. 'punktfunk-host pen-test' draws a pressure-ramped sine stroke
through the real tracker→uinput chain, no client needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 16:18:31 +02:00
co-authored by Claude Fable 5
parent 248e1cbf08
commit aca5aa9993
11 changed files with 662 additions and 13 deletions
+5
View File
@@ -307,6 +307,11 @@ fn real_main() -> Result<()> {
// Standalone input-injection smoke test (no client needed): open the session's input
// backend and inject a scripted mouse/keyboard pattern. Watch a focused app / `wev`.
Some("input-test") => devtest::input_test(),
// Standalone stylus smoke test (no client needed): create the "Punktfunk Pen" virtual
// tablet and draw a pressure-ramped stroke through the real tracker→uinput chain.
// Watch in Krita/GIMP (pressure brush) or `libinput debug-events`.
#[cfg(target_os = "linux")]
Some("pen-test") => devtest::pen_test(),
// Zero-copy FFI/GPU probe: init the EGL importer + CUDA context (no capture needed).
#[cfg(target_os = "linux")]
Some("zerocopy-probe") => zerocopy::probe(),