fix(quic/apple): QUIC keep-alive + reconnect input re-engage
ci / rust (push) Failing after 36s
ci / web (push) Failing after 51s
docker / build-push (., web/Dockerfile, punktfunk-web) (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
ci / docs-site (push) Failing after 40s
apple / swift (push) Successful in 1m16s
docker / deploy-docs (push) Successful in 17s
ci / rust (push) Failing after 36s
ci / web (push) Failing after 51s
docker / build-push (., web/Dockerfile, punktfunk-web) (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
ci / docs-site (push) Failing after 40s
apple / swift (push) Successful in 1m16s
docker / deploy-docs (push) Successful in 17s
Three native-client bugs isolated against a stock Moonlight client (which stays connected / keeps input working under the same actions): - Connection drops mid-stream: the quinn endpoints (host + client) ran with default transport config, so keep_alive_interval was OFF. Any quiet stretch (no input, audio muted/stalled, a capture hiccup, a mode change) let the idle timer expire and quinn closed the session -> next_au=Closed -> "Session ended". Moonlight's ENet sends keepalive pings; we sent nothing. Add a shared TransportConfig (keep-alive 4s under an explicit 20s idle timeout) to both endpoint::server_from_der and endpoint::client_pinned_with_identity. - Reconnect input dead (macOS): the session-start auto-capture one-shot was consumed even when engageCapture(fromClick:false) was refused (window not key yet at the instant of reconnect), with no retry -> capture stayed off and input never forwarded. Clear the one-shot only on a successful engage, and retry on NSWindow.didBecomeKey. Stays scoped to session start, so it does not resurrect the rejected auto-grab-on-activation behavior. - Reconnect input dead (iOS): wasCapturedOnResign leaked stale state across sessions and the foreground-restore could fire before this session's InputCapture was wired (setForwarding no-ops on nil). Reset it per session in start() and guard the didBecomeActive restore on inputCapture != nil. Validated: cargo build -p punktfunk-core --features quic; swift build; swift test (39 passed, 0 failures); xcframework rebuilt (all 5 slices), no ABI/header drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -197,6 +197,17 @@ public final class StreamLayerView: NSView {
|
||||
self?.releaseCapture()
|
||||
})
|
||||
}
|
||||
// Becoming key RETRIES a still-pending session-start auto-capture — the case where a
|
||||
// session began (reconnect) while this window wasn't key yet, so engageCapture(fromClick:
|
||||
// false) was refused by its key-window guard and, with no retry, capture stayed off and
|
||||
// input dead. This is a no-op once capture engaged (pendingAutoCapture is cleared) and
|
||||
// after a manual ⌘⎋/focus-loss release (the flag is already false), so it does NOT
|
||||
// resurrect the deliberately-rejected "auto-grab on every activation" behavior.
|
||||
windowObservers.append(NotificationCenter.default.addObserver(
|
||||
forName: NSWindow.didBecomeKeyNotification, object: window, queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.attemptPendingCapture()
|
||||
})
|
||||
attemptPendingCapture()
|
||||
}
|
||||
|
||||
@@ -301,8 +312,13 @@ public final class StreamLayerView: NSView {
|
||||
|
||||
private func attemptPendingCapture() {
|
||||
guard pendingAutoCapture, window != nil, bounds.width > 0 else { return }
|
||||
pendingAutoCapture = false // one shot, even if the engage below is refused
|
||||
engageCapture(fromClick: false)
|
||||
// Clear the one-shot only once it ACTUALLY engaged. If the engage was refused — the
|
||||
// app/window isn't key yet (common right after a reconnect), or the cursor grab raced
|
||||
// app activation — leave it armed so didBecomeKey (or the next layout pass) retries.
|
||||
// This stays scoped to session start: a later manual release (⌘⎋, focus loss) doesn't
|
||||
// re-arm it, so it never resurrects auto-grab-on-activation.
|
||||
if captured { pendingAutoCapture = false }
|
||||
}
|
||||
|
||||
private func engageCapture(fromClick: Bool) {
|
||||
|
||||
Reference in New Issue
Block a user