fix(client/apple): let the pad drive two overlays on tvOS #458

Merged
enricobuehler merged 2 commits from fix/tvos-overlay-pad-input into main 2026-08-30 23:35:12 +00:00
Owner

Half of #453. The issue reports two separate faults; this fixes one of them.

The report

after a suspend of the host the apple tv tries to wake up the machine but is not detecting when its awake. runs into failure and no way to press cancel or retry with the controller

What was wrong

ConnectOverlay and TrustCardView each mount a zero-size view owning a GamepadMenuInput for the overlay's lifetime, and both gated it #if os(iOS) || os(macOS).

That is not tvOS being driven some other way. The console home underneath runs the same binding on tvOS (GamepadHomeView.swift:23 is #if os(iOS) || os(macOS) || os(tvOS)), and ConnectOverlay's own comment records that the home gates itself inactive while an overlay is up "so nothing else is consuming the pad". With the home inactive and no binding in the overlay, nothing read the pad at all — exactly the dead end reported.

TrustCardView had the identical hole in four blocks: the GamepadManager observation, the GamepadHintBar legend, the .background binding, and the binding struct. That one stands between a pad-only Apple TV and every unknown host, so it is fixed here rather than left for the next report.

What is deliberately NOT changed

GamepadPrompt keeps its exclusion. I widened it too before reading its header, which says:

tvOS keeps the system alerts: the focus engine drives them natively there, which is the whole reason this gap was tvOS-invisible.

That is a reasoned exclusion, not an oversight. Reverted.

What this does and does not reach

GamepadMenuInput requires manager.active?.controller.extendedGamepad, which is the same condition GamepadUIEnvironment.isActive uses to turn the console UI on. So this covers the console takeover exactly.

A Siri Remote is not an extended gamepad. It reaches these buttons through the tvOS focus engine or not at all, and nothing in either overlay calls .defaultFocus or .focusSection, so whether focus even lands there is unverified. I did not guess at it — that wants an Apple TV in front of someone. Both binding structs carry this in a doc comment.

The other half of #453 is untouched

"is not detecting when its awake" is a presence bug, not an input one, and it shares a root cause with #327:

  • HostStore.refreshReachability probes only hosts.filter { !discovery.advertises($0) } — an advertising host is never probed, so its Online state rests entirely on an mDNS advert that HostDiscovery is deliberately built to hold on to.
  • HostWaker.start then guards !isOnline(), so a stale-true presence makes it skip the wake entirely and dial a sleeping host.

One predicate serves both issues, and it needs a decision on the expiry window. #453 stays open for it.

Verification

Check Result
swift build (macOS) exit 0
swift test 417 tests, 6 skipped, 0 failures
xcodebuild -scheme Punktfunk-tvOS -destination 'generic/platform=tvOS' BUILD SUCCEEDED, 0 errors

The tvOS leg needed BUILD_TVOS=1 scripts/build-xcframework.sh first (tier-3 targets, nightly -Zbuild-std). Both changed files appear in the appletvos compile log, so the newly-included code really was built for the platform rather than skipped.

No on-glass testing — no Apple TV here.

Refs #453

Half of #453. The issue reports two separate faults; this fixes one of them. ## The report > after a suspend of the host the apple tv tries to wake up the machine but is not detecting when its awake. runs into failure and **no way to press cancel or retry with the controller** ## What was wrong `ConnectOverlay` and `TrustCardView` each mount a zero-size view owning a `GamepadMenuInput` for the overlay's lifetime, and both gated it `#if os(iOS) || os(macOS)`. That is not tvOS being driven some other way. The console home underneath runs the *same* binding on tvOS (`GamepadHomeView.swift:23` is `#if os(iOS) || os(macOS) || os(tvOS)`), and `ConnectOverlay`'s own comment records that the home gates itself inactive while an overlay is up "so nothing else is consuming the pad". With the home inactive and no binding in the overlay, **nothing read the pad at all** — exactly the dead end reported. `TrustCardView` had the identical hole in four blocks: the `GamepadManager` observation, the `GamepadHintBar` legend, the `.background` binding, and the binding struct. That one stands between a pad-only Apple TV and every unknown host, so it is fixed here rather than left for the next report. ## What is deliberately NOT changed **`GamepadPrompt` keeps its exclusion.** I widened it too before reading its header, which says: > tvOS keeps the system alerts: the focus engine drives them natively there, which is the whole reason this gap was tvOS-invisible. That is a reasoned exclusion, not an oversight. Reverted. ## What this does and does not reach `GamepadMenuInput` requires `manager.active?.controller.extendedGamepad`, which is the same condition `GamepadUIEnvironment.isActive` uses to turn the console UI on. So this covers the console takeover exactly. **A Siri Remote is not an extended gamepad.** It reaches these buttons through the tvOS focus engine or not at all, and nothing in either overlay calls `.defaultFocus` or `.focusSection`, so whether focus even lands there is unverified. I did not guess at it — that wants an Apple TV in front of someone. Both binding structs carry this in a doc comment. ## The other half of #453 is untouched "is not detecting when its awake" is a presence bug, not an input one, and it shares a root cause with #327: - `HostStore.refreshReachability` probes only `hosts.filter { !discovery.advertises($0) }` — an advertising host is never probed, so its Online state rests entirely on an mDNS advert that `HostDiscovery` is deliberately built to hold on to. - `HostWaker.start` then guards `!isOnline()`, so a stale-true presence makes it **skip the wake entirely** and dial a sleeping host. One predicate serves both issues, and it needs a decision on the expiry window. #453 stays open for it. ## Verification | Check | Result | | --- | --- | | `swift build` (macOS) | exit 0 | | `swift test` | 417 tests, 6 skipped, **0 failures** | | `xcodebuild -scheme Punktfunk-tvOS -destination 'generic/platform=tvOS'` | **BUILD SUCCEEDED**, 0 errors | The tvOS leg needed `BUILD_TVOS=1 scripts/build-xcframework.sh` first (tier-3 targets, nightly `-Zbuild-std`). Both changed files appear in the `appletvos` compile log, so the newly-included code really was built for the platform rather than skipped. ⚠ **No on-glass testing** — no Apple TV here. Refs #453
enricobuehler added 1 commit 2026-08-30 21:35:30 +00:00
fix(client/apple): let the pad drive two overlays on tvOS
apple / swift (pull_request) Successful in 2m12s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 2m8s
ci / bun-nix (pull_request) Successful in 36s
ci / rust-arm64 (pull_request) Successful in 3m8s
ci / docs-site (pull_request) Successful in 2m57s
ci / docs-drift (pull_request) Successful in 1m6s
ci / rust (pull_request) Failing after 6m22s
f6f40f4dd3
On an Apple TV the wake prompt was a dead end: the host failed to wake,
and neither Cancel nor Try Again could be pressed with a controller.

`ConnectOverlay` and `TrustCardView` each mount a zero-size view that
owns a `GamepadMenuInput` for the overlay's lifetime, and both gated it
to iOS and macOS. That is not tvOS being driven some other way — the
console home underneath runs the same binding there, and it gates itself
inactive while an overlay is up, so with no binding in the overlay
nothing read the pad at all. The trust card had the same hole, between a
pad-only Apple TV and every unknown host.

`GamepadPrompt` keeps its exclusion: it replaces system alerts that the
tvOS focus engine already drives, which its own header says.

Both bindings need an EXTENDED gamepad, the same thing that turns the
console UI on, so this covers the takeover exactly. A Siri Remote is not
one and reaches these buttons through the focus engine or not at all;
whether focus lands in either overlay is unverified and not addressed.

Refs #453
enricobuehler added 1 commit 2026-08-30 22:44:49 +00:00
Merge branch 'main' into fix/tvos-overlay-pad-input
apple / swift (pull_request) Successful in 2m6s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m58s
ci / docs-site (pull_request) Successful in 1m57s
ci / docs-drift (pull_request) Successful in 51s
ci / bun-nix (pull_request) Successful in 51s
ci / rust-arm64 (pull_request) Successful in 3m44s
ci / rust (pull_request) Canceled after 6m56s
77d75c73c1
enricobuehler merged commit cd48ee991e into main 2026-08-30 23:35:12 +00:00
enricobuehler deleted branch fix/tvos-overlay-pad-input 2026-08-30 23:35:33 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#458