feat(android): native display resolution + Settings screen
apple / swift (push) Successful in 53s
android / android (push) Failing after 1m15s
ci / rust (push) Failing after 43s
ci / web (push) Successful in 28s
ci / docs-site (push) Successful in 30s
ci / bench (push) Successful in 1m43s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m53s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m44s
deb / build-publish (push) Successful in 6m52s
docker / deploy-docs (push) Successful in 22s

The connect mode was hardcoded to 720p60 — violating the "native client resolution, no
scaling" invariant. Derive the device's real display mode (landscape, long edge = width) and
add a Settings screen to tune the stream, mirroring the Linux/Apple clients.

- crates/punktfunk-android: nativeConnect gains bitrateKbps + compositorPref + gamepadPref
  (CompositorPref/GamepadPref wire bytes via from_u8); these were hardcoded Auto/Auto/0.
- app/Settings.kt: Settings (width/height/hz/bitrate/compositor/gamepad; 0 = native/auto) +
  a SharedPreferences store + nativeDisplayMode (Display.mode, landscape-swapped) +
  effectiveMode + the UI option tables.
- app/SettingsScreen.kt: dropdowns for resolution / refresh / bitrate / compositor / controller.
- MainActivity: App owns the settings + a Settings screen; ConnectScreen resolves the effective
  mode (Native = the display), shows it on the Connect button, and threads the prefs through
  nativeConnect.

Mic + codec selection deferred (mic uplink isn't wired yet; the decoder is HEVC-only).

Verified live (emulator pf_phone -> home-worker-2): default -> host mode=2400x1080@60 (the
emulator's native display, was 720p); Settings 1920x1080 + 20 Mbps + DualSense -> host
mode=1920x1080, requested_kbps=20000, gamepad=dualsense (host created a UHID DualSense).
Settings persist across screens; pinned reconnect stays silent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:13:55 +02:00
parent 262305b771
commit 3bcc36c801
5 changed files with 290 additions and 16 deletions
@@ -27,7 +27,10 @@ object NativeBridge {
/**
* Connect, presenting [certPem]/[keyPem] (both empty = anonymous) and pinning [pinHex] (empty =
* trust-on-first-use — read [nativeHostFingerprint] after; else 64-hex host SHA-256, mismatch →
* `0`). Returns an opaque session handle, or `0` on failure. Pair with exactly one [nativeClose].
* `0`). [width]/[height]/[refreshHz] are the requested virtual-output mode (the host streams at
* exactly this); [bitrateKbps] 0 = host default; [compositorPref]/[gamepadPref] are the
* `CompositorPref`/`GamepadPref` wire bytes (0 = Auto). Returns an opaque session handle, or `0`
* on failure. Pair with exactly one [nativeClose].
*/
external fun nativeConnect(
host: String,
@@ -38,6 +41,9 @@ object NativeBridge {
certPem: String,
keyPem: String,
pinHex: String,
bitrateKbps: Int,
compositorPref: Int,
gamepadPref: Int,
): Long
/** 64-hex SHA-256 of the cert the host presented on [handle]; valid after a successful connect. */