feat(core): stylus wire P0 — state-full RICH_PEN batches, PenTracker, HOST_CAP_PEN
apple / swift (push) Successful in 1m24s
release / apple (push) Successful in 10m3s
android / android (push) Successful in 13m31s
arch / build-publish (push) Successful in 13m39s
ci / web (push) Successful in 1m4s
ci / docs-site (push) Successful in 1m12s
windows-host / package (push) Successful in 16m40s
apple / screenshots (push) Successful in 6m38s
ci / bench (push) Successful in 5m26s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m9s
ci / rust (push) Successful in 20m46s
decky / build-publish (push) Successful in 30s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m15s
deb / build-publish (push) Successful in 8m50s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 45s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 12s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m31s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m6s
deb / build-publish-host (push) Successful in 9m52s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m44s
docker / deploy-docs (push) Successful in 28s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 7m4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m35s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m45s
flatpak / build-publish (push) Failing after 8m6s

The pen plane from design/pen-tablet-input.md, protocol side only (the P1 uinput
tablet injector will consume it): 0xCC kind 0x05 carries batches of state-full
PenSamples (pressure, polar tilt + azimuth, barrel roll, hover distance, eraser
tool, barrel buttons); PenTracker diffs samples into injector transitions so a
lost datagram self-heals; HOST_CAP_PEN (0x10) gates sending and stays
unadvertised until a real backend exists. C ABI: PunktfunkPenSample +
punktfunk_connection_send_pen, documented in docs/embedding-the-c-abi.md §8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 12:58:31 +02:00
co-authored by Claude Fable 5
parent 1a6deeb781
commit 4714235fe6
10 changed files with 1116 additions and 17 deletions
+32
View File
@@ -437,6 +437,38 @@ punktfunk_connection_send_input(c, &ev);
- `punktfunk_connection_send_rich_input2(c, &richEx)` — the forward-compatible superset (Steam
trackpads, signed coords, pressure); set `struct_size = sizeof(PunktfunkRichInputEx)`.
### Stylus / pen
Full-fidelity pen (pressure, tilt, azimuth, barrel roll, hover, eraser, barrel buttons) has its
own plane. **Gate on the capability first** — only send when
`punktfunk_connection_host_caps(c, &caps)` shows `PUNKTFUNK_HOST_CAP_PEN`; without it the call
returns `UNSUPPORTED` and you keep your pen-as-touch fallback (what every client does today).
Samples are **state-full**: every sample carries the complete pen state (in-range / touching /
buttons in `state`, plus all axes — unknown axes take their `PUNKTFUNK_PEN_*_UNKNOWN` sentinel,
never 0). There are no down/up events to send; the host diffs consecutive samples and
synthesizes the transitions, so a lost datagram self-heals on the next one. Batch a capture
callback's coalesced samples (oldest first, `dt_us` = spacing) into one call, at most
`PUNKTFUNK_PEN_BATCH_MAX` per call — split longer runs into consecutive calls.
```c
// Example: an in-contact Apple Pencil sample, mapped into video-frame space
PunktfunkPenSample s; memset(&s, 0, sizeof s);
s.state = PUNKTFUNK_PEN_IN_RANGE | PUNKTFUNK_PEN_TOUCHING;
s.tool = PUNKTFUNK_PEN_TOOL_PEN;
s.x = 0.5f; s.y = 0.5f; // normalized 0..1 across the video frame
s.pressure = (uint16_t)(force_norm * 65535); // 0 while hovering
s.tilt_deg = 90 - altitude_deg; // polar tilt-from-normal
s.azimuth_deg = azimuth_deg; // 0..359, clockwise from north
s.roll_deg = PUNKTFUNK_PEN_ANGLE_UNKNOWN; // Pencil Pro: rollAngle in degrees
s.distance = PUNKTFUNK_PEN_DISTANCE_UNKNOWN;
punktfunk_connection_send_pen(c, &s, 1);
```
While hovering, send `PUNKTFUNK_PEN_IN_RANGE` samples (with `distance` if you have it); when the
pen leaves range, send one final sample with `state = 0` so the host releases proximity —
though a host-side timeout releases a vanished client's stroke regardless.
---
## 9. Feedback planes (rumble, HID, HDR)