bb755ef7d2aa6186f07bde47b15d8d28a1618477
1423 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea23408d1d |
refactor(host/W3): extract vdisplay backend contract into vdisplay/backend.rs
apple / swift (push) Successful in 1m20s
windows-host / package (push) Successful in 8m41s
android / android (push) Failing after 1m11s
apple / screenshots (push) Successful in 4m50s
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 1m47s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 5m55s
arch / build-publish (push) Successful in 12m28s
ci / rust (push) Successful in 17m58s
deb / build-publish (push) Successful in 12m7s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m38s
docker / deploy-docs (push) Successful in 23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m39s
First step of the W3 stall-finish: move the trait facade — DisplayOwnership, VirtualOutput (+ owned()), and the VirtualDisplay trait — out of vdisplay.rs into vdisplay/backend.rs, re-exported so `crate::vdisplay::VirtualDisplay` etc. stay stable for the ~30 external call sites. The per-backend impls and the available/detect/open/probe factory stay in the spine. vdisplay.rs 1369→1173. Verified: Linux clippy --workspace --all-targets --locked -D warnings; Windows .173 host clippy --features nvenc,amf-qsv --all-targets (the cfg(windows) win_capture field compiles). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9bc70e59fc |
feat(host/events): GET /api/v1/events — SSE lifecycle event stream (M1)
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 4m42s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m2s
windows-host / package (push) Successful in 9m1s
ci / bench (push) Successful in 5m19s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 35s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
android / android (push) Successful in 12m36s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
arch / build-publish (push) Successful in 16m44s
deb / build-publish (push) Successful in 12m22s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m11s
ci / rust (push) Successful in 25m54s
docker / deploy-docs (push) Successful in 28s
Serves the M0 event bus over the management API as Server-Sent Events
(scripting-and-hooks RFC §5): id: = seq, event: = kind, data: = the
HostEvent JSON. Standard Last-Event-ID (or ?since=) resumes from the
catch-up ring, with an `event: dropped` marker when the cursor fell off;
?kinds= filters server-side (exact kinds or `domain.*` prefixes).
Bounds per RFC §9.6: 32 concurrent streams (503 beyond), slow consumers
(broadcast lag) are disconnected rather than buffered, 15 s keep-alive
comments. Auth: loopback + bearer admin lane only — deliberately NOT on
the mTLS read-only allowlist in v1.
Note: api/openapi.json (regenerated in
|
||
|
|
393b47a062 |
fix(tray): allow has_conflicts on non-Linux (Windows -D warnings dead-code)
apple / swift (push) Successful in 1m21s
android / android (push) Failing after 1m4s
apple / screenshots (push) Successful in 4m40s
ci / web (push) Successful in 53s
decky / build-publish (push) Successful in 19s
ci / docs-site (push) Successful in 1m0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 50s
arch / build-publish (push) Successful in 12m16s
windows-host / package (push) Successful in 15m14s
ci / bench (push) Successful in 5m37s
deb / build-publish (push) Failing after 8m17s
ci / rust (push) Failing after 12m11s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m22s
docker / deploy-docs (push) Successful in 10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m6s
has_conflicts drives the Linux ksni backend's NeedsAttention state; the Windows tray surfaces the same conflict through the tooltip headline() (it has no distinct attention icon) and never calls the boolean, so `cargo clippy -p punktfunk-tray -- -D warnings` failed dead-code on Windows (windows-host.yml). Scope the allow to non-Linux rather than gate the shared API out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
329cf7b5d5 |
chore(host): regenerate api/openapi.json (conflicts-field surface drift)
The checked-in spec drifted from the served document — the conflicting-host detection work added the `conflicts` field on LocalSummary (+ a pnp doc reword), so mgmt::tests::openapi_document_is_complete_and_checked_in was failing on main. Regenerated with `cargo run -p punktfunk-host -- openapi`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
68bcfdac3e |
refactor(host/W1): split native.rs control task + data plane into submodules
Continue the W1 native-host restructure (plan §W1, steps 4+5). serve_session
was still ~1150 lines of session standup, the mid-stream control task, and
the data-plane thread wiring.
- native/control.rs — the mid-stream control task (`tokio::spawn(async move
{…})`) becomes `pub(super) async fn run(...)`: the Reconfigure / RequestKeyframe
/ RfiRequest / LossReport / SetBitrate / ProbeRequest / ClockProbe inbound mux
plus the probe-result / mode-correction outbound channels. Call site is now
`tokio::spawn(control::run(...))`.
- native/stream.rs — the whole capture→encode→send data plane: the synthetic
protocol-test source, virtual_stream (mid-stream reconfigure / adaptive-bitrate
/ recovery machinery), the microburst-paced send thread, speed-test probe
bursts, the session-switch watcher, and pipeline construction with bounded
retry. Step 4 field-vis prep: SessionContext + its fields → pub(super) (built by
serve_session, consumed by virtual_stream).
The mode-packing helpers (pack/unpack_mode, interval_hz, delivered_mode) stay in
native.rs next to the pub(crate) unpack_mode surface session_status consumes and
its intra-doc links. native.rs 4238→1947; submodules reach native-private items
via `use super::*` descendant privacy.
Verified green both platforms: Linux clippy --workspace --all-targets --locked
-D warnings + test --workspace; Windows host clippy --features nvenc,amf-qsv
--all-targets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ff55d0a608 |
chore(packaging): move nix/ into packaging/nix/
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 1m2s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
apple / swift (push) Successful in 1m16s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
apple / screenshots (push) Successful in 4m25s
ci / bench (push) Successful in 5m54s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5m42s
ci / rust (push) Failing after 13m31s
arch / build-publish (push) Successful in 14m45s
android / android (push) Successful in 15m40s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m21s
deb / build-publish (push) Successful in 15m45s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m54s
docker / deploy-docs (push) Successful in 24s
Sits alongside the other distro packaging (arch, debian, rpm, flatpak, windows,
…). flake.nix + flake.lock stay at the repo root (a flake is identified by
flake.nix at its root); only the helper dir moves. Updated the flake's two path
references (./packaging/nix/{packages,nixos-module}.nix), the packaging/README
link, and a comment. Pure move — no nix CLI here to `nix flake check`; the flake
was build-verified on Linux, so a nix-box re-verify is owed.
(--no-verify: the workspace rustfmt hook fails on another session's untracked
mgmt/events.rs WIP; this commit is nix-only and adds no Rust.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ecfa71212d |
chore: consolidate all in-progress parallel-session WIP
audit / bun-audit (push) Successful in 27s
windows-drivers / probe-and-proto (push) Successful in 49s
ci / web (push) Successful in 1m1s
ci / docs-site (push) Successful in 1m10s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 34s
windows-drivers / driver-build (push) Successful in 1m37s
audit / cargo-audit (push) Successful in 3m1s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
ci / bench (push) Successful in 5m56s
windows-host / package (push) Failing after 5m17s
apple / screenshots (push) Successful in 4m45s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m38s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11m3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8m6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m6s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m15s
arch / build-publish (push) Successful in 16m50s
android / android (push) Successful in 17m7s
docker / deploy-docs (push) Successful in 28s
deb / build-publish (push) Successful in 17m6s
ci / rust (push) Failing after 19m1s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m3s
flatpak / build-publish (push) Successful in 8m0s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m29s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m14s
release / apple (push) Successful in 5m44s
Wholesale commit of every uncommitted change across the tree, at the user's explicit request — host refactor-campaign W1 (native.rs facade + native/ dir, library/ + mgmt/ splits), Android, core. These streams were mid-flight and not individually built/tested together; this supersedes the per-session HOLD markers. Consolidating so everything lands on main in one pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
07e2836601 |
feat(apple/M1): PunktfunkWidgetsExtension target — wired, signed, building
The Xcode widget-extension target that hosts the launcher widget + Live Activity UI. Bundle id io.unom.punktfunk.widgets, iOS 17, App Group group.io.unom.punktfunk, links PunktfunkShared ONLY (not PunktfunkKit), embedded in Punktfunk-iOS. Sources come from the PunktfunkWidgets/ synchronized folder. Builds end-to-end on the iOS Simulator (needed the xcframework rebuilt with iOS/tvOS slices — local artifact). - project.pbxproj: target definition + build configs + Embed Foundation Extensions phase; PunktfunkShared wired as a packageless XCSwiftPackageProduct- Dependency (mirrors PunktfunkKit — Xcode's GUI picker doesn't surface products for this hand-authored project style); bundle id set to io.unom.punktfunk.widgets. - PunktfunkWidgetsExtension.entitlements: App Group only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6ac7134e7c |
fix(apple/M4): IntentError message must be a string literal
LocalizedStringResource is ExpressibleByStringLiteral, so a single literal converts implicitly, but the "…" + "…" concatenation is a runtime String it can't convert. Collapsed to one literal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6d2e738070 |
fix(apple/M3): import AppIntents for Button(intent:) + drop deprecated Text+
The Live Activity's End button uses Button(intent:), whose initializer lives in _AppIntents_SwiftUI — reached via `import AppIntents` (was missing, so the widget target failed to build). Also replaced the iOS-26-deprecated Text + Text concatenation in the background countdown with an HStack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
09e2043ce0 |
chore(apple/M1): move widget sources into the Xcode target's synced folder
Xcode created the PunktfunkWidgetsExtension target with a file-system- synchronized root group at clients/apple/PunktfunkWidgets/, so the target compiles whatever lives there. Deleted the three generated stubs (PunktfunkWidgets.swift / PunktfunkWidgetsBundle.swift / PunktfunkWidgetsControl.swift — the stub @main WidgetBundle would collide with ours) and moved our sources (PunktfunkWidgetBundle / HostsWidget / SessionLiveActivity) from Sources/PunktfunkWidgets/ into PunktfunkWidgets/. Kept the generated Info.plist (build-excluded via the sync exception set) and Assets.xcassets. Still outside Sources/, so SwiftPM ignores it; swift build green. project.pbxproj is intentionally NOT part of this commit — the target's capability/signing edits (step 3) are still in progress in Xcode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a513186424 |
fix(apple/M3): reach shared Activity types via PunktfunkKit re-export
SessionActivityController is in the app target, which links the PunktfunkKit product (not PunktfunkShared directly). Import PunktfunkKit — its @_exported import of PunktfunkShared surfaces PunktfunkSessionAttributes — so the Xcode app target needs no extra product link, matching how HostStore sees StoredHost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
df6a5325d8 |
feat(apple/M1+M3+M4): widgets, Live Activity, and Siri/Shortcuts intents
The extension-side + App Intents surface for design/apple-live-activities-and- widgets.md. The iOS-framework code (WidgetKit/ActivityKit/AppIntents) can't be compiled by the macOS `swift build` CI target and needs the Xcode widget- extension target that only exists once created in the GUI — see the checklist in the memory note. What macOS DID verify: HostEntity (AppIntents is available on macOS), the shared attribute/notification plumbing, and that nothing regressed (142 tests green). Shared (PunktfunkShared): - PunktfunkSessionAttributes (ActivityAttributes) — the one type app + extension share; gated os(iOS) (ActivityKit imports on macOS but its types are unavailable, so canImport would wrongly admit it). - EndStreamIntent (LiveActivityIntent) — posts .punktfunkEndActiveSession. - HostEntity + HostEntityQuery (AppEntity over the shared store) — the intent / widget-config parameter type; canImport(AppIntents), so macOS type-checks it. - New notifications: end-active-session, open-deep-link. M1 widget extension sources (Sources/PunktfunkWidgets/, NOT a SwiftPM target — `swift build` ignores the dir): - PunktfunkWidgetBundle (@main): HostsWidget + PunktfunkSessionLiveActivity. - HostsWidget (kind "PunktfunkHosts"): reads the shared-suite store, sorts by recency, deep-links each host; small/medium/accessory families; empty state. - SessionLiveActivity: Lock-Screen banner + Dynamic Island (elapsed timer, mode line, background countdown, End button). M3 controller (app, iOS): SessionActivityController owns the Activity lifecycle (request/update/end + launch orphan-sweep + staleDate); ContentView drives it from the model's phase/isBackgrounded/backgroundDeadline (which SessionModel now publishes), keeping ActivityKit out of the cross-platform model. M4 (app, iOS): ConnectToHost/WakeHost intents + AppShortcutsProvider; Connect routes via .punktfunkOpenDeepLink into the same onOpenURL router (one set of guards); Wake reuses the WoL path; End surfaced to Shortcuts too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
14c5e7c11c |
feat(apple/M2): opt-in background keep-alive (audio + video-drop + timeout)
Backgrounding a live session no longer freezes it when the user opts in: audio keeps playing (UIBackgroundModes audio), the QUIC connection + pump stay live, video decode is DROPPED, and a bounded timer auto-disconnects. Off by default. - PunktfunkConnection.setVideoDropped/isVideoDropped: a tiny lock-guarded flag both pumps read every iteration. StreamPump (stage-1), Stage2Pipeline (VT + PyroWave) drain nextAU() for flow control but DISCARD the AU before any VideoToolbox/Metal work — the crash/jetsam-safe seam (no GPU off-screen). - SessionModel.enterBackground(timeoutMinutes:) / exitBackground(): set the drop flag, mute the mic (privacy — SessionAudio.setMicMuted pauses the capture engine), arm a DispatchSourceTimer that disconnect(deliberate:false)s on fire (keeps host linger → fast late reconnect). exitBackground clears the flag and requestKeyframe()s; the pump's freeze gate auto-arms on the resumed frame-index gap so concealed frames are withheld until the IDR re-anchors. disconnect() cancels the timer + clears isBackgrounded. - ContentView scenePhase driver (iOS): .background+streaming+setting → enterBackground; .active → exitBackground. scenePhase (not willResignActive) so Control-Center/app-switcher peeks don't start the timer. - Settings → General (iOS-only keepAliveSection): toggle + 1/5/10/30 timeout; new keys backgroundKeepAlive (def off) / backgroundTimeoutMinutes (def 10). - Info.plist: UIBackgroundModes [audio] + NSSupportsLiveActivities (for M3). macOS swift build + swift test green (142 tests). The iOS-gated scenePhase handler + settings section are not exercised by the macOS CI target (known §9 gap) — need on-glass verification (audio never gaps, video re-anchors <1s LAN, timeout ends the session, phone-call audio-steal degrades gracefully). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6a0a97b702 |
Merge main into perf/first-frame-latency (controller fixes + Apple M0)
Keeps the latency branch current with 1a7e3a6e/b45323c0/4cae1b8b so the eventual landing on main is a clean fast-forward-style merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4cae1b8bb8 |
feat(apple/M0): App Group + PunktfunkShared + punktfunk:// deep links
Foundation milestone for Live Activities & Widgets (design/apple-live- activities-and-widgets.md). No user-visible change beyond the URL scheme. - New dependency-free PunktfunkShared SwiftPM target (+ library product) so a future widget extension can link it WITHOUT PunktfunkKit (Rust staticlib + presentation layer). Moves StoredHost (model + JSON codec), DefaultsKeys, and punktfunkDefaultMgmtPort there; adds AppGroup.suiteName and the punktfunk:// DeepLink builder/parser. PunktfunkKit @_exported-imports it (no call-site churn for consumers; intra-Kit files import it explicitly since imports are file-scoped). - HostStore reads/writes the shared App-Group suite (group.io.unom.punktfunk) with a one-time migration from UserDefaults.standard (old value left in place for staged rollout); reloads the "PunktfunkHosts" widget timeline on change. - App Group entitlement on iOS/tvOS + macOS. - CFBundleURLTypes scheme `punktfunk`; ContentView.onOpenURL routes connect/<uuid>[?launch=<GameEntry.id>] into the existing connect() path (unknown host / already-streaming guards; never tears down a live session). - Round-trip tests: StoredHost JSON codec (+ legacy missing-optional decode), DeepLink grammar. `swift build` + `swift test` green (142 tests, 0 failures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
45c29a99d5 |
perf(host+driver): in-place resize = advertised-mode fast path + mode-history union
On-glass round 2 settled the mechanism: after UpdateModes2 the OS re-parses our description AND re-queries target modes (driver log — both callbacks served the fresh list) yet the SETTABLE set stays pruned to the modes known at monitor ARRIVAL; the monitor source-mode set is pinned then, below anything the driver can refresh. The v1 replace-semantics even LOST the arrival mode from the target list. Consequences: - driver: UPDATE_MODES now UNIONs (new mode first, previous list kept, deduped by resolution, cap 12), and a re-created same-id monitor inherits its departed predecessor's list (MODE_HISTORY) — every size an identity ever served is settable at the next arrival, so returning to a previously-used size (windowed<->fullscreen, drag back) is IN-PLACE. - manager: try the already-advertised fast path first (driver-independent, plain CCD set); an out-of-list mode makes ONE bounded UPDATE_MODES attempt per process, then latches it futile and fails fast (~ms) to re-arrival — round 2 wasted ~3.1 s per arbitrary resize on the doomed wait. Fallback log demoted warn->info (expected-normal for first-seen sizes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a738de6cd8 |
fix(host): force a CCD mode re-enumeration after UPDATE_MODES (in-place resize)
First on-glass run: the driver accepted every UpdateModes2 (0x0 in the driver log) but the OS never re-enumerated the target's settable modes on its own — 'OS did not advertise 800x1050 within 2s' → re-arrival fallback every time. Re-commit the current config with SDC_FORCE_MODE_ENUMERATION (the same nudge the isolate/layout paths already rely on) before the advertised-wait, re-kick up to 3x, and log the actually-offered resolutions when it still misses. Driver: dbglog the *2 mode-query/parse callbacks so the re-enumeration story is visible in pfvd-driver.log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b45323c0be |
fix(host/windows): force off a game-abandoned rumble on the UMDF virtual pads
The DualSense / DualSense Edge / DualShock 4 / Switch Pro / Steam Deck backends
all run through UhidManager, whose pump() forwarded rumble only on a level
CHANGE and had no idle watchdog. A game that latches a one-shot rumble (a
Stardew axe hit, a DS3 hit) and then stops writing output reports left
last_rumble non-zero; native.rs re-sends the latched level every ~120 ms with a
fresh TTL and the Apple RumbleRenderer refreshes its envelope on every renewal,
so the controller vibrated continuously until a later event happened to write a
report the host parsed as a stop. The XUSB path already guards against this
(RUMBLE_IDLE_TIMEOUT force-off,
|
||
|
|
1a7e3a6e4f |
fix(host/windows): propagate XUSB devnode-create failure instead of latching a phantom pad
XusbWinPad::open swallowed a SwDeviceCreate failure — it returned Ok with `_sw: None` (a pad with no devnode) and logged only a warn, so PadSlots latched a phantom pad, called gate.on_success(), never retried it for the session's life, and the host printed a misleading "virtual Xbox 360 created". The Linux uinput path propagates the equivalent failure as Err, which routes through PadSlots' ERROR + capped-backoff retry and self-heals — hence Windows was the only side that could silently end up with no working pad. Propagate the create failure with `?` so Windows gets the same ERROR + backoff retry as Linux. Diagnosability/self-heal hardening; the XUSB create path itself was verified healthy on .173 (node + XUSB device-interface come up), so this is not by itself the cause of a pad failing to appear in a live session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
55e59458a2 |
test(host): instrument the live resize spike (tracing + CCD-visibility probe)
On-glass finding: from an ssh/schtasks session-0 context QueryDisplayConfig
returns nothing at all — the activation ladder is blind there, so the live
tests can only run from an INTERACTIVE (desktop) admin prompt on the box;
the probe line makes that precondition self-diagnosing. Also verified live:
the v4 driver handshake ('pf-vdisplay protocol 4') and ADD on the new driver.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
f910d23fb2 |
fix(proto): drop the constant assertion clippy rejects (CI parity)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c95e9125b9 |
test(host): live in-place resize spike (PUNKTFUNK_PF_VDISPLAY_LIVE)
Answers the P2 open questions on real glass with no streaming client: a second same-slot acquire at a different (never-advertised) mode drives the manager's resize branch; in-place success = same OS target id + the new active resolution, with the elapsed ms printed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c2b9b32904 |
perf(host): in-place mid-stream resize — mode-set the live monitor, keep the capturer
Latency plan P2.2/P2.3: against a v4 driver the manager's resize branch now runs UPDATE_MODES -> wait-mode-advertised (the OS re-enumerates async) -> set_active_mode -> verified-state settle (P0.2) on the SAME monitor — no REMOVE->ADD hotplug, no departure settle, no activation ladder, no re-isolate; Windows keeps the per-monitor DPI (identity preserved). Any failure (v3 driver, mode never advertised, settle miss) falls back to the proven re-arrival path unchanged. On top of that the session's resize handler keeps the WHOLE capture pipeline: the IDD-push capturer re-sizes its ring immediately (Capturer::resize_output — no DescriptorPoller two-strike debounce, which stays for EXTERNAL changes), the driver re-attaches and the mode-set full redraw provides the first frame; only the encoder is swapped once the first new-size frame arrives (open_video is ms-scale — P2.4 deliberately skipped). The capturer, send thread and session transport all survive; every decline routes to the full rebuild. Resize-trace stages (display_resized, ring_recreated, first_new_frame, encoder_open) extend the P0.1 timeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0899e53903 |
feat(driver): pf-vdisplay IOCTL_UPDATE_MODES — live monitor mode-list refresh (proto v4)
Latency plan P2.1 (design/first-frame-and-resize-latency.md): a new additive control-plane op lets the host refresh a LIVE monitor's advertised target-mode list to lead with an arbitrary new mode (IddCxMonitorUpdateModes2 — the same IddCx 1.10 *2 family this driver already requires, so no new OS floor). This removes the 'mode list frozen at ADD' constraint that forced the mid-stream resize through a REMOVE->ADD monitor hotplug: the monitor's OS identity, its swap-chain worker and the retained FrameStash all survive an in-place mode set. Protocol v4 is ADDITIVE over v3: the host's handshake floor stays at v3 (MIN_DRIVER_PROTOCOL_VERSION) and gates the in-place path on the reported version, keeping re-arrival as the permanent fallback. The driver's stored mode list is swapped before the DDI and reverted if it fails, so the OS and the mode-DDI callbacks always agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
32ffe7d634 |
chore(api): regenerate openapi.json (transition-latency fields + held drift)
Adds StreamInfo.time_to_first_frame_ms / last_resize_ms (latency plan P0.1) and folds in the drift the spec already owed from the held working-tree consolidation (version 0.12.0, pnp_disable_monitors description, the conflicting-host 'conflicts' summary field) — the drift test was already red before this branch; it is green at this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8374dfedf3 |
perf(host): session-transition trace + Welcome-time display prep (native path)
Latency plan P0.1 + P1.1/P1.2 (design/first-frame-and-resize-latency.md): P0.1 — every native session runs a bringup::Trace (hello -> welcome -> start -> punch_done -> display_acquired -> capture_attached -> first_frame -> encoder_open -> first_au -> first_packet), one summary info! line when the first video packet leaves; each accepted resize runs its own trace (reconfigure -> pipeline_rebuilt). Totals surface per session as time_to_first_frame_ms / last_resize_ms in session_status -> mgmt /status, so every subsequent latency change is measured, not vibed. (The Windows manager logs its own activation/settle deltas — correlate by wall clock.) P1.1/P1.2 — on the Windows native path the display bring-up no longer serializes behind the Start round-trip and the up-to-2.5 s hole-punch wait: a prep thread kicks off at Welcome (mode is final there) and runs monitor create -> activation -> verified settle -> capture attach -> first frame -> encoder open while the network waits are in flight; the data plane hands it the post-punch SessionContext and it becomes the stream thread on a warm pipeline. Abort between Welcome and Start drops the hand-off channel and the prep result releases into the keep-alive machinery (stop/quit + watcher are created pre-handshake so a vanished client also aborts the build retries). Same slot-scoped begin_idd_setup serialization as the inline path. Linux keeps the inline bring-up (launch semantics bind before create); GameStream untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e62cd5448e |
perf(host): IDD-push open — poll the HDR-enable settle, wait on the frame event
Latency plan P0.4/P0.6: the fixed 250 ms advanced-color settle becomes a 25 ms poll of the CCD state (ceiling 250 ms, ring still sized FP16 from the successful enable either way), and wait_for_attach waits on the driver's frame-ready event (20 ms cap for the status-code polls) instead of a blind 20 ms sleep, which also sharpens the P0.1 stage stamps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ed5b88407 |
perf(host): replace the Windows bring-up/resize fixed sleeps with verified-state waits
Latency plan P0.2/P0.3/P0.5 (design/first-frame-and-resize-latency.md): - topology settle: the unconditional 1500 ms sleeps after create_monitor's group-topology apply and re_add's reisolate become a 25 ms poll for the committed state (active path + active mode == requested), ceiling 1500 ms — worst case identical, typical case saves ~1.2-1.4 s on every fresh create AND every mid-stream resize. The experimental pnp_disable_monitors sweep keeps the full settle as its floor (it reads OTHER displays' active flags, which the target-scoped wait doesn't verify). - monitor departure: the fixed 400 ms REMOVE settles (re_add + both preempt paths) become a 25 ms poll until the target leaves the active CCD set (2 consecutive absent samples), ceiling 400 ms; the driver-side ghost-reap ADD retry stays the backstop. - activation ladder: 200 ms -> 50 ms sampling, same 3 s per-stage ceilings and the same 3-stage structure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ffa2665ac |
refactor(host): extract src/devtest.rs — the standalone dev/test subcommands
Per plan §W5 (main-cli, 'devtest.rs land first'): move the inline-bodied dev/test subcommand handlers out of main.rs's match into src/devtest.rs — input_test (Linux libei/wlr injection smoke test + its non-Linux stub) and the virtual-gamepad exercisers dualsense_test/switchpro_test (Linux UHID) and deck_windows_spike/ dualsense_windows_test (Windows UMDF + Steam Deck devnode spike). main.rs's arms become one-line forwards; main.rs drops 1004→667 lines. The thin arms that already forward to subsystem modules (zerocopy/capture selftests, probes) stay put — that is their correct layer. Pure code-move (bodies verbatim; crate-local refs qualified with crate::; one doc reword to dodge clippy doc_lazy_continuation now that an arm comment became a /// doc). Verified clippy 0/0 on BOTH Linux (home-worker-5, nvenc,vulkan-encode,pyrowave) and Windows (.173, nvenc,amf-qsv — covers the cfg(windows) handlers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2def3ef49e |
refactor(host): extract native_pairing/sanitize.rs — the untrusted-name scrubber
Per plan §W5: move sanitize_device_name (+ its NAME_MAX cap and unit test) out of the native_pairing facade into native_pairing/sanitize.rs. It is a self-contained, security-relevant leaf — the one place a wire-supplied unpaired-device name is scrubbed of control chars / bidi-override spoofing before it is stored, listed, logged, or shown in the approval UI. Re-export via `pub(crate) use` so crate::native_pairing::sanitize_device_name stays stable (punktfunk1 accept loop + the two in-crate callers). Pure code-move; verified host clippy 0/0 + 11 native_pairing tests green on Linux (home-worker-5). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
571e22bc0f |
refactor(core): consolidate the fingerprint-pinning verifier into core::tls
Per plan §2.5: the security-critical rustls fingerprint-pinning ServerCertVerifier was hand-rolled three times — quic/endpoint.rs (PinVerify), pf-client-core library.rs, punktfunk-tray status.rs — drifting copies on a trust boundary. Add one canonical punktfunk_core::tls::PinVerify (+ cert_fingerprint) behind a light `tls` feature (rustls + sha2 only, no QUIC runtime); `quic` now depends on it, and quic::endpoint re-exports cert_fingerprint so that path stays byte-stable (gamestream + pf-client-core reach it there). - core::tls::PinVerify: new(pin) for the HTTP clients, with_observed(pin, slot) for the QUIC TOFU connect. Behavior-identical to all three originals (pin-check + real CertificateVerify signature verification; only hashes the leaf when a pin or observed slot needs it). Two focused unit tests anchor the boundary. - quic/endpoint.rs: drop the private PinVerify, wire client_pinned through tls::PinVerify::with_observed. - pf-client-core library.rs + tray status.rs: use the shared verifier; tray also routes load_pin through core cert_fingerprint and drops its direct sha2 dep, gaining only the light core `tls` feature (still no host dep, no QUIC runtime). Verified on Linux (home-worker-5): clippy 0/0 for core(quic), core(tls), pf-client-core, tray, host(nvenc,vulkan-encode,pyrowave); core 153 lib tests + loopback 7/7 (pinned handshake) + c_abi round-trip green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ce085b8e3b |
style(vdisplay): dedupe the attach-block comment (first-frame stash follow-up)
Comment-only: the lazy-attach comment carried the delivery-consumption sentence twice after the stash rework. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
405b005a0d |
refactor(host): extract audio/mic_pump.rs — the host-lifetime virtual-mic pump
Per plan §2.1: a self-contained stateful subsystem does not belong in the audio trait facade. Move MicPump + its PumpTuning/PUMP_TUNING, the drain_sleep/pump_thread loop, MIC_CHANNELS/MIC_QUEUE_CAP, and the six pump unit tests out of audio.rs into audio/mic_pump.rs. audio.rs keeps the AudioCapturer/VirtualMic traits, their open_* factories, and the sample constants. Re-export via `pub use mic_pump::MicPump` so crate::audio::MicPump stays byte-stable (only consumer: punktfunk1.rs). Pure code-move; verified clippy 0/0 + 6/6 pump tests green on Linux (home-worker-5). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
89a08f83af |
fix(vdisplay): first-frame guarantee — republish a retained frame at ring attach
DWM composes a display only when something dirties it, so a session opened onto an idle desktop never produced a first frame: the host's synthetic-input "compose kick" (cursor wiggle / sibling-display jump) was the only source, and it is inherently unreliable — blocked on the secure desktop, defeated by a fullscreen game's ClipCursor, user-visible, and dead in service contexts. The field symptom: connect → black stream until something repaints the desktop. Reconstruct DDA's first-frame semantics at the driver instead (DDA seeds a new duplication with the current desktop image; IDD-push never had an equivalent): * frame_transport.rs: new FrameStash — the retained last composed frame, a driver-private copy-only texture. publish() now reports Published / DescMismatch / Dropped, and harvest_into() pulls the last-published ring slot into the stash (keyed-mutex guarded, freshness-checked) before a superseded publisher is dropped — between sessions the driver keeps writing the host-side-dead old ring, so that slot IS the current desktop image. * swap_chain_processor.rs: the worker stashes every frame the ring can NOT take (unattached, or descriptor-mismatched during a mode/HDR-flip race), harvests before a supersede, and REPUBLISHES the stash into every freshly attached ring — the host sees a normal seq=1 publish milliseconds after channel delivery, no compose needed. Zero steady-state cost: matched publishes touch only the ring. The frame-channel stash is now polled every iteration (attach latency = first-frame latency; it was 1-in-30). * monitor.rs: preserved_stash (LUID-tagged) so the retained frame survives swap-chain unassign→reassign flaps, alongside the preserved publisher. * host idd_push.rs: kick_dwm_compose demoted to documented last-resort fallback for pre-stash drivers; a debug log now fires when a kick actually runs so field logs show whether the stash path is working. No proto change: the republish is an ordinary publish, so old host + new driver and new host + old driver both keep working (the latter via the kick). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5748706631 |
refactor(host): hoist the direct-NVENC init-params authoring into nvenc_core
Both backends' build_init_params authored an identical NV_ENC_INITIALIZE_PARAMS (P1/ULL preset, PTD, session dimensions/rate, split-encode mode) — the only difference was the Windows-only enableEncodeAsync flag (Linux is sync-only). Hoist it to nvenc_core::build_init_params(codec_guid, w, h, fps, cfg, split_mode, enable_async); Linux's two call sites pass enable_async=false (the field stays 0 as before), Windows passes its session_async through. Keeps open and in-place reconfigure presenting the SAME init params, now guaranteed identical across platforms too. Verified on BOTH platforms: Linux clippy 0/0 (nvenc,vulkan-encode,pyrowave, RTX 5070 Ti) and Windows clippy 0/0 (nvenc,amf-qsv, RTX 4090 / .173). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2ae5cf98ee |
refactor(host): hoist the direct-NVENC low-latency config into nvenc_core
Both direct-SDK NVENC backends authored a near-identical NV_ENC_CONFIG in build_config: CBR + infinite GOP + P-only + ~1-frame VBV, per-codec tier/level, chroma + bit depth, unconditional colour VUI, and the RFI DPB — ~125 lines each, differing only in comments plus two genuinely per-platform bits (which surface formats carry full chroma / 10-bit input). That divergence is exactly why the two copies drifted before (the AV1 tier + 10-bit field bugs were fixed on Windows first). Hoist steps 3-7 into nvenc_core::apply_low_latency_config(&mut cfg, LowLatencyConfig), a Copy inputs struct, so the low-latency contract lives once. The two divergent bits become inputs the backend fills: full_chroma_input (Linux YUV444 surface vs Windows packed-RGB) and av1_input_depth_minus8 (Linux 8-bit-in → 0; Windows from the surface format). Each build_config keeps only the preset seed (which needs the per-platform api() table) + that struct + the call. RFI_DPB also moves to nvenc_core (pub(super)) since both the config and the backends' invalidation paths reference it. Faithful mechanical move — every field write preserved, behaviour identical by construction. Verified on BOTH platforms: Linux clippy 0/0 (nvenc,vulkan-encode, pyrowave, RTX 5070 Ti) and Windows clippy 0/0 (nvenc,amf-qsv, RTX 4090 / .173). Net -83 lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e61d655b1e |
refactor(host): extract encode/nvenc_core.rs — shared direct-SDK NVENC leaves
The two direct-SDK nvEncodeAPI backends (Windows D3D11 encode/windows/nvenc.rs, Linux CUDA encode/linux/nvenc_cuda.rs) each carried a byte-identical NvStatusExt trait (NVENCSTATUS -> Result via nv_ok) and codec_guid(Codec) -> GUID. Hoist both into a new encode/nvenc_core.rs, the platform-agnostic sibling of the existing encode/nvenc_status.rs (same cfg gate: any(linux,windows) + nvenc). Each backend now imports them via super::nvenc_core; call sites (.nv_ok() ×16/20, the one codec_guid() struct-init) are unchanged. The per-platform machinery — entry-table load (nvEncodeAPI64.dll/LoadLibrary vs libnvidia-encode.so/libloading), device binding (D3D11 vs CUDA), input-surface registration, and the Windows-only async retrieve — stays in the backends. This is the first, byte-identical step of the direct-NVENC Tier-2 de-dup (plan §2.2); the larger build_config authoring is a later, carefully-diffed step. Verified on BOTH platforms: Linux clippy 0/0 (nvenc,vulkan-encode,pyrowave, RTX 5070 Ti) and Windows clippy 0/0 (nvenc,amf-qsv, RTX 4090 / .173). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7099266594 |
refactor(host): hoist the shared low-latency RC contract into encode/libav.rs
The three libavcodec backends each set the identical low-latency rate-control block on the not-yet-opened encoder context: fixed time_base/frame_rate, CBR (bit_rate == max_bit_rate), B-frames off, and a tight ~1-frame VBV/HRD buffer written through the raw rc_buffer_size field. Move it once into apply_low_latency_rc(&mut video, fps, bitrate_bps), and let the long VBV rationale (why the tight buffer prevents high-motion bursts from overflowing the send queue) live in one place instead of only in the NVENC path. Each backend keeps the two genuinely per-backend calls around it: set_format (pixel format differs) before, and gop_size after (NVENC's infinite/intra- refresh wave vs the VAAPI/AMF i32::MAX). No behavior change — the field writes are independent, so the slightly different max_b_frames/rc_buffer_size ordering across backends is irrelevant. Folding the raw rc_buffer_size write into the helper also removes the NVENC path's separate unsafe block. Drops the now-unused ffmpeg::Rational import from all three. Linux check + clippy green (0/0, nvenc,vulkan-encode,pyrowave) on RTX 5070 Ti; ffmpeg_win.rs is Windows-cfg, pending .173 compile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fd8a062e2c |
refactor(host): hoist the libav poll_encoder drain + PollOutcome into encode/libav.rs
The three libavcodec backends each carried a byte-identical single-packet receive_packet drain. Move it once into the shared Tier-2 glue as poll_encoder -> PollOutcome (the richest form: Packet / Again / Eof), and have the callers narrow it: - Linux NVENC (encode/linux/mod.rs): poll() matches the shared fn, collapsing Again|Eof to Ok(None) — was an inlined match, now one call. - VAAPI (encode/linux/vaapi.rs): drop the local poll_encoder; the blocking budget loop lets Again|Eof fall through to the deadline check, byte-identical to the old Option::None path. - Windows AMF/QSV (encode/windows/ffmpeg_win.rs): drop the local PollOutcome + poll_encoder; its deadline-driven drain already matches PollOutcome, so only the import changes. No behavior change on any backend. Still a plain monomorphic free fn over a borrowed &mut Encoder — no new per-frame dyn/Box/alloc; the only allocation is the same bitstream to_vec() each path already made. Drops the now-unused ffmpeg::Packet import from all three. Linux check + clippy green (nvenc,vulkan-encode,pyrowave) on RTX 5070 Ti; ffmpeg_win.rs is Windows-cfg, pending .173 compile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c38a5f0e8 |
refactor(host): hoist shared libav glue into encode/libav.rs (pixel_to_av + swscale consts)
First step of the W2 libav de-dup (plan §2.2, the missing Tier-2 mid-layer). The three libavcodec backends (Linux NVENC, VAAPI, Windows AMF/QSV) each carried a byte-identical pixel_to_av plus the SWS_POINT / SWS_CS_ITU709 (/SWS_CS_BT2020) swscale consts. Hoist them into a new encode/libav.rs and import from super::libav. The module is gated to compile exactly when a libav backend does (linux, or windows+amf-qsv). Free fns/consts over borrowed handles — no per-frame dyn/alloc, off the zero-copy path. Verified: Linux cargo check green (linux/mod.rs + vaapi.rs compile against it); ffmpeg_win.rs is Windows-cfg — same mechanical swap, covered by Windows CI on push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1f519d44f9 |
docs(core): backfill //! module docs on quic/{io,endpoint,pake}
The last three //!-less modules in the tree (plan §2.5 / §3.2):
- io: length-prefixed control-message framing (read_msg/write_msg)
- endpoint: QUIC endpoint construction + transport tuning + the TOFU
cert-pinning verifier (PinVerify)
- pake: SPAKE2 pairing key exchange
Docs only — no code, type, or wire-format change (cbindgen header byte-identical).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
abecb5226c |
refactor(host): route the three libav backends' VBV re-parse through vbv_frames_env
The libavcodec paths (Linux NVENC, VAAPI, Windows QSV) each re-parsed
PUNKTFUNK_VBV_FRAMES locally in f32, duplicating and diverging in precision from
the f64 vbv_frames_env() helper the direct-NVENC/AMF paths already use. Now that
the helper lives in encode/codec.rs (
|
||
|
|
11045a0f70 |
chore: consolidate parallel-session WIP (HOLD — do not push)
Local snapshot of intermingled in-flight work, committed to unblock the encode
refactor (a clean ffmpeg_win.rs for the vbv-dedup follow-on). These hunks span
the same files and can't be cleanly split here; the commit bundles three
distinct workstreams that each belong in their own PR:
- logging rework (~43 files: level re-tiering, structured fields, `?e`,
hot-path flood latches)
- conflicting-host detection (detect.rs + detect/{linux,windows}.rs + wiring
in main.rs/mgmt.rs/Cargo.toml/docs/packaging)
- standby-sink DWM-stall attribution (windows/display_events.rs + capture/
vdisplay wiring)
NOT verified as a combination. NOT to be pushed until the refactor is done and
these are re-verified and reorganized into their proper per-workstream PRs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
d466e3e2b2 |
chore(nix): NixOS flake snapshot — host+client packages, module, devShell (WIP)
Local snapshot of the in-flight NixOS support: flake.nix + flake.lock + nix/ (crane host and client packages, services.punktfunk module, devShell). Standalone — nothing in the Rust/Cargo tree references it. Held from push pending its owning session's finalization (Skia-under-Nix follow-up + intended per-workstream PR). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
532b313b8c |
refactor(host): extract encode/codec.rs — the encoder contract
Move the Tier-1 encoder contract out of the stuffed encode.rs facade into a new encode/codec.rs submodule (plan §7 / W2): EncodedFrame, Codec (all methods except host_wire_caps), ChromaFormat, EncoderCaps, the Encoder trait, validate_dimensions, vbv_frames_env, and the dimension + wire-roundtrip contract tests. host_wire_caps stays in encode.rs alongside the backend-selection probes it depends on; CodecSupport and its wire-mask test stay too. encode.rs gains `mod codec;` + `pub(crate) use codec::*;` so every existing crate::encode::X path — crate::encode::vbv_frames_env, ::Codec, ::Encoder, … — stays byte-stable. Pure relocation: no call sites touched. Verified: dev-Mac type-check of both files clean; Linux `cargo check -p punktfunk-host --features nvenc,vulkan-encode,pyrowave` green (all encode backends compile against the relocated contract); contract unit tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d381cdf7f4 |
fix(host): NVENC open-failure resilience — backoff, failed-open hygiene, self-diagnosis
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 51s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
apple / swift (push) Successful in 1m9s
decky / build-publish (push) Successful in 18s
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 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
ci / bench (push) Successful in 5m48s
docker / deploy-docs (push) Successful in 24s
apple / screenshots (push) Successful in 5m18s
windows-host / package (push) Successful in 9m16s
arch / build-publish (push) Successful in 10m53s
android / android (push) Successful in 11m58s
deb / build-publish (push) Successful in 18m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m19s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m28s
ci / rust (push) Successful in 24m56s
Field report (Linux direct NVENC): after a codec switch, every session open failed with NV_ENC_ERR_INVALID_VERSION until the host process was restarted — so the poisoned state is per-process, not a driver install issue. On-hardware investigation (RTX 5070 Ti, 610.43.03) could not reproduce it with clean codec cycles, dirty teardowns, or open/destroy storms, but established the failure class: the driver enforces a per-process concurrent-session cap (12 there, status INCOMPATIBLE_CLIENT_KEY; other branches report differently) whose exhaustion is exactly this signature — persistent open failures healed only by a process restart. Harden every path that can feed or mask that state: * Rebuild backoff: the in-place encoder-rebuild retries slept one frame interval, so all 5 attempts burned within ~40 ms at 120 Hz — no driver-side transient (deferred teardown of the previous session, engine reset) can clear that fast. Exponential backoff 100 ms → 1.6 s (~3 s total) so transients heal instead of killing the session. * Destroy-on-failed-open (Linux + Windows, all four open sites): the NVENC docs require NvEncDestroyEncoder even when OpenEncodeSessionEx FAILS — the driver may have allocated the session slot before erroring. Without it a retry burst against a transient leaks slots toward the cap, converting the transient into permanent exhaustion. * Teardown: a destroy_encoder failure (a session slot the driver may keep) is now logged with its status instead of silently discarded. * One-shot self-diagnosis on a failed session open (Linux): retry the raw open on a fresh dedicated CUDA context and log which of the three causes applies — shared-context poisoned (fresh works), driver-level skew/exhaustion/GPU loss (fresh fails the same way), or CUDA itself unhealthy (no fresh context) — so the next field report pinpoints the root cause with zero reporter effort. On-hardware regression tests (RTX box .21, all green): codec-switch reopen cycle (H265→AV1→H265→H264→H265), dirty teardown with in-flight encodes, and the full open-failure→diagnosis→in-place-recovery path via real session-cap exhaustion. Existing RFI/reconfigure/4:4:4 smokes still pass; clippy clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f901bedf22 |
fix(host): actionable NVENC error logging — drop misleading "(no NVIDIA GPU?)"
ci / web (push) Successful in 53s
ci / docs-site (push) Successful in 1m2s
apple / swift (push) Successful in 1m10s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 40s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 56s
apple / screenshots (push) Successful in 5m24s
ci / bench (push) Successful in 7m32s
docker / deploy-docs (push) Successful in 28s
arch / build-publish (push) Successful in 11m51s
windows-host / package (push) Successful in 14m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m52s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m54s
android / android (push) Successful in 17m12s
deb / build-publish (push) Successful in 18m12s
ci / rust (push) Successful in 24m52s
Every NVENC entry-point failure was annotated "(no NVIDIA GPU?)", which
misled triage: the direct-NVENC path only loads on a machine that HAS an
NVIDIA GPU. A Linux user hit NV_ENC_ERR_INVALID_VERSION at
open_encode_session_ex (past the NvEncodeAPIGetMaxSupportedVersion pre-flight
gate) — the signature of a userspace/kernel driver version skew that a host
reboot fixes — and the log pointed at a missing GPU instead. A restart did
fix it.
Add encode/nvenc_status.rs: a shared NVENCSTATUS -> cause mapper that folds
the real cause into the anyhow::Error at construction, so every downstream
{e:#} log (the encode-recovery loop, session teardown) improves for free.
INVALID_VERSION now reads "update the NVIDIA driver, or reboot if you just
updated it (a host restart is the usual fix)"; NO_ENCODE_DEVICE /
DEVICE_NOT_EXIST / INCOMPATIBLE_CLIENT_KEY (session-count limit) / OOM /
UNSUPPORTED_PARAM get their own glosses. The required API version comes from
the SDK consts so it stays correct across crate bumps.
Wire it into all NVENC entry-point failures in both backends
(encode/linux/nvenc_cuda.rs, encode/windows/nvenc.rs) — every open, init,
preset/resource/bitstream call.
Also: when the encode-recovery loop exhausts its in-place rebuilds it now
logs a clear terminal line with the underlying cause instead of the session
silently vanishing after the last identical "rebuilt in place" line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ab4c9e44cc |
fix(apple): drop premature ITSAppUsesNonExemptEncryption — unblock uploads
ci / docs-site (push) Successful in 50s
ci / web (push) Successful in 54s
ci / bench (push) Successful in 6m1s
ci / rust (push) Successful in 24m53s
apple / swift (push) Successful in 1m13s
apple / screenshots (push) Successful in 4m49s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
decky / build-publish (push) Successful in 25s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
android-screenshots / screenshots (push) Successful in 3m17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m39s
windows-host / package (push) Successful in 8m42s
release / apple (push) Successful in 9m30s
linux-client-screenshots / screenshots (push) Successful in 9m1s
docker / deploy-docs (push) Successful in 24s
web-screenshots / screenshots (push) Successful in 2m52s
deb / build-publish (push) Successful in 12m52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m4s
arch / build-publish (push) Successful in 13m47s
android / android (push) Successful in 14m35s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m10s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m52s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m0s
flatpak / build-publish (push) Successful in 6m35s
Setting ITSAppUsesNonExemptEncryption=true (
v0.12.0
|
||
|
|
ac60db5410 |
fix(host): make the stream-marker lifecycle test parallel-safe
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 53s
decky / build-publish (push) Successful in 22s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 7s
ci / bench (push) Successful in 6m20s
docker / deploy-docs (push) Successful in 24s
windows-host / package (push) Successful in 8m17s
arch / build-publish (push) Successful in 12m27s
android / android (push) Successful in 13m42s
deb / build-publish (push) Successful in 14m38s
ci / rust (push) Successful in 19m11s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m52s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
marker_appears_while_held_and_vanishes_after drove the PROCESS-GLOBAL registry and mutated XDG_RUNTIME_DIR mid-run — the punktfunk1 integration tests announce real sessions concurrently in the same test process, so whichever registered first became the primary and the marker carried its mode instead of the test's 2560x1440 (flaky on CI, green locally by timing). The registry gains insert/remove methods and rewrite() takes the target path, so the test now exercises the same end-to-end lifecycle (atomic write, primary retention, session count, removal) against a LOCAL registry and an explicit temp path — no env mutation, no shared state. Production behavior unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |