Commit Graph
7 Commits
Author SHA1 Message Date
enricobuehlerandClaude Opus 5 25b127803f feat(client/apple): bind a host to a profile, connect with one, pin one as its own card
The host surfaces (design §5.2/§5.2a). A bound host wears a tinted chip that says
what a click will do. The card menu grows "Connect with ▸" — a ONE-OFF that never
rebinds, with a checkmark on the binding and an explicit "Set Default Profile" as
its last item for the users who do want to rebind from there — plus "Pin as Card ▸"
and "Copy Link".

A pinned host+profile combo becomes its own card next to its host: same record,
same live status, the profile as the prominent subtitle, one click to connect. It
is an extra grid ENTRY, never a duplicated host record — duplicating would fork
pairing, Wake-on-LAN and renames. Its menu carries only connect-shaped actions;
edit, pair, forget and remove stay on the primary card, where the thing they act on
lives. On the gamepad carousel and tvOS the same pins are tiles, which is the whole
point: focus-and-press is what those surfaces do well, and menus are not.

The edit sheet binds and pins; both rows vanish when no profiles exist, so a user
who never makes one sees exactly today's sheet. A dangling binding renders as
"Default settings (profile deleted)" and is cleaned up on save.

The speed test finally writes where the tested host reads. Unbound: the global, as
before. Bound to a profile that overrides bitrate: that override. Bound to one that
inherits it: both are offered, because either is defensible and guessing would
silently pick one. Every button names its target, and the probe now runs at the
mode that host would actually stream — the measurement is the streaming path.

Shortcuts gains `ProfileEntity` over the App-Group catalog and a Profile parameter
on Connect, still round-tripping through the URL router rather than opening a second
connect path. Connect and Wake drop their iOS wall — AppIntents is real on macOS and
tvOS, and "Stream Desktop with Work" from Spotlight was the ask; only the phrases
provider stays iOS-gated, since it bundles the LiveActivityIntent. The deep-link
observer moved out with them: an intent that posts to nobody is a shortcut that
silently does nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:06:41 +02:00
enricobuehlerandClaude Opus 4.8 e87fd42cee feat(apple): default-UI connect/wake modal, auto-wake toggle, pixel-exact windowed streaming
Three client-UX changes that share the connect/present path (and settings files):

- Connect/wake overlay is now mode-aware: the console/gamepad UI keeps the
  full-screen aurora takeover, while the default (touch/desktop) UI shows a
  Liquid Glass modal over the host grid — the takeover looked out of place there.
- Add an auto-wake toggle (DefaultsKey.autoWake, default on) across macOS/iOS/tvOS
  Settings + the gamepad settings view; gate startSession/prepareWake and the
  gamepad "Wake & Connect" label on it. MAC-address learning stays always-on.
- Windowed sessions now stream at the window's native pixels (Match-window
  default-on) so the picture is 1:1 pixel-exact instead of the presenter
  resampling a fixed-mode frame; fullscreen reports full-display px, also 1:1.
  Also lands the mid-resize aspect-fit tracking (decoded contentSize) that keeps
  the picture undistorted after a resize.

swift build + swift test (121 tests) green; screenshot scenes verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 01:29:12 +02:00
enricobuehlerandClaude Opus 4.8 dd02e1f402 feat(clients): unified full-screen connect/wake takeover + iOS/tvOS Wake-on-LAN
ci / docs-site (push) Successful in 1m8s
ci / web (push) Successful in 54s
decky / build-publish (push) Successful in 20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
android / android (push) Successful in 12m23s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 6m3s
arch / build-publish (push) Successful in 12m57s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9m40s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7m4s
docker / deploy-docs (push) Successful in 11s
flatpak / build-publish (push) Successful in 5m57s
deb / build-publish (push) Successful in 13m26s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m50s
apple / swift (push) Successful in 4m31s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m8s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m44s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m7s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m9s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m45s
release / apple (push) Successful in 21m27s
ci / rust (push) Failing after 6m47s
apple / screenshots (push) Successful in 20m18s
Give instant feedback the moment a host is picked, and make the wake wait a
full-screen takeover instead of a modal card — unified into one ConnectOverlay
across every client:

- android: new ConnectOverlay (aurora backdrop; Connecting / Waking / timed-out
  phases) replaces the tiny inline "Connecting…" row and the WakeOverlay card.
  The dial phase is now cancelable and hands off to the wake wait in one frame.
- console (pf-console-ui): the connect/wake overlays become a full-screen aurora
  takeover (draw_takeover) instead of a centered card over a dim scrim; the
  Waking → Connecting handoff no longer blinks.
- apple: new ConnectOverlay mirrors it (macOS / iOS / tvOS), replacing the
  per-tile connecting spinner + the WakeOverlay card; instant "Connecting…" from
  model.phase, and the carousel is gated inactive during the dial.

Also enable Wake-on-LAN on iOS/tvOS now that the multicast entitlement is
approved: enable com.apple.developer.networking.multicast and flip
wakeOnLANAvailable to true on every platform (MACs were already learned from
mDNS, so wake works immediately).

Verified: Android compileDebugKotlin + screenshot renders; console clippy +
36 tests + rendered phases on Linux; Apple swift build + 121 tests + rendered
phases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:29:35 +02:00
enricobuehlerandClaude Opus 4.8 60121f1ea9 feat(apple): wake-until-up overlay + host edit with MAC prefill
- HostWaker + WakeOverlay: after sending the Wake-on-LAN packet, wait until the host
  is really back (resend + mDNS poll, timeout, cancel/retry) before connecting.
  macOS-only in practice — WoL stays gated off on iOS/tvOS pending the multicast
  entitlement.
