fix(client): the six Phase 0 touch defects #447

Merged
enricobuehler merged 7 commits from touch/phase-0 into main 2026-08-30 20:38:48 +00:00
7 Commits
Author SHA1 Message Date
enricobuehler ea92f7148d Merge origin/main into touch/phase-0
ci / bun-nix (pull_request) Successful in 1m6s
ci / web (pull_request) Successful in 1m32s
ci / docs-site (pull_request) Successful in 2m2s
apple / swift (pull_request) Successful in 2m19s
ci / docs-drift (pull_request) Failing after 51s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 3m0s
android / android (pull_request) Successful in 7m28s
ci / rust (pull_request) Failing after 8m49s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 8m8s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m8s
main took C ABI 27 for the Steam Controller 2 hidout widening, so
punktfunk_connection_host_caps2 and PUNKTFUNK_HOST_CAP2_TOUCH move to
ABI 28; the header is regenerated. The SC2 passthrough badge and the
touch-fallback badge now stack in the same bottom-centre slot.
2026-08-29 23:33:13 +02:00
enricobuehler 5f5033bc28 feat(core): HOST_CAP2_TOUCH says whether a host injects touch
ci / bun-nix (pull_request) Successful in 33s
ci / docs-site (pull_request) Successful in 1m7s
ci / docs-drift (pull_request) Failing after 1m47s
apple / swift (pull_request) Successful in 2m13s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 2m15s
ci / rust-arm64 (pull_request) Successful in 4m9s
android / android (pull_request) Canceled after 6m17s
ci / rust (pull_request) Canceled after 6m13s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Canceled after 5m49s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Canceled after 0s
Selecting the Touch (passthrough) model against a Hyprland, sway or
Omarchy host did nothing: the wlroots injector drops every contact, and
no capability bit let a client know. Windows below build 1809 fails the
same way. The client offered the mode, the host dropped the events, and
no layer said so.

The host now sets HOST_CAP2_TOUCH in the Welcome's second capability
byte when its live injector carries touch (libei, gamescope EIS, KWin;
the PT_TOUCH probe on Windows). The byte never reached a client before:
NativeClient::host_caps2, punktfunk_connection_host_caps2 and
PUNKTFUNK_HOST_CAP2_TOUCH expose it (C ABI 27, additive). Each client
runs the trackpad model for the session when the bit is absent and shows
a one-line notice at stream start.
2026-08-29 19:50:38 +02:00
enricobuehler b96d727447 feat(android): nativeRequestMode switches the live session's mode
The core and the C ABI can renegotiate a session's resolution and
refresh without reconnecting, and the Apple and desktop clients use it.
Android had no binding, so the in-stream Resolution row the touch
overlay design adds could not work there.

One JNI on the nativeDisconnectQuit template, one external fun.
2026-08-29 19:43:44 +02:00
enricobuehler 3e9b6d123e fix(android): touch on the letterbox bars reaches the gesture engine
On a phone held against a 16:9 stream, a trackpad swipe that started on
a black bar did nothing: the gesture layer sat on the aspect-fitted
picture box, so most of the screen was inert and read as unresponsive.

The gesture layer now spans the whole container. Every absolute
mapping — direct pointer, passthrough, the pen lane — measures against
the picture rect (`videoFitRect`, the same centre-aligned fit the
surface is laid out with) and clamps into it, so a contact on a bar
lands on the nearest picture edge. Trackpad deltas need no rect.
2026-08-29 19:43:42 +02:00
enricobuehler 88213b1bf2 feat(client): long-press to drag on every touch engine
Press-and-hold — the touch idiom for "pick this up" — produced a click
and then a cursor move on all three clients, in trackpad and pointer
mode alike. The engines armed a held left button only from a tap
followed by a second touch within 250 ms. Dragging a window, selecting
text and dragging a file were unreachable from a touchscreen.

One finger held still for 500 ms now presses the left button and drags
until it lifts; the tap-then-drag path stays. A still finger raises no
event, so each engine gets its clock: a run-loop tick on the desktop,
withTimeoutOrNull on Android, a main-queue work item on Apple. The
Android gesture releases a held button on teardown, which it never did.
2026-08-29 19:38:50 +02:00
enricobuehler faa8c92cd0 fix(client): honour invert scroll on two-finger touch scrolling
Turning on "Invert scroll" changed nothing for two-finger scrolling on
an iPad or a Deck. The desktop applied the sign only in the wheel path
(Capture::on_wheel); the touch engine's notches went out raw. On Apple,
InputCapture.sendScroll applies it and its comment called itself the
one scroll sink, but TouchMouse sends straight to the connection.

Both engines now apply the sign where the notch is made, as Android
already did. The Swift comment names the second sink.
2026-08-29 19:34:42 +02:00
enricobuehler ea04667f40 fix(presenter): scroll on exactly two fingers, never three
A three-finger drag scrolled on a Deck and summoned the keyboard on an
iPad. The published vocabulary is two-finger scroll; the desktop engine
scrolled on two OR MORE fingers.

Three or more fingers now anchor their own centroid, disqualify the tap
when they travel, and never scroll or move the cursor — the rule the
Android and Apple engines already apply.

Act::wire() moves to input.rs, the only place that needs InputKind, so
touch.rs has no platform-gated dependency and builds everywhere. Before
this, `cargo test -p pf-presenter` ran zero tests on macOS and passed.
2026-08-29 19:33:36 +02:00