- Add/Edit host sheet gains a Wake-on-LAN MAC field, prefilled from the stored MAC
  or the live mDNS advert; parseMacs validates aa:bb:cc:dd:ee:ff.
- Gamepad chrome/home and glass-style polish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:05:17 +02:00
enricobuehlerandClaude Fable 5 83460cd2c0 feat(clients): unified stats vocabulary across every client + Moonlight comparison docs
One stat model everywhere (design/stats-unification.md): four measurement
points (capture/received/decoded/displayed), three stages that tile the
interval exactly, and a HUD that shows the addition explicitly —

  end-to-end 14.2 ms p50 · 19.8 p95 · capture→on-glass
  = host+network 9.8 + decode 2.1 + display 2.3

replacing each client's ad-hoc mix of overlapping absolutes (the Apple HUD's
three arrow lines that looked sequential but weren't), mean-vs-median decode
times (Windows/Linux), missing same-host-clock flags (Windows/Linux), and
three different names for the same capture→received measurement (probe's
"reassembled", Apple/Android's "client", Windows/Linux's post-decode "lat").

Per client: Apple threads receivedNs through the VT decode via the frame
refcon bit pattern so the decode stage exists at all (stage-1 fallback
honestly degrades to a capture→received headline); Windows carries
FrameTimes through the existing frame channel to the render thread and adds
e2e p50/p95 post-Present; Linux stamps received at AU pop and rides
decoded_ns on DecodedFrame to the paintable-set site; Android pairs receipt
stamps with MediaCodec output buffers via the codec's pts round-trip (JNI
stats array 14→16 doubles, indexes 0-13 unchanged). fps now uniformly counts
received AUs; lost/(received+lost) per window, hidden at zero.

docs-site gains "Understanding the Stats Overlay": what each line means, why
the equation only approximately sums (percentiles), and a line-by-line
Moonlight/Sunshine matrix — including that Moonlight has no end-to-end
number and its "network latency" is an ENet control RTT, so punktfunk's
headline must not be compared against any single Moonlight line.

Verified here: linux client + probe + core check/clippy/fmt green, android
native cargo-ndk arm64 check green. Pending: Windows CI + on-glass, swift
test on the mac, on-device Android.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:01:29 +00:00
enricobuehlerandClaude Opus 4.8 8c6c9705f4 feat(apple): stage-2 default + pixel-perfect, decode robustness, UI/rumble polish
Stream reliability
- Default to the stage-2 presenter (VTDecompressionSession + CAMetalLayer): it detects
  and recovers a wedged decoder, where stage-1's AVSampleBufferDisplayLayer freezes hard
  on a lost HEVC reference frame with no app-side recovery (confirmed Apple limitation).
  Stage 1 is now a DEBUG-only presenter toggle, plus the automatic no-Metal fallback.
- Stage-2 pixel-perfect: render the drawable at the decoded size (shader stays 1:1 =
  identity) and let the layer's contentsGravity scale via the system compositor — the
  same path stage-1's videoGravity used — instead of scaling in-shader.
- Loss recovery in both pumps is now a persistent awaitingIDR want, retried until an IDR
  actually lands, so a keyframe request swallowed by the throttle can't strand a frozen
  frame; 100 ms keyframe throttle to match the Android path.
- Fix "Publishing changes from within view updates": defer the HostStore writes out of
  the .onChange(of: model.phase) callback.
- Move AVAudioSession setActive/setCategory off the main thread (async on a shared serial
  queue) to stop the UI-stall warning.

Controllers
- Rumble: capped-exponential backoff when the gamecontrollerd.haptics XPC breaks (-4811)
  so a transient server interruption self-heals instead of cascading; playsHapticsOnly so
  a controller engine doesn't join the always-active streaming audio session.
- Host cards: iPad pointer "magnet" hover effect; iPhone press scale + light haptic.

UI / design
- Ship Geist (SIL OFL 1.1) as the app font (bundled OTFs + registration), with the
  license surfaced in Acknowledgements.
- Restructure iOS/iPadOS Settings into a category NavigationSplitView; resolution wheel
  with custom-resolution entry; 10-bit HDR toggle in Display.
- Industrial host-card redesign (left-aligned, bold, brand monogram tiles).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:26:10 +02:00
enricobuehlerandClaude Opus 4.8 93273a24b4 feat(apple): App Store screenshot harness + CI zip artifact
A DEBUG-only "shot mode" renders one mock-populated screen full-bleed
(PUNKTFUNK_SHOT_SCENE=<name> -> ScreenshotHostView instead of ContentView),
so the OS can screenshot the REAL, fully-rendered UI. tools/screenshots.sh
drives it: screencapture for the mac window, `simctl io booted screenshot`
for the iOS/iPad/tvOS Simulators, at exactly the App Store Connect sizes.

ImageRenderer was tried first and rejected: it can't rasterize this app's
chrome (NavigationStack, Form/TabView, Liquid-Glass/NSVisualEffect all render
black or the "can't render" placeholder). Capturing the live window/Simulator
avoids that. Only the stream hero is synthetic (StreamView needs a live
connection) - a synthwave frame + the real glass HUD, overridable via
PUNKTFUNK_SHOT_HERO.

CI: a new `screenshots` job in apple.yml builds the iOS (+ tvOS best-effort)
xcframework slices, runs the harness per platform best-effort, and attaches
the result as a single zip artifact (punktfunk-appstore-screenshots). It is
isolated from the build/test job and skipped on PRs, so a capture gap (missing
Simulator runtime, or no Screen Recording grant for the mac window capture)
never reds the core signal.

Generated PNGs (clients/apple/screenshots/) are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:44:03 +02:00