a14b000c9bacc9cede18382b2af235defedb09d2
2875
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a14b000c9b |
feat(apple): the tray gradient blur is back — as a real blur this time
The form screens' pinned title and legend lost their gradient blur when
GamepadTrayScrim went (
|
||
|
|
bac63059a9 |
feat(apple): drive the console UI from a hardware keyboard
Asked for by a field user: "make the iPadOS client compatible with keyboard to select games with keyboard arrows, enter to launch". An iPad on a Magic Keyboard and a couch Mac are the same situation the console layout was built for — a screen driven from a distance with a fixed set of directional inputs — and the cursor/confirm/back model already exists here for the pad. A keyboard is a third input onto it, not a new navigation scheme: arrows move, Return and Space activate, Esc backs out, everywhere the controller already worked (carousel, menu lists, prompts) plus the plain poster grid. `active` mirrors each caller's existing controller gate rather than being a second, parallel notion of "who has input". Without that, a launcher sitting under an open screen would keep eating key presses and navigate behind it — the same defect the pad gate exists to prevent. Esc returns `.ignored` when a screen has no back action, so it still reaches the `.cancelAction` shortcut that closes a macOS sheet. The plain grid needed real arithmetic rather than a flat index. It renders up to TWO `LazyVGrid` sections (launchers above titles), so a flat index steps by the wrong amount at the boundary whenever the first section's last row is partial — up from the titles' first row lands mid-launcher-row instead of above. `LibraryGridNav` moves within a section and hands off at its edges preserving the column, clamping into partial rows. It lives in PunktfunkKit because it is edge-case arithmetic and that is the target tests can reach; 12 cases cover the partial row, the hand-off, a stale cursor, an empty grid and a zero column count. The column count comes from the grid's MEASURED width run through `.adaptive`'s own fitting rule, so up/down move exactly one visual row instead of a guess that drifts with window size. Measured via a background GeometryReader — a sibling inside a ScrollView would claim the whole viewport. The grid cursor starts nil and only appears on the first arrow press, so a touch user is never shown a selection they didn't ask for. tvOS is excluded throughout: its focus engine already routes hardware arrows, and these screens hand it navigation authority deliberately. 17 PunktfunkKit tests pass; macOS + tvOS typecheck; launcher and settings verified rendering and navigating in the iPad Pro 13" simulator. |
||
|
|
3daead7d71 |
fix(apple): the sub-screens' legends sit where the launcher's does
Carries the display-corner inset (previous commit) through the three screens that pin the same legend pill — settings, add-host, pair — so they don't drift from the launcher the moment the user opens one. Each of those trays is `maxWidth: .infinity, alignment: .leading`, so the pill is left-aligned there too and the iPad's short centre indicator is never in its way; the keyboard tray sits ABOVE the legend in the stack, so pulling the tray down doesn't put keycaps over the strip. And the settings tab strip now takes its padding from the row metrics instead of two fixed numbers. It was the other half of the iPad sizing complaint: the tier work grew the fields under it while the tab pills stayed at phone scale, which read as a tab bar that belonged to a different screen. macOS + tvOS typecheck. Re-landed on the probe spelling: the corner inset is read from `\.displayBottomInset` (DisplayBottomInsetProbe via ContentView), never asked of UIKit during body — the original spelling froze the update graph of any view that called it on device (see the legend-pinning commit). |
||
|
|
18d0009c35 |
feat(apple): the trust prompt takes controller input
Trust-on-first-use had no pad wiring at all, and it appears at the worst possible moment for that: mid-connect, with capture already disabled so ContentView can blur the stream and stop forwarding. A pad-only user pressed A to connect, got this card, and found the controller in their hands doing nothing — the only way past was to reach for the screen. A trusts, B cancels, X runs the PIN ceremony instead. The legend only appears with a pad attached; naming controller buttons to a trackpad user would be describing hardware they don't have. `GamepadManager` is observed rather than read once so the bar arrives if a pad wakes up while the card is already open. Safe to poll unconditionally while the card is up: capture is off for its whole lifetime and the home screens are unmounted behind the session view, so nothing else is reading the pad. The A press that STARTED the connect is typically still held when this mounts — `GamepadMenuInput` adopts the held state without firing on start, so it doesn't auto-trust. tvOS is untouched: the focus engine already drives these buttons. macOS + tvOS typecheck. |
||
|
|
8ef5350431 |
fix(apple): pin the launcher legend to the display's corner
On an iPad the controls pill sat two to three times further from the bottom of
the screen than from the left, so it read as floating rather than tucked into
the corner. `safeAreaInset` places its content INSIDE the safe area, so the
pill's bottom margin was stacking on top of the device's own ~24 pt
home-indicator inset while the leading margin had nothing to stack on.
Subtracting that inset is the fix; GETTING the inset is where this commit's
history is. Three spellings failed before this one, each silently:
- a `GeometryReader` carrying `.ignoresSafeArea()` — a proxy reports no
inset for an edge it has been told to ignore, so it can only answer 0;
- `.ignoresSafeArea(.container, edges: .bottom)` on the inset CONTENT,
which does not move content the inset mechanism itself placed;
- asking UIKit for the key window (`UIApplication.shared.connectedScenes…`)
DURING body — which answers the right number and then KILLS the asking
view. On a physical iPad (never the simulator) that walk re-enters UIKit
layout mid-render and SwiftUI silently severs the view's update graph:
from then on every `@State` write lands in storage without `body` ever
running again. That is exactly how Settings and Add Host stopped opening
while their triggers kept firing and `showSettings` kept reading true —
no AttributeGraph warning, nothing in the log. Found by bisecting device
builds; a build that opens the screen programmatically renders green with
input-driven navigation completely dead, so only a real press counts.
So the inset is measured where it is knowable: DisplayBottomInsetProbe, a
UIView that reads its WINDOW's inset from UIKit's own callbacks
(didMoveToWindow / safeAreaInsetsDidChange / layoutSubviews), hops out of the
current update, and publishes through `\.displayBottomInset` from ContentView.
The legend reads the environment — proven safe on glass — and
`gamepadLegendBottomPadding` is pure arithmetic.
Tablets only. The padding goes negative to pull the pill back down through the
indicator strip, which is safe there because the pill is left-aligned and an
iPad's indicator is a short bar in the middle. A phone's indicator is taller
and its legend runs most of the width, so the same move would cross it —
phones keep the plain margin, and the tier comes from the size classes, so an
iPad in a narrow Stage Manager window is treated as the in-hand case it is.
macOS + tvOS typecheck; margins and Settings/Add Host opening from a real
controller press verified on the iPad itself.
|
||
|
|
522ac7bd49 |
feat(apple): the connect path's modals are pad-navigable
`.alert` and `.confirmationDialog` are UIKit/AppKit surfaces: a game controller cannot move through their buttons or press one. On iOS/macOS that made every prompt in the connect path a dead end for a pad-only user, and they are not incidental prompts — "Pairing required" is the FIRST thing an unpaired host shows (so pairing was unreachable before it even got to a PIN), "Connection failed" strands the console UI behind a modal only a finger can dismiss, and "Waiting for approval" owns the only Cancel for a connect that may never complete. GamepadPromptView renders those states as a console card with a focus list of actions; the system alerts stand down while it is up. tvOS keeps them — the focus engine drives them natively there, which is exactly why this gap was invisible from that platform. Two things are deliberate rather than incidental: The gate is "not STREAMING", not `model.connection == nil`. A connection object exists well before a stream does — through the pair-required and approval handshakes, precisely when these fire — so gating on the connection would hand those cases back to the system dialog. Streaming is the one state that must keep the alert: there the pad belongs to GamepadCapture. And the overlay hangs off `driven`, not `home`, for the same reason: `home` renders only while the connection is nil, so a prompt mounted there would be skipped in the very case it was written for. The launcher stands down from the controller poll while a prompt is up (`promptActive`) — without it the host carousel keeps scrolling underneath the modal and one A press reaches both. macOS + tvOS typecheck; console UI verified opening Settings in the iPad simulator with the prompts wired in. |
||
|
|
8ab4918923 |
fix(apple): the host strip stopped flashing as it settles
Reported from the iPad: the cards arrive, come to rest, and then flash. They did — and it was not the motion. Every card sat at the WRONG fill for as long as the entrance animation ran, then snapped to its real one in a single frame once SwiftUI tore the animation down, roughly a third of a second AFTER the last card had stopped moving. `rotation3DEffect` was the cause. A real 3D transform renders its subtree through an offscreen layer, and these cards carry translucent glass, which resolves differently in there — so the strip showed the aurora through itself for the duration and went opaque the instant the layer was dropped. Measured on an iPad Pro 13", sampling the centred tile: it held #4a3d87 across twelve frames in which nothing moved, then stepped to #423970 — 23/255 of blue in one frame, on every card at once, while the backdrop beside them stayed exactly #713fa0 throughout. Stretching the timeline from 1.02 s to 2.82 s moved the step from 0.70 s to 2.50 s after launch: the same 0.32 s before the end in both runs, which is what proves it is the animation ENDING and not any state the tiles were waiting on. Deleting the rotation deleted the step outright. `compositingGroup()` above and below the transforms each changed nothing, so this is the 3D layer specifically, not compositing in general. The turn is now projected instead of rendered: cos(angle) as a horizontal squeeze IS the orthographic projection of a Y-axis rotation, hinged on the edge the card fans from — which also puts the code behind the doc comment that already described the fan that way (the sign of a rotation carried it before; cos is even, so the anchor has to). Affine, so there is no offscreen pass and no layer to drop. What's lost is the perspective trapezoid, which at card size was never what sold the motion; the fan-out, the stagger and the overshoot are untouched. Also drops the temporary PUNKTFUNK_DIAG_OPEN hook that gated each step of this branch in the simulator. 284 tests pass; macOS + tvOS typecheck; entrance re-recorded on the iPad Pro simulator — the tile now settles monotonically (#4153cf → #383c77 → #39396e → #393a6e) and holds, with the fan-out still reading as a turn. |
||
|
|
09bac99090 |
feat(apple): pairing you can actually do with a controller
PairSheet is a `Form` with two `TextField`s. On tvOS the focus engine drives those natively, but on iOS/macOS a controller cannot reach a text field, type into it, or press the button underneath — so for anyone in the console UI, pairing (the ONE thing between a fresh install and a first stream) ended at "now touch the screen". GamepadPairView is the same ceremony in the gamepad UI's own vocabulary: the vertical focus list the settings and add-host screens use, A on a field to open GamepadKeyboard in a bottom tray, B to peel one layer. It mirrors GamepadAddHostView field for field, because it is the same interaction and someone who has added a host should recognise it immediately. The ceremony itself moved to a shared `PairCeremony` used by both presentations, so they can never disagree about what a wrong PIN means, what a host rejection says, or when a late result must be discarded. On iOS it is a shell layer like settings and add-host, and it LEADS the shell's screen order: it blocks a connect the user already asked for and can be raised from on top of the library (launching a title on an unpaired host), so it has to win; backing out reveals whatever it interrupted. macOS has no shell, so its sheet switches content by mode instead. tvOS is untouched. macOS + tvOS typecheck; console UI verified opening Settings in the iPad simulator with the pair screen wired into the shell. |
||
|
|
5db3b3c4fd |
feat(apple): the controls legend is clickable
Every hint cell that names an action is a real button on iOS/iPadOS/macOS. The legend already lists every action a screen has, in one fixed place, so a user without a pad in their hands — an iPad on a stand, a Mac driven by trackpad, anyone running `gamepadUIMode == "always"` — was reading a complete menu they could not press. tvOS keeps them inert deliberately. There is no pointer there, so a tappable cell would have to be FOCUSABLE, and that puts six new stops in the path of a focus engine whose flow on these screens is load-bearing and hard-won — while every action in the legend already has a native route (select, Menu, Play/Pause, the focusable tab pills). Cells that name an INPUT rather than an action stay labels: "↔ Adjust" is the stick itself, and "A Type" over the on-screen keyboard has no tap equivalent because a touch user types by tapping the keycap. Two details that are load-bearing rather than tidy: the decorative hairline gets `allowsHitTesting(false)` (it sits on top of the cells), and the press style's `contentShape` sits below its `scaleEffect` so shrinking the artwork cannot move the target out from under a resting finger and lose the touch-up. macOS + tvOS typecheck; 272 tests pass; console UI verified opening Settings in the iPad simulator. |
||
|
|
ffb1ecfebe |
feat(apple): size the console UI for the iPad
GamepadFormMetrics had exactly two tiers, tvOS and everything-else, so a 13" iPad Pro drew its settings rows, section tabs and legend at iPhone scale in the middle of a very large display — the field verdict was that the sizing "does not adapt to larger screens". There is a third tier now, between the in-hand and 10-foot sets. Chosen from the SIZE CLASSES, not the device idiom: an iPad in a narrow Stage Manager or Split View window keeps the in-hand numbers, because what is being read is the window, not the panel behind it. Phone sizing is untouched. Published from ContentView — the app root — rather than only from `gamepadPaletteInk`. A screen that applies that modifier itself sits ABOVE its own copy of the environment, so its `@Environment` resolves against its parent and would read the bare phone default no matter what the modifier published. The screenshot harness publishes it too, for the same reason: it mounts these screens with no ContentView above them, so an iPad capture used to render the whole gamepad UI at iPhone scale. macOS + tvOS typecheck; the console UI verified in the iPad simulator opening Settings at the tablet tier. |
||
|
|
08e462fee9 |
fix(apple): Graphite's selected settings tab was white on light grey
The tab strip's selected pill is FILLED with the palette accent, and `ink.onAccent` — chosen by that accent's own luminance, and documented for "a filled pill's label" — has existed for exactly this since the palettes landed. The strip was using the plain foreground instead, which is white on every dark palette and therefore looked fine on all but one: Graphite's accent is a light grey (luma ≈ 0.80), so its selected tab read white on white. |
||
|
|
2590238b8f |
fix(apple): the console sub-screens' backdrop, glass and option band
Four things the field reported on glass, all in the console's visual layer.
The tray scrim is GONE, not re-tuned. It laid `.ultraThinMaterial` across the
top and bottom of every form screen, and a material by definition lifts and
tints whatever it blurs — so it read grey, and washing it with the palette's
ground only made it read coloured. There is no public way to blur a backdrop
without that tint, so the layer had to go. The darkening it also provided was
never its to provide: the backdrop's own legibility scrim
(0.38/0.06/0.08/0.40 — the same gradient pf-console-ui bakes into its shader)
has always been one layer down, which is why the console has no tray band.
Pale palettes rendered every row, pill and card as a flat white slab, and
lowering the tint did nothing, because the opacity came from the glass BODY:
`.regular` is a bright, high-body material and a pale palette's `ink.glass` is
literal white. Pale palettes take `Glass.clear` now, with a light wash to keep
dark ink legible.
A focused settings row changed colour in a hard jump, a beat late, while its
scale animated smoothly beside it — the focus tint rode `Glass.tint`, and a
Glass value is opaque to SwiftUI's animation system. It is a plain fill
between the glass and the label now, so the existing animation covers it.
The host cards appeared to be swapped for different ones as their entrance
landed: `CardEntrance` swings each tile in on a rotation3DEffect, and Liquid
Glass samples the backdrop through its own layer, which it cannot do under a
3D transform. Those tiles take the material path (`forceMaterial`).
And the option band never turned like a cylinder because the band MASKED
itself: a mask rasterises what it covers, flattening `rotation3DEffect`'s
perspective, so the projection was computed and discarded every frame. The
soft edge is folded into each option's own opacity instead. Its ±1 neighbours
stay hidden at rest — showing them reproduced the documented overlap defect
("2752 × 2064" with "280 ×" through it), which is why they were dropped.
Also fixes pale palettes washing out: the calm mix added a plusLighter wash of
a near-white ground on top of a field already mixed toward it, saturating the
form screens to white.
macOS + tvOS typecheck; console UI verified opening Settings in the simulator,
with no tray band.
|
||
|
|
beb639f067 |
fix(ui): the OLED palette is called Eclipse now
Every other background reads as a place or a material — Violet, Nebula, Abyss, Ember, Moss, Graphite, Holo, Sunset, Bloom, Dawn, Mint, Opal — and "OLED" was a spec sheet sitting among them, naming the panel it suits rather than what it looks like. It looks like black with a thin violet corona at one corner, which is an eclipse, and that puts it beside Nebula and Abyss. The ID stays "oled" in all three clients: it is the stored ui_palette value AND the cross-client key, so renaming it would orphan every saved choice and silently desync Apple, Android and the console UI. Only the label moved. No test or doc pinned the old string. Palette tests pass; macOS builds. |
||
|
|
90450ff1f6 |
feat(apple): the legends remember which pad you own
`buttonGlyph` asked the LIVE controller for its `sfSymbolsName` and fell back to generic letter glyphs, so the moment a DualSense slept, disconnected or ran flat, every ✕/◯ hint turned into A/B under its owner's hands. Under `gamepadUIMode == "always"` — the console UI up with no pad by design — they were never right at all. GamepadManager now remembers the family of the last controller that was actually attached (`DefaultsKey.lastGamepadKind`, written on selection and deliberately never cleared on disconnect), and the legends resolve live → remembered → generic. Not a user-facing setting: a glyph-style picker is one more row asking a question the app can answer itself, and the remembered pad is right essentially always. GamepadGlyphs maps by POSITION, not by name, because that is what `GCExtendedGamepad`'s buttonA/B/X/Y are. So ✕ is role `.a` (the bottom button), and a Switch pad gets Nintendo's transposed labels rather than Xbox letters printed on the wrong physical buttons. The test asserts every symbol name RESOLVES on the running OS. That is the load-bearing one: `Image(systemName:)` draws an unknown name as nothing at all — no crash, no log, no red build — so a typo would silently blank a legend on device and no amount of building would catch it. macOS + tvOS typecheck; the 5 glyph tests pass; the console UI verified opening its Settings screen in the iPad simulator. |
||
|
|
1317901122 |
Merge pull request 'Uninstalling the Windows host left every audio device it minted behind forever — and the installer script documented that as a decision' (#145) from worktree-win-audio-uninstall-cleanup into main
android / android (push) Failing after 1m32s
ci / rust-arm64 (push) Successful in 1m53s
apple / swift (push) Successful in 1m34s
ci / bun-nix (push) Successful in 22s
ci / web (push) Successful in 1m48s
ci / docs-site (push) Successful in 1m44s
deb / build-publish-client-arm64 (push) Successful in 1m44s
deb / build-publish (push) Successful in 4m5s
ci / rust (push) Successful in 7m24s
apple / screenshots (push) Successful in 5m54s
arch / build-publish (push) Successful in 9m40s
deb / build-publish-host (push) Successful in 7m28s
windows-host / package (push) Successful in 13m52s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 20s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 7s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 7s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / builders-arm64cross (push) Successful in 7s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m4s
docker / deploy-docs (push) Failing after 6m14s
Reviewed-on: #145 |
||
|
|
7a9fa4501c |
Merge pull request 'Nobara could never use the patched gamescope — and the RPM it was told to install was unsigned' (#144) from worktree-gamescope-pin-bump-nobara into main
apple / swift (push) Successful in 1m35s
ci / rust-arm64 (push) Successful in 1m59s
android / android (push) Failing after 2m37s
ci / bun-nix (push) Successful in 17s
ci / docs-site (push) Successful in 1m19s
ci / web (push) Successful in 2m27s
deb / build-publish-client-arm64 (push) Successful in 1m54s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 8s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 10s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 13s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
deb / build-publish (push) Successful in 3m37s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
apple / screenshots (push) Successful in 5m43s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Failing after 41s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m2s
docker / deploy-docs (push) Skipped
arch / build-publish (push) Successful in 9m55s
deb / build-publish-host (push) Successful in 6m53s
docker / builders-arm64cross (push) Successful in 12s
ci / rust (push) Successful in 11m23s
flatpak / build-publish (push) Successful in 9m37s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 8m57s
windows-host / package (push) Successful in 18m53s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 16s
nix / flake (push) Failing after 17m1s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m26s
Reviewed-on: #144 |
||
|
|
d87a8df28d |
fix(windows): uninstall removes the audio devices the host mints
ci / bun-nix (pull_request) Successful in 25s
ci / web (pull_request) Successful in 1m21s
ci / docs-site (pull_request) Successful in 1m23s
apple / swift (pull_request) Successful in 1m42s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 2m37s
android / android (pull_request) Successful in 6m12s
ci / rust (pull_request) Successful in 7m37s
The field report: uninstalling punktfunk left "Punktfunk Speakers", "Punktfunk Microphone" and the per-pad "Wireless Controller" endpoints sitting in Windows' Sound settings forever. They have no installer payload behind them, which is why nothing in the uninstall touched them. The host mints them at RUNTIME as extra devnodes on Valve's streaming-audio drivers, and both providers deliberately re-resolve their devnode across restarts instead of re-minting it — so they persist by design. Persistent across restarts must not mean permanent: the .iss even documented leaving them behind as a decision. New `driver uninstall --audio` leg (a third Inno [UninstallRun] entry, after the two driver legs and well after `service uninstall`, since a live host re-mints on its next wiring pass): * restores the default playback device first, if a host that died mid-stream left it parked on our loopback sink — otherwise Windows re-picks by its own ranking rather than giving the operator back the device they had; * removes every MEDIA-class devnode carrying one of our three durable owner markers (pad slot, minted role, probe), phantoms included; * deletes each endpoint's MMDevices record, resolved through the devnode link BEFORE the devnode goes. Marker-matched, never name-matched: our instances are name-identical to Steam's own, and Steam's devnodes, its drivers, and a VB-CABLE from the era when we bundled one carry no marker and stay untouched. A ROOT\ enumeration guard means a marker-shaped value on a real sound card can never cost the user their hardware. The registry half is best-effort: those keys are SYSTEM-owned and the uninstaller runs elevated but as a user, so on a stock box the record survives as an inert NOTPRESENT entry that Sound settings only shows behind "Show Disconnected Devices". The device itself is gone either way, and seizing ownership of SYSTEM registry keys from an uninstaller is a worse thing to ship than that scrap. |
||
|
|
46390739d8 |
fix(pf-vdisplay): the box's OWN session unit needs the gamescope bind too
apple / swift (pull_request) Successful in 1m39s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 5m40s
nix / flake (pull_request) Failing after 19m15s
ci / bun-nix (pull_request) Successful in 20s
ci / docs-site (pull_request) Successful in 1m1s
ci / web (pull_request) Successful in 1m11s
ci / rust-arm64 (pull_request) Successful in 1m28s
ci / rust (pull_request) Successful in 6m26s
`launch_session` spawns a transient unit and can hand `systemd-run` the `BindReadOnlyPaths` directly, but a box that owns an autologin `gamescope-session-plus@<client>.service` is RESTARTED IN PLACE instead — no `systemd-run`, so that path kept running Nobara's hardcoded `/usr/bin/gamescope` and the previous commit fixed only half the problem. Found on the box: after a reboot the host took the `ensure_box_gamescope_mode` path (the autologin unit was live) rather than the managed one. Deliver the same two fixes as a drop-in on that unit — the bind, and the WSI opt-out when the box's layer was built for a different gamescope — plus `PF_HZ`/`PF_HDR_ARGS`, which the wrapper reads and would otherwise default to 60 Hz. `daemon-reload` before the restart or systemd runs the old unit. Best-effort: a failure to write it must not block a restart that would otherwise work, and it is a no-op on a box already resolving to `/usr/bin/gamescope`. ⚠ REMOVED on restore, deliberately. Leaving it would put the patched build — and our HDR and cursor flags — under the user's ORDINARY game mode, which is exactly what `packaging/gamescope/README.md`'s "sits BESIDE the distro package" rule exists to prevent. The bind is ours only for as long as we are driving the session. `ensure_box_gamescope_mode` grows an `hdr` param to build those args; both call sites already had it in scope (`self.hdr`, and `create_managed_session`'s parameter). Gate: `scripts/xcheck.sh linux clippy` clean (0 warning/error lines), `cargo fmt` clean. |
||
|
|
3500e95660 |
fix(pf-vdisplay): make Nobara's session run the patched gamescope, and stop the WSI layer killing every client
Two independent reasons a Nobara box could never stream from a gamescope session,
both found on glass (VM 123, Nobara 44, RTX 5070 Ti).
**1. The session ran a stock gamescope, so the host refused it.**
Nobara's `gamescope-session-plus` builds its command as
GAMESCOPECMD="/usr/bin/gamescope \
and reads `GAMESCOPE_BIN` NOWHERE. All three of our spawn levers miss at once: the env
var is ignored, and an absolute path cannot be redirected by a PATH shim. So the session
ran stock gamescope, the capability probe rejected it, and every session died with
"pipeline build failed (out of retries) … it ignored GAMESCOPE_BIN / the PATH shim".
`~/.gamescope-cmd.log` — which the script writes with the exact command it ran — settles
that in one line, and is the first thing to read on any such report.
Fixed by binding our wrapper over `/usr/bin/gamescope` inside the transient unit's mount
namespace (`BindReadOnlyPaths`). Deliberately a bind, not a replacement: punktfunk-gamescope
ships under its own name precisely so it sits BESIDE the distro package, and the bind is
scoped to the session — nothing outside it sees the redirect and nothing is written to
`/usr`. Skipped when the resolved binary already IS `/usr/bin/gamescope`.
**2. With the patched gamescope finally running, every Vulkan client died — black screen.**
The box's `VkLayer_FROG_gamescope_wsi` ships with the DISTRO's gamescope and speaks its
`gamescope_swapchain` protocol. Ours disagrees, so the compositor rejects the client's
`swapchain_feedback` ("message too short") and drops it. Steam never paints; there is no
other symptom, which is what makes it expensive to find.
Measured with `vkcube` under each build, layer on:
ours 3.16.25-17 ON -> 1 rejected client
ours 3.16.25-17 OFF -> 0
OLD pin 3.16.25-4 ON -> 1 rejected client
stock 3.16.23.2 ON -> 0
⭐ The upstream protocol XML is BYTE-IDENTICAL between the distro's commit (5cdb5b0) and
our pin — same interface version, same `uuuuuus` signature — so this is the distro patching
gamescope, not a version bump. Hence the gate is "do the upstream triples differ", not a
floor, and an unreadable version on either side leaves the layer alone rather than degrading
a box that works (Bazzite/SteamOS, where it has always been fine).
⚠⚠ The old pin fails identically, so REVERTING the pin bump fixes nothing here — this is
pre-existing, not a regression from 5fb8dce4.
Verified against the UNPATCHED distro script, reproducing exactly what this code emits:
the session's own log reports `punktfunk-gamescope version 3.16.25-17-ga87390d+pfhdr4`,
with 0 swapchain_feedback errors, 0 client-communication errors and 0 aborts.
Gate: `scripts/xcheck.sh linux clippy` clean (0 warning/error lines), `cargo fmt` clean.
Non-vacuity re-verified per the xcheck note — a planted type error in the new function
produced 3 errors, and removing it went back to Finished.
Still open, deliberately NOT addressed here: a 10-bit HDR stream aborts gamescope in
`destroy_buffer` (upstream `pipewire.cpp:88`), which is a separate defect.
|
||
|
|
bc9201d136 |
fix(packaging/gamescope): bump the pin past upstream's capture-format probe, and sign the RPM
Three things, one delivery path — a Fedora/Nobara box getting the patched gamescope. **The pin moves 8c676c39 -> 5fb8dce4** (3.16.25-1 -> 3.16.25-11). The commit that matters is ff6b924, `rendervulkan: fall back to XBGR2101010 when XRGB2101010 is unsupported`: it probes `linearTilingFeatures` for STORAGE+SAMPLED and captures as XBGR2101010 where A2R10G10B10 linear storage is unavailable — which is every NVIDIA. That covers the paths that are upstream's rather than ours: the RGB intermediate `paint_pipewire()` acquires when the stream is YCbCr, and AVIF screenshots. #143 fixed our own node host-side; this is the other half, and its commit message asked for exactly this bump. All six patches rebased. Only 0006 conflicted: upstream's f8be7ee added `vulkan_has_drm_modifiers_for_features()` immediately above the `g_device` declaration our patch turns into a reference — both kept. 0003 and 0005 come out byte-identical; 0006 also picks up the `--zero-commit --no-signature` form 0001-0005 already used. **Patch 0001 now offers `xBGR_210LE` BEFORE `xRGB_210LE`**, mirroring the host-side `HDR_FORMAT_ORDER` rationale on the producer end. A consumer takes the first pod it can use, and we were handing third-party consumers (OBS and friends) the one format NVIDIA fills byte-reversed under a correct-looking label. Deliberately NOT done by calling upstream's `vulkan_get_rgb10_capture_format()`, which is what pw_pods.rs proposes: that symbol landed after 3.16.25, so it would break `packaging/nix/gamescope.nix` — which applies these patches to whatever gamescope nixpkgs pins — with an opaque C++ error instead of a patch conflict. The reorder gets the same outcome on any base. Note added there so the next reader does not "fix" it. **And the RPM was never signed.** `Sign RPMs` runs right after `Build RPM`; the gamescope RPM is built ~90 steps later, behind its own ~10-minute cache, so it missed the signing pass entirely — every punktfunk-gamescope RPM ever published went out unsigned. The repo file we tell users to install carries `gpgcheck=1`, so `dnf install punktfunk-gamescope` failed with "The package is not signed" on every Fedora and Nobara box. The package was in the channel the whole time and could not be installed from it, which is worse than absent: the notes and the docs-site both say it is there. `sign-rpms.sh` now takes explicit paths (defaulting to `dist/*.rpm` as before) and a second pass signs this one before publish, fail-closed on a tag like the first. Verified on Nobara 44 (VM 123, RTX 5070 Ti passthrough), canary 0.27.0-0.ci12611.g516a2954: * Builds clean in the fc44 CI image; banner `3.16.25-17-ga87390d+pfhdr4` (11 upstream + our 6), so the marker the host probes still reads 4 — no capability moved, hence pkgrel 3 and `.pfhdrN` staying put. * `pw-cli enum-params` on the live node: BGRx, NV12, **xBGR_210LE (81), xRGB_210LE (80)** — 8-bit consumers still negotiate bit-for-bit, 10-bit now leads with the safe one. * All four patched flags present, `--pipewire-composite-external-overlay` included. * Patch 0006 confirmed working by comparison, which is the only way to see it: the new build exits 0 where both the pre-0006 `+pfhdr2` build and the stock 3.16.23.2 abort with 134. * Signing fix proven with a throwaway key: `Signature: (none)` -> `digests signatures OK`. * Host health on the canary: synthetic spike 300/300 encoded, loopback 300 recovered, 0 mismatches. One unexplained one-off: the very first headless run after install segfaulted at exit (SIGSEGV, after "Primary child shut down!"). Not reproduced in 11 subsequent runs across every flag combination, so it is recorded rather than diagnosed — the binary is stripped and there is no symbolised core. |
||
|
|
003ce8bea7 |
Merge pull request 'Every NVIDIA gamescope HDR stream had red and blue swapped — and a sysext step added in a release was unreachable forever' (#143) from worktree-hdr-rb-swap-nvidia into main
arch / build-publish (push) Failing after 3s
ci / rust (push) Failing after 2s
ci / rust-arm64 (push) Failing after 2s
deb / build-publish (push) Failing after 3s
deb / build-publish-host (push) Failing after 0s
deb / build-publish-client-arm64 (push) Failing after 1s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
ci / bun-nix (push) Successful in 31s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 22s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 23s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
apple / swift (push) Successful in 1m40s
ci / web (push) Successful in 1m8s
ci / docs-site (push) Successful in 1m16s
docker / builders-arm64cross (push) Successful in 16s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m10s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m28s
docker / deploy-docs (push) Failing after 1m41s
android / android (push) Successful in 5m38s
apple / screenshots (push) Successful in 5m53s
windows-host / package (push) Successful in 16m59s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 13s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m1s
Reviewed-on: #143 |
||
|
|
235b8e55d4 |
Merge pull request 'chore(web): console onto @unom/ui 0.9.2' (#142) from worktree-console-unom-092 into main
audit / license-gate (push) Failing after 2s
audit / cargo-audit (push) Failing after 2s
audit / docs-site-audit (push) Successful in 22s
audit / bun-audit (web) (push) Failing after 22s
audit / bun-audit (sdk) (push) Successful in 27s
audit / bun-audit (plugin-kit) (push) Successful in 29s
audit / pnpm-audit (push) Successful in 20s
ci / rust-arm64 (push) Failing after 2s
deb / build-publish-client-arm64 (push) Failing after 2s
ci / rust (push) Failing after 2s
ci / bun-nix (push) Successful in 31s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 19s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 13s
arch / build-publish (push) Canceled after 1m29s
ci / web (push) Canceled after 1m12s
ci / docs-site (push) Canceled after 1m9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13s
deb / build-publish (push) Canceled after 1m14s
deb / build-publish-host (push) Canceled after 1m13s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 1s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 1m3s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 28s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 25s
docker / deploy-docs (push) Canceled after 0s
windows-host / package (push) Canceled after 1m1s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
nix / flake (push) Failing after 4m16s
Reviewed-on: #142 |
||
|
|
0b252403cd |
fix(web): fix the card inset at the root, not at the call sites
ci / bun-nix (pull_request) Successful in 51s
ci / docs-site (pull_request) Successful in 1m35s
ci / web (pull_request) Successful in 2m30s
ci / rust-arm64 (pull_request) Successful in 3m16s
ci / rust (pull_request) Failing after 9m12s
nix / flake (pull_request) Failing after 19m50s
The broken inset on the Displays configuration card was the symptom. The cause is structural, and it had already been diagnosed at least twice in-tree without being fixed. Two faults, both in components/ui/card.tsx: 1. The padding was a RESPONSIVE COMPOUND: `p-4 pt-0 sm:p-6 sm:pt-0`. tailwind-merge resolves conflicts only within a variant, so any call-site override won at the base and lost at `sm:` — correct on a phone, wrong on every desktop. Measured on the Displays card before this change: padding-top 24px at 500px, 0px at 1440px. 2. `pt-0` encoded an assumption about a SIBLING that nothing enforced — "a CardHeader is above me and supplies the top inset". Delete the header, which is exactly what tabbing a page does since the tab label replaces the card title, and the top inset silently vanishes at ≥640px. Fix: - One single-variant utility, `p-padding-card` — the same `--spacing-padding-card` token @unom/ui's own Card uses, so nested cards finally agree on their inset. A single variant cannot half-lose an override. - Top inset is now self-correcting: `[&:not(:first-child)]:pt-0`. Ask the DOM instead of the author. A headerless CardContent keeps its inset with nothing to remember. Seven call sites had grown their own compensation in five dialects — `p-6`, `p-card pt-card sm:pt-card` (×3), `p-4 sm:pt-6` (×3), `pt-4 sm:pt-6`, and my own `pt-6` from the tabs commit. All removed; they are the symptom-fixes this replaces. LogsCard even carried a six-line comment correctly describing the trap and working around it locally — that comment is now three lines saying it no longer needs saying. `flush` stays: full-bleed content is a real intent, expressed as a prop the component honours rather than a utility that has to out-argue the one already there. Guarded by UI/Card → "Inset with and without header", a headered/headerless pair that has to look identical on every side. It must be checked at BOTH widths — a single width cannot show this class of bug, which is why it kept surviving. Verified by measuring computed padding at 500px and 1440px: first child 20px on all four sides, after-a-header 0px top and 20px elsewhere, identical at both widths. tsc clean, biome clean on every touched file, 9/9 server tests, build + i18n clean, 32/32 screenshots. |
||
|
|
0ab17ee81d |
fix(packaging): a post_merge step added in a release was unreachable forever
ci / bun-nix (pull_request) Successful in 48s
ci / docs-site (pull_request) Successful in 1m20s
ci / web (pull_request) Successful in 1m16s
apple / swift (pull_request) Successful in 1m45s
ci / rust-arm64 (pull_request) Successful in 1m43s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 3m52s
ci / rust (pull_request) Failing after 8m7s
A sysext upgrade is driven by the script from the OLD image -- /usr/bin/punktfunk-sysext
is replaced by the very `systemd-sysext refresh` that runs mid-upgrade -- so a
post_merge step ADDED in the new release is executed by nobody. The old script
does not have it, and the new script never gets a turn: from then on `update`
matches the "already on $cur" branch and returns before post_merge. The step is
permanently unreachable on exactly the installs that need it, and nothing says so.
Field-proven on the Bazzite host that took 0.25.0 -> 0.26.0 (2026-08-09). The
casualty was the `punktfunk` group, which post_merge learned to create in 0.26.0
(
|
||
|
|
31aef4b09f |
feat(web): tab the Virtual displays page
ci / rust-arm64 (pull_request) Successful in 2m6s
ci / docs-site (pull_request) Successful in 3m59s
ci / bun-nix (pull_request) Successful in 4m39s
ci / web (pull_request) Successful in 5m1s
ci / rust (pull_request) Failing after 13m18s
nix / flake (pull_request) Failing after 23m11s
Same pill strip the plugin UIs use, via @unom/ui's Tabs: Configuration | Live displays. The page was two stacked cards, and the configuration card ALONE is taller than the viewport — the existing comment on the unsaved badge says as much, because that height is how pending edits went unnoticed. The live-display list sat below all of it, so in practice it was off screen. Two details that are not cosmetic: - The dirty marker moved from the card header onto the Configuration TRIGGER. Behind a tab the old badge would vanish entirely while Live was open — a strictly worse version of the problem it was added to solve. On the trigger it survives both tabs, and the Custom block keeps its own inline badge for when the tab IS open. - The strip is extracted as a presentational `DisplayTabs` rather than inlined in `DisplaySection`. The container calls `useBlocker`, which needs a router, so it cannot render in Storybook — and this page's story exists specifically to pin the MOTION NESTING of the preset grid (a card sets no delayChildren, so tiles nested one level deeper stop staggering). Inserting tabs changes that ancestor chain, so the story has to render the real one or it passes for the wrong reason. Adds Pages/Displays → "Unsaved on other tab", which switches to Live with a dirty draft: if the marker ever goes silent there, the warning is gone exactly when it matters. Verified: tsc clean, biome clean, `bun test server/` 9/9, vite build + i18n check clean, Storybook builds, 32/32 screenshots. |
||
|
|
97928516a0 |
fix(pf-capture): every NVIDIA HDR stream had red and blue swapped
gamescope's capture textures are mappable, hence linear-tiled, and NVIDIA does not implement linear-tiled STORAGE for A2R10G10B10_UNORM_PACK32. Upstream says it plainly in rendervulkan.cpp: "imageStore lands in XBGR order there, swapping R/B". So the composite writes XBGR bytes into a buffer still LABELLED XRGB2101010, and our patch's spa_format_to_drm() derives that label from the negotiated SPA format alone, never asking the hardware what it can actually write. The host then believed the label, correctly at every step: xRGB_210LE -> PixelFormat::X2Rgb10 -> NV_ENC_BUFFER_FORMAT_ARGB10. DRM XRGB2101010 really is "B in the low 10 bits" and NVENC ARGB10 really is "B in the lowest 10 bits"; the Windows twin (R10G10B10A2 -> ABGR10) is correct by the same rule. Every mapping audits clean because the label was right and only the CONTENT was wrong -- which is why this survived a full trace of both ends. Fix the preference host-side: offer xBGR_210LE FIRST. The first compatible consumer pod wins, so that is what a gamescope session lands on, and an XBGR2101010 texture is one NVIDIA writes in its own order -- label and content agree. It costs nothing elsewhere: A2B10G10R10_UNORM_PACK32 is the universally supported packed-10 format, it is what upstream's own fallback picks, and X2Bgr10 has a first-class encoder path (NVENC ABGR10, VAAPI X2BGR10LE). xRGB_210LE stays as the second pod so a producer offering only it can still negotiate HDR instead of dropping to the SDR downgrade. Doing it here rather than in the patch set is deliberate: the real fix is for spa_format_to_drm() to offer only what vulkan_get_rgb10_capture_format() reports, but that function landed after 3.16.25 and the pin is 3.16.25-7-g60561e2+pfhdr4 (0 "2101010" strings in the shipped binary), so the deployed gamescope cannot self-correct. This ships in the host binary with no gamescope rebuild. Field-confirmed on the RTX 5070 Ti Bazzite host with 0.26.0, and confirmed host-side rather than client-side by reproducing the identical swap from two unrelated clients (16" MacBook Pro and Mac Studio). SDR was never affected -- it takes no packed-10 path. Gate (pf-lxcheck2, linux/amd64): fmt clean, clippy --all-targets -D warnings clean, cargo test -p pf-capture 60 passed / 0 failed incl. the new hdr_offers_xbgr_before_xrgb order pin. |
||
|
|
d13d253c2f |
chore(web): @unom/ui 0.8.16 → 0.9.2
ci / rust-arm64 (pull_request) Failing after 31s
ci / docs-site (pull_request) Successful in 3m1s
ci / bun-nix (pull_request) Successful in 3m27s
ci / web (pull_request) Successful in 4m29s
ci / rust (pull_request) Failing after 13m16s
nix / flake (pull_request) Failing after 19m47s
Brings the console onto the current design system. 0.9.x adds the Badge, Spinner, Skeleton, Switch, Table, EmptyState and CodeBlock primitives, and 0.9.2 carries the form fixes found while overhauling the rom-manager plugin UI: - Select's border and focus ring resolved to `--main`, which is the FOREGROUND here (`--main: var(--foreground)` in web/src/styles.css), so the trigger wore a near-white border and a 3px near-white focus ring. Its chevron and placeholder were painted `--secondary`, a SURFACE colour, and all but vanished. Now on `--input`/`--ring`, the same tokens InputText already used. - InputNumber declares a color-scheme, so the browser-drawn spinner arrows stop being near-black on a near-black field. Both defects were live in this console too — the console palette is what exposes them. Verified: codegen + vite build clean, `tsc --noEmit` clean, `bun test server/` 9/9, Storybook builds, 31/31 screenshots. A probe over all 61 stories reports ZERO page errors, and the two stories containing a Select now render it at h-input-height with `border: rgb(42, 33, 72)` (the input token) and a muted-foreground chevron. Note: the console's components/ui/ wrapper layer is unchanged and still required — @unom/ui's DialogContent remains a surface with no Portal or placement, which is exactly what web/src/components/ui/dialog.tsx supplies. |
||
|
|
516a295432 |
Merge pull request 'My gamescope gate withheld the host .deb it was meant to protect — the release still ships the KDE-breaking one' (#140) from worktree-gamescope-gate-placement into main
ci / web (push) Successful in 1m5s
ci / bun-nix (push) Successful in 17s
ci / rust-arm64 (push) Successful in 1m38s
ci / docs-site (push) Successful in 2m36s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 8s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 7s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 4s
deb / build-publish-client-arm64 (push) Successful in 1m38s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 14s
deb / build-publish (push) Successful in 3m45s
docker / builders-arm64cross (push) Successful in 8s
docker / deploy-docs (push) Successful in 31s
deb / build-publish-host (push) Successful in 6m39s
ci / rust (push) Successful in 10m37s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m48s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m37s
Reviewed-on: #140 |
||
|
|
2c190b27b4 |
Merge pull request 'Switching audio device mid-stream killed the sound for the rest of the session — AVAudioEngine stops itself, and nothing ever restarted it' (#141) from worktree-audio-device-switch-silence into main
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
apple / swift (push) Successful in 1m41s
release / apple (push) Successful in 10m9s
apple / screenshots (push) Successful in 6m10s
Reviewed-on: #141 |
||
|
|
3cfa5ca194 |
Merge pull request 'The capability-hint test asserted the environment, not the code — main is red on a machine where nothing is wrong' (#139) from worktree-kwin-capability-test-env into main
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
deb / build-publish (push) Canceled after 1m55s
deb / build-publish-host (push) Canceled after 1m10s
deb / build-publish-client-arm64 (push) Canceled after 50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
android / android (push) Successful in 6m20s
windows-host / package (push) Successful in 11m23s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 18s
arch / build-publish (push) Successful in 11m50s
Reviewed-on: #139 |
||
|
|
bf913c5706 |
fix(apple): switching audio device mid-stream killed the sound for the rest of the session
ci / bun-nix (pull_request) Successful in 36s
ci / web (pull_request) Successful in 1m22s
apple / swift (pull_request) Successful in 1m39s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m40s
ci / rust-arm64 (pull_request) Successful in 2m53s
ci / rust (pull_request) Failing after 9m43s
Field report, macOS client, host-independent: start a stream with AirPods in, take them
out — nothing on the speakers; put them back in — nothing in the AirPods either. Only
restarting the whole stream brought audio back.
An AVAudioEngine does not follow the audio hardware. When the output device changes under
a running engine, its IO unit sees the new hardware, THE ENGINE STOPS ITSELF, and it posts
AVAudioEngineConfigurationChange. It stays stopped until somebody starts it again, and
nothing here ever did — no error, no log line, just a session rendering silence from that
moment on. Putting the AirPods back in is a second stop, not a recovery, which is exactly
why that half of the report looked so strange.
Measured on the client's own playback topology (source node -> main mixer, 48 kHz stereo)
by moving the default output device programmatically: render callbacks go from ~94/s to
zero the instant the device changes, and both restarting the same engine and building a
fresh one resume them.
The fix watches the hardware and rebuilds the topology the session was started with, on
whatever device is there now. Three triggers, because no single one covers the ground:
- the engine's own configuration-change notification, every platform — the direct
signal, but it can only be posted BY an engine, so it cannot report a rebuild that
failed to start;
- a CoreAudio HAL default-output-device listener on macOS — independent of any engine
and of the engine's topology. This is what makes the recovery work for the
voice-processing engine, which is the DEFAULT macOS configuration (mic and echo
cancellation both default on) and whose notification behaviour could not be verified:
no Mac in the fleet can initialize VPIO at all;
- route-change and media-services-reset on iOS/tvOS, where the session rather than the
device is what moves. The route observer is now installed for mic-off (.playback)
sessions and on tvOS too — it used to be iOS-and-mic-only, for the earpiece steer,
but every platform has engines a route change can stop.
They collapse into one debounced rebuild (one switch produces a burst), with a floor
between rebuilds so a device that renegotiates in a loop cannot spin the session, and a
short retry ladder for a device caught mid-transition — a rebuild that fails leaves no
engine to post the next notification, so that path must not simply give up. The ring is
deliberately carried across: the drain thread keeps decoding through the switch, and the
ring's overflow policy has already dropped whatever went stale while the engine was down.
A rebuild is only ever done when it concerns us. A healthy engine that followed the change
on its own is left alone, and somebody changing the system default while this session is
pinned to a named speaker is none of our business — rebuilding for that would cost an
audible gap for nothing.
The trigger wiring is split into AudioDeviceWatcher for one reason: an end-to-end test of
the recovery needs a live session, which needs a host, and punktfunk-host does not build
on macOS — so the part where a silent failure costs the session ALL of its audio would
otherwise ship unverified. On its own the watcher is pointed at the real hardware from a
unit test: a real default-output-device move must reach the owner, our engine's
notification must get through, a foreign engine's must not. Neutralizing the wiring fails
both positive tests and neither negative one.
AudioDeviceSwitchTests drives the real SessionAudio through the out-and-back switch
against the loopback host; it skips wherever that fixture cannot run (which is every Mac,
today) and the open host's frame budget is raised so it outlives the switch.
|
||
|
|
5bd92dac5d |
fix(ci): my gamescope gate withheld the host .deb it was supposed to protect
ci / bun-nix (pull_request) Successful in 25s
apple / swift (pull_request) Successful in 1m33s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m40s
ci / rust-arm64 (pull_request) Successful in 1m40s
ci / docs-site (pull_request) Successful in 2m3s
android / android (pull_request) Successful in 4m10s
ci / rust (pull_request) Successful in 8m9s
The gate #135 added fails the job at the gamescope BUILD step. In deb.yml that step runs before "Publish to the Gitea apt registry" and "Attach the host .deb to the Gitea release", so failing it skipped both. Consequence on the v0.26.0 tag, and it is the worst thing in this release so far: the host .deb on the release is from 00:17 — re-point #1, BEFORE #136 revoked CAP_SYS_NICE. Every other .deb is from 08:29-08:31. So the published Debian host still runs `setcap cap_sys_nice=ep` in its postinst, which is exactly what makes the host unidentifiable to KWin and kills every KDE desktop session. A gate meant to protect the release withheld the fix for it and left the broken artifact in place. rpm.yml has the identical latent bug and only escaped it because Fedora went green: a gamescope failure there would skip the sysext image, the feed publish and the release attach, withholding the punktfunk RPMs and .raw images too. Both now warn at the build/package steps and gate as the LAST step of the job, after everything has published. A missing EXTRA must never stop a good artifact shipping — go red afterwards instead. Also: name noble's dependencies outright. `apt-get build-dep gamescope` gives it almost nothing (the distro has no comparable package), which is why this peeled one dep per CI cycle — wayland-protocols, then xdamage. The full set is derived from the Arch package's depends+makedepends, which is the build that demonstrably works, plus wlroots' own (it is a forced fallback subproject). One `apt-get` per name on purpose: a single transaction aborts wholesale on one unknown package, installing NOTHING and hiding the real gap behind a name typo. Per-package, best-effort, with the missing name echoed; the end-of-job gate is what actually decides. ⚠ Verification: both YAML files parse; every gamescope-touching `run:` block is `bash -n` clean with matrix placeholders substituted (9 blocks); the .deb glob matches build-gamescope-deb.sh's documented output (`dist/punktfunk-gamescope_<version>_<arch>.deb`) and the RPM glob excludes debuginfo/debugsource exactly as the attach loop above it does. The noble dep NAMES cannot be proven from macOS — that is what the next tag run decides, and it now decides it without holding the host .deb hostage. |
||
|
|
e8a4f54c07 |
fix(pf-vdisplay): the capability-hint test asserted the environment, not the code
apple / swift (pull_request) Successful in 1m33s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 5m0s
ci / bun-nix (pull_request) Successful in 42s
ci / docs-site (pull_request) Successful in 1m21s
ci / web (pull_request) Successful in 1m48s
ci / rust-arm64 (pull_request) Successful in 2m47s
ci / rust (pull_request) Successful in 6m57s
`silent_without_capabilities` called the real `capability_denial_hint()` and
asserted it returns "", on the strength of a doc comment that read "The test
process has no capabilities."
That is true on a dev box and false in CI, where the runner container is root
with a full permitted set. main went red on
|
||
|
|
f80636f901 |
Merge pull request 'The release notes advertise a privilege 0.26.0 deliberately does not grant' (#138) from worktree-notes-capsysnice-correction into main
android-screenshots / screenshots (push) Successful in 1m29s
release / apple (push) Successful in 12m13s
decky / build-publish (push) Successful in 37s
windows-host / package (push) Successful in 11m48s
windows-host / canary-manifest (push) Skipped
deb / build-publish-client-arm64 (push) Successful in 1m27s
deb / build-publish (push) Successful in 4m18s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m41s
linux-client-screenshots / screenshots (push) Successful in 2m54s
sbom / sbom (push) Successful in 20s
deb / build-publish-host (push) Failing after 5m18s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m54s
windows-host / winget-source (push) Successful in 21s
docker / builders-arm64cross (push) Successful in 9s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 6s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 18s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 21s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 13s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 18s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
docker / deploy-docs (push) Successful in 28s
android / android (push) Successful in 10m26s
arch / build-publish (push) Successful in 11m24s
web-screenshots / screenshots (push) Successful in 5m5s
flatpak / build-publish (push) Successful in 16m37s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m18s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m6s
ci / rust-arm64 (push) Successful in 1m40s
ci / web (push) Successful in 2m5s
ci / docs-site (push) Successful in 1m12s
ci / bun-nix (push) Successful in 38s
ci / rust (push) Canceled after 1m31s
|
||
|
|
0f79587dd6 |
docs(release): the notes claimed a privilege 0.26.0 deliberately does not grant
The user-facing v0.26.0 notes said, of the PyroWave GPU-priority lever:
"it is now, and the package grants the host the permission that switch needs"
That was true of 0.26.0-1 and is now the opposite of true. Granting CAP_SYS_NICE
made the host unidentifiable to KWin and killed desktop streaming on every KDE
box across all five Linux channels, so 0.26.0-2 revokes it everywhere and must
keep doing so. The lever is wired natively on Linux for the first time — that
part stands — but it is dormant on an ordinary install, and the notes have to
say so rather than advertise a speed-up nobody gets.
CHANGELOG.md was already corrected in #136 (the 0.26.0-2 note under PW1 and the
qualifier on the owed A/B). This is the user-facing half, which #136 did not
touch:
* the PyroWave bullet now leads with what DID land (two encoder handles, the
capture buffer headroom) and describes the priority switch as present but
dormant, with the reason.
* a new Fixed entry for the KDE breakage itself. Worth telling users even
though the release was never announced: 0.26.0-1 packages did reach the
registries, and anyone who pulled one has a desktop session that fails with
a missing-screencast error surviving a clean reinstall. It also explains the
dormancy the bullet above now refers to.
Deliberately NOT written as a "Before you update" action: upgrading strips the
capability by itself on every channel, so there is nothing for a reader to do.
Commit count 47 -> 52.
Voice check clean (0 internal-vocabulary hits above "## For developers"); notes
67 lines.
|
||
|
|
651a7a82a1 |
Merge pull request '0.26.0-1 gave the host CAP_SYS_NICE, which made it invisible to KWin — every KDE desktop session died, on five packaging channels' (#136) from worktree-kwin-capability-identification into main
ci / web (push) Successful in 1m4s
apple / swift (push) Successful in 1m37s
ci / rust-arm64 (push) Failing after 2m20s
ci / rust (push) Failing after 2m21s
ci / docs-site (push) Successful in 1m18s
ci / bun-nix (push) Successful in 26s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 37s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 30s
deb / build-publish-client-arm64 (push) Successful in 1m56s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
android / android (push) Successful in 6m21s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m21s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 11s
apple / screenshots (push) Successful in 5m58s
deb / build-publish (push) Successful in 5m32s
deb / build-publish-host (push) Successful in 6m11s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
arch / build-publish (push) Successful in 11m40s
windows-host / package (push) Successful in 12m30s
windows-host / winget-source (push) Skipped
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m58s
nix / flake (push) Successful in 18m34s
windows-host / canary-manifest (push) Successful in 14s
Reviewed-on: #136 |
||
|
|
4d383811c0 |
fix(packaging): the same CAP_SYS_NICE broke KDE on FIVE channels, not one — Bazzite included
ci / bun-nix (pull_request) Successful in 17s
ci / web (pull_request) Successful in 1m7s
apple / swift (pull_request) Successful in 1m38s
ci / rust-arm64 (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m46s
android / android (pull_request) Successful in 5m31s
ci / rust (pull_request) Failing after 9m2s
nix / flake (pull_request) Successful in 12m24s
The Arch fix in the previous commit was incomplete. 0.26.0-1 granted the host CAP_SYS_NICE through
every Linux channel we ship, and each one breaks KWin identification the same way:
* packaging/rpm/punktfunk.spec .......... %caps(cap_sys_nice=ep) in %files <- Fedora AND Bazzite
via rpm-ostree layering
* packaging/bazzite/build-sysext.sh ..... setcap on the staging tree, recorded by mksquashfs
* packaging/debian/build-deb.sh ......... setcap in the postinst
* packaging/nix/nixos-module.nix ........ security.wrappers with capabilities = "cap_sys_nice=ep"
* scripts/steamdeck/install.sh .......... setcap on $BIN, six lines after writing the .desktop
whose Exec= it thereby voids
Bazzite was NOT a separate fault, as first reported here — it is this one. Verified by mounting the
published punktfunk-0.26.0-1-x86-64.raw: `getcap usr/bin/punktfunk-host` reports cap_sys_nice=ep,
stored as security.capability in the squashfs. The claim in packaging/arch/build-sysext.sh that
"file capabilities don't survive this squashfs path" is false and is corrected here; mksquashfs
records them, which is exactly why the image shipped one.
NixOS deserves its own note: a security.wrappers entry does not dodge the problem. The wrapper
raises the capability into its AMBIENT set before exec'ing the store binary, precisely so it
survives — which lands CAP_SYS_NICE in the exec'd process's permitted set and fails the readlink
identically to a file capability. ExecStart now points at the store path directly, which is also the
path packages.nix substitutes into the .desktop's Exec=, so the two finally agree.
Measured blast radius of holding a capability, same-uid reader, CachyOS kernel 7.1.6:
/proc/PID/exe ....... EPERM <- KWin's identification. Desktop sessions die.
/proc/PID/root/* .... EPERM <- xdg-desktop-portal reads .flatpak-info here to resolve an
app id; the wlroots and Hyprland backends go through it
/proc/PID/environ ... EPERM
/proc/PID/cgroup .... OK
/proc/PID/status .... OK
/proc/PID/cmdline ... OK
Compositor backends, by exposure: KWin is broken outright (proven, field-confirmed). gamescope has
no identity gate and was never affected, which matches the field — only Desktop mode was reported.
Mutter drives Mutter's own D-Bus API, not the portal, and looks unaffected. wlroots and Hyprland go
through the ScreenCast portal, whose app-id resolution reads a path the capability blocks — a real
exposure, not something I reproduced end to end.
The sysext build now HARD-FAILS if a capability is staged, rather than trusting that the RPM payload
never carries one: a merged sysext's /usr is read-only squashfs, so a bad image cannot be repaired
on the box, and the spec was one %caps() away from baking one in again.
Docs corrected, because they advertised the capability as a feature:
* docs-site running-as-a-service "GPU scheduling priority" — rewritten: the host carries no
capability, why it must not, and how to clear a 0.26.0-1 install (Bazzite needs a new image)
* docs-site configuration.md — the PYROWAVE_QUEUE_PRIORITY row no longer claims the packages grant it
* packaging/bazzite/README.md — §6.5 still described the kde-desktop-setup.sh behaviour from
before it stopped writing KWIN_WAYLAND_NO_PERMISSION_CHECKS and started REMOVING it; plus a
note that 0.26.0-1 Desktop mode cannot be repaired in place
* packaging/arch/README.md — the false "capabilities don't survive the sysext" line
* CHANGELOG v0.26.0 PW1 — annotated with the 0.26.0-2 correction rather than rewritten, and the
owed PyroWave-under-load A/B now says it needs a gamescope-only box
Verified: bash -n on all five changed shell files; nix-instantiate --parse on nixos-module.nix and
packages.nix; the published 0.26.0-1 sysext mounted and its capability read; getcap on an uncapped
file exits 0 with empty output, so the new build assertion cannot false-positive.
|
||
|
|
42ee6c5628 |
fix(packaging): the host's CAP_SYS_NICE made it invisible to KWin, killing every KDE session
0.26.0-1 setcap'd `cap_sys_nice=ep` on /usr/bin/punktfunk-host so the encoder could open an
elevated global-priority Vulkan queue. On every KDE box that ended desktop streaming outright:
KWin virtual output failed: KWin does not expose zkde_screencast_unstable_v1 to this client
reported from CachyOS on NVIDIA and on AMD, surviving a clean reinstall of host and client, and
worked around only by KWIN_WAYLAND_NO_PERMISSION_CHECKS=1.
The two cannot coexist. KWin hands out its restricted protocols — zkde_screencast_unstable_v1,
which mints our virtual output, and org_kde_kwin_fake_input, which injects input — only to a client
it can IDENTIFY, by resolving that client's /proc/<pid>/exe and matching it against an installed
.desktop's Exec=. The kernel refuses that readlink to any reader whose effective set is not a
superset of the target's PERMITTED set (cap_ptrace_access_check), and KWin holds no capabilities.
So the instant the binary carries one, KWin's executablePath() is empty, nothing matches, and the
global is never advertised — presenting exactly as a missing or mis-installed .desktop file.
Measured on CachyOS (kernel 7.1.6), same-uid reader, cap_sys_nice=ep on the target:
no capability .............................. readlink /proc/<pid>/exe OK
capability ................................. EPERM
capability + prctl(PR_SET_DUMPABLE, 1) ..... EPERM <- dumpable is NOT the gate
capability dropped + PR_SET_DUMPABLE(1) .... OK <- only an uncapped process works
The third row also rules out the reflex fix of moving the grant to systemd AmbientCapabilities=,
which lands CAP_SYS_NICE in the very same permitted set. Nothing short of not holding the
capability restores identification, so the host does not get one.
The cost is pacing only. pf-zerocopy's device create already walks REALTIME -> HIGH -> default when
a priority class is refused, and pf-frame's thread nice is a documented best-effort no-op without
the capability — so this is 0.25.0's behaviour exactly, which is the behaviour that worked.
* packaging/arch/punktfunk-host.install: grant -> revoke. post_upgrade strips the capability from
boxes that already ran 0.26.0-1's scriptlet. A pacman upgrade writes a new inode and file
capabilities do not survive that, so this is belt-and-braces for reinstall/downgrade paths.
* pf-vdisplay kwin.rs: all three "KWin does not expose zkde_screencast" errors now read
/proc/self/status and, if this process holds ANY capability, name it with its CapPrm mask and
the `setcap -r` that repairs it. The failure stays impossible to diagnose from the Wayland side
otherwise, and it is not unique to our own packaging — a hand-rolled setcap does it too.
Verified on 192.168.1.21 (CachyOS): the capability/dumpable matrix above; cargo check and
cargo clippy --all-targets -- -D warnings clean for pf-vdisplay; both new unit tests pass; and the
hint itself exercised end-to-end, silent uncapped and firing with CapPrm=0x0000000000800000 under
cap_sys_nice=ep. The shipped punktfunk-host-0.26.0-1-x86_64.pkg.tar.zst was unpacked to confirm its
.INSTALL carries the setcap on both post_install and post_upgrade.
Ships as 0.26.0-2 — packaging plus one crate, no version bump.
|
||
|
|
08eaf337e8 |
Merge pull request 'v0.26.0 promised a Fedora and an apt gamescope that were never built' (#135) from worktree-gamescope-rpm-deb-builddeps into main
ci / web (push) Successful in 1m6s
ci / rust-arm64 (push) Successful in 1m35s
ci / bun-nix (push) Successful in 27s
ci / docs-site (push) Successful in 1m12s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 16s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
deb / build-publish-client-arm64 (push) Successful in 1m30s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 19s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 16s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m12s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m14s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 9s
deb / build-publish-host (push) Successful in 5m53s
deb / build-publish (push) Successful in 6m9s
docker / builders-arm64cross (push) Successful in 7s
ci / rust (push) Successful in 11m22s
docker / deploy-docs (push) Failing after 6m12s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m32s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m20s
|
||
|
|
39869031be |
fix(ci): the gamescope RPM and .deb never built, and a warning let the tag ship anyway
v0.26.0's notes and docs-site say the patched gamescope is now installable on
Fedora and on Debian/Ubuntu. Neither package exists on the release. Both builds
failed inside best-effort steps that emit `::warning::` and return 0, so every
job stayed green and the only evidence was a warning nobody reads. Arch built
fine, which is why it is the sole gamescope package attached.
Two distinct missing build deps, same root cause: `dnf builddep gamescope` /
`apt-get build-dep gamescope` resolve the DISTRO'S OLDER PACKAGED gamescope,
which does not need what the pinned master tree needs.
Fedora (f43 AND f44)
/usr/sbin/ld: cannot find -lstdc++
have you installed the static version of the stdc++ library ?
ERROR: Compiler sccache c++ cannot compile programs.
build-punktfunk-gamescope.sh appends `-static-libstdc++ -static-libgcc` to
LDFLAGS deliberately, so the binary still starts on SteamOS's older libstdc++.
Without libstdc++-static that trips meson's very FIRST sanity check, so
nothing builds at all.
Debian/Ubuntu noble
protocol/meson.build:7:17: ERROR: Neither a subproject directory nor a
wayland-protocols.wrap file was found.
The tree carries no wrap fallback for wayland-protocols.
Both proven deps are installed WITHOUT `|| true` so a rename is loud. The
remaining Arch makedepends the older packaged gamescope may not pull (glm,
cmake, libXcursor, wayland-protocols-devel on Fedora) stay best-effort, since
meson finds fallbacks and a name that moves between releases should not fail
the job.
And the part that actually matters: on `refs/tags/v*` a missing gamescope is
now an ERROR, not a warning. A release must not be able to make a claim its own
CI silently dropped. Gated in two places per platform — the build step, and the
packaging step that is authoritative and also covers the cache path (the build
step is skipped entirely on a cache hit, so a stale cache would otherwise reach
packaging and skip in silence). Canary keeps the old best-effort behaviour.
Deliberately NOT gated: the sysext leg. The notes make no claim about gamescope
inside the sysext, and with the build fixed gs-cache is populated so it gets the
binary anyway — gating it would add release-blocking risk with no matching
promise.
⚠ Verification is CI itself: both YAML files parse, and every gamescope-touching
`run:` block is `bash -n` clean with the matrix placeholders substituted. The
dep names cannot be proven from macOS; the rpm and deb legs on the next tag are
the proof, and they are now hard-gated, so a wrong name fails loudly instead of
shipping another empty promise.
|
||
|
|
55f361cb92 |
Merge pull request 'The v0.26.0 tag went red on Windows — a Linux-only reader tripped dead_code' (#134) from worktree-pyrowave-wire-dead-code into main
apple / swift (push) Successful in 1m33s
ci / rust-arm64 (push) Successful in 4m54s
ci / web (push) Successful in 1m47s
release / apple (push) Successful in 10m42s
ci / rust (push) Successful in 8m51s
ci / docs-site (push) Successful in 1m44s
ci / bun-nix (push) Successful in 32s
apple / screenshots (push) Successful in 5m55s
android-screenshots / screenshots (push) Successful in 2m16s
deb / build-publish (push) Successful in 3m56s
decky / build-publish (push) Successful in 23s
deb / build-publish-client-arm64 (push) Successful in 2m39s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m32s
deb / build-publish-host (push) Successful in 6m49s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m0s
linux-client-screenshots / screenshots (push) Successful in 2m55s
android / android (push) Successful in 11m36s
arch / build-publish (push) Successful in 13m24s
flatpak / build-publish (push) Successful in 8m4s
windows-host / winget-source (push) Successful in 35s
windows-host / package (push) Successful in 11m45s
windows-host / canary-manifest (push) Skipped
sbom / sbom (push) Successful in 35s
docker / deploy-docs (push) Failing after 6m10s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 11s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 13s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 1m5s
docker / builders-arm64cross (push) Successful in 16s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 57s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m57s
web-screenshots / screenshots (push) Successful in 4m48s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m13s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m38s
|
||
|
|
2079411f4f |
fix(pf-encode): the Windows host could not compile — a Linux-only reader tripped dead_code
apple / swift (pull_request) Successful in 1m35s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m24s
android / android (pull_request) Successful in 5m55s
ci / rust-arm64 (pull_request) Successful in 4m5s
ci / bun-nix (pull_request) Successful in 33s
ci / docs-site (pull_request) Successful in 1m32s
ci / rust (pull_request) Successful in 21m44s
The v0.26.0 tag went red on windows-host at the clippy step, after a clean
build:
error: function `wire_sequence` is never used
--> crates\pf-encode\src\enc\pyrowave_wire.rs:68:15
= note: `-D dead-code` implied by `-D warnings`
`pyrowave_wire` is cfg'd for linux OR windows and is genuinely shared —
`packet_boundary` and `stamp_color_bits` each have callers on both backends.
`wire_sequence` does not: every call site is in `enc/linux/pyrowave.rs`, which
is `#[cfg(all(target_os = "linux", feature = "pyrowave"))]`. Alternating
encoder handles are a Linux-side concern (PW5); the Windows backend drives
pyrowave's compat device with a single handle and never needs the counter. The
module's own `#[cfg(test)]` block does not reference it either, so on Windows
the item has zero callers in every target and dead_code is correct — it is the
`-D warnings` promotion to a hard error that stops the lib compiling.
Scoped to the one item rather than the file, and expressed as
`cfg_attr(not(target_os = "linux"), ...)` rather than a bare `allow`, so
dead_code stays LIVE on Linux — where the caller lives, and where this function
quietly losing its last caller would be a real finding rather than noise.
⚠ Not reproducible off a Windows box: cross-compiling to
x86_64-pc-windows-msvc from macOS dies in openh264-sys2's build script
(clang++ rejects `-fPIC` for that target) long before the lint stage. The
mechanism is nonetheless exact — one item, one cfg, zero callers behind it —
and the windows-host and windows-msix legs are the proof.
No behaviour change on any platform: this adds a lint attribute and eight
lines of comment.
|
||
|
|
4d1a1348c0 |
Merge pull request 'chore(release): bump workspace version to 0.26.0' (#133) from worktree-release-0260 into main
apple / swift (push) Successful in 1m41s
audit / bun-audit (plugin-kit) (push) Successful in 1m1s
audit / bun-audit (sdk) (push) Successful in 33s
audit / bun-audit (web) (push) Failing after 36s
audit / docs-site-audit (push) Successful in 27s
audit / pnpm-audit (push) Successful in 30s
ci / web (push) Successful in 2m25s
audit / license-gate (push) Successful in 4m21s
ci / bun-nix (push) Successful in 24s
ci / docs-site (push) Successful in 1m26s
apple / screenshots (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 6m20s
android-screenshots / screenshots (push) Canceled after 0s
android / android (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 24s
deb / build-publish-client-arm64 (push) Canceled after 17s
decky / build-publish (push) Canceled after 5s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
linux-client-screenshots / screenshots (push) Canceled after 0s
release / apple (push) Canceled after 1m6s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
sbom / sbom (push) Canceled after 0s
web-screenshots / screenshots (push) Canceled after 1s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m21s
audit / cargo-audit (push) Successful in 2m17s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m48s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m6s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m13s
nix / flake (push) Successful in 20m20s
flatpak / build-publish (push) Successful in 21m23s
|
||
|
|
e5180a5b7d |
chore(release): bump workspace version to 0.26.0
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m13s
apple / swift (pull_request) Successful in 1m35s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 2m39s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m15s
ci / bun-nix (pull_request) Successful in 56s
ci / docs-site (pull_request) Successful in 3m39s
ci / rust-arm64 (pull_request) Successful in 7m12s
android / android (pull_request) Successful in 9m5s
ci / rust (pull_request) Successful in 20m42s
nix / flake (pull_request) Failing after 20m28s
47 commits since v0.25.0, most of them from field reports on 0.25.0 itself, plus Wave 2 of the PyroWave Linux host-performance program. Nothing breaks: the wire protocol stays at 2 and the C ABI stays at 17, so this release adds no call, no message and no capability bit. pf-driver-proto is byte-for-byte identical to v0.25.0 and to v0.24.0. Four new environment variables (PUNKTFUNK_OVERLAY_MASK, PUNKTFUNK_GAMESCOPE_REFRESH_RATES, PUNKTFUNK_PYROWAVE_CHUNK_KIB, PUNKTFUNK_PYROWAVE_STREAMED_AU), verified new by git grep at the v0.25.0 tag rather than assumed. plugin-kit goes 0.3.2 -> 0.4.0 for the `plugin` launch kind; the SDK goes 0.1.2 -> 0.1.4; gamescope patch level +pfhdr2 -> +pfhdr4. Two behaviour changes make a client advertise LESS than it used to, both deliberate: VIDEO_CAP_444 is now probed against the driver rather than ridden off the setting alone (every Steam Deck with "Full chroma" on was losing HEVC entirely, not crispness — no AMD silicon decodes HEVC 4:4:4), and the Decky client-update check now reports a failure instead of dressing it up as "up to date". Bump is the same four files as 0.25.0: Cargo.toml, Cargo.lock, docs/releases/v0.26.0.md, docs/releases/whatsnew/v0.26.0.txt — plus the CHANGELOG.md section, which the split at 0.25.0 made part of the ritual. Gates run locally, all green: * cargo fmt --all --check clean * cargo metadata --locked resolves * Cargo.lock diff versions-only, 70/70 changed lines, 35 crates * Play whatsnew gate 398/500 chars, not byte-identical to any other release * notes voice check 0 internal-vocabulary hits above "## For developers" Notes are 66 lines against 0.25.0's 83, covering 47 commits. Still owed on glass and recorded in the CHANGELOG's verification table: iPhone + Bluetooth listen, Apple TV stats overlay, MacBook audio listen, the Deck HEVC/4:4:4 retest, a Windows wake-from-sleep cycle, and the PyroWave-under-game-load A/B with CAP_SYS_NICE actually granted. |
||
|
|
4070d043d6 |
Merge pull request 'PyroWave on Linux: the GPU-priority knob never fired, a dmabuf timeout condemned the host, and the jumbo grow was dead code' (#132) from worktree-wave2-pyrowave into main
apple / swift (push) Successful in 1m35s
android / android (push) Canceled after 3m13s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 3m9s
ci / rust (push) Canceled after 2m42s
ci / rust-arm64 (push) Canceled after 1m10s
ci / web (push) Canceled after 3s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 2s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 8s
nix / flake (push) Canceled after 7s
release / apple (push) Canceled after 5m6s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 15s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 14s
windows-host / package (push) Canceled after 3m27s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
Reviewed-on: #132 |
||
|
|
ebf61cb448 |
Merge branch 'worktree-wave2-pw5-encode-overlap' into worktree-wave2-pyrowave
apple / swift (pull_request) Successful in 1m34s
apple / screenshots (pull_request) Skipped
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m9s
ci / bun-nix (pull_request) Successful in 1m19s
ci / docs-site (pull_request) Successful in 1m57s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m13s
ci / web (pull_request) Successful in 3m25s
android / android (pull_request) Successful in 5m4s
ci / rust-arm64 (pull_request) Successful in 6m29s
ci / rust (pull_request) Successful in 11m37s
nix / flake (pull_request) Successful in 18m17s
# Conflicts: # crates/pf-encode/src/enc/linux/pyrowave.rs |
||
|
|
4a92c64144 | Merge branch 'worktree-wave2-pw7a-jumbo-shard' into worktree-wave2-pyrowave | ||
|
|
2426056465 | Merge branch 'worktree-wave2-pw6-streamed-au' into worktree-wave2-pyrowave | ||
|
|
d3aaa16a7d |
Merge branch 'worktree-wave2-pw3-dmabuf-latch' into worktree-wave2-pyrowave
# Conflicts: # packaging/arch/punktfunk-host.install # scripts/steamdeck/install.sh |
||
|
|
2dd65bdd41 |
Merge pull request 'Opening the Steam menu on the Deck moved the game too — the pad is now held neutral while an overlay owns it' (#131) from worktree-deck-overlay-input-mask into main
apple / swift (push) Successful in 1m38s
audit / cargo-audit (push) Successful in 1m45s
audit / bun-audit (plugin-kit) (push) Successful in 37s
audit / bun-audit (sdk) (push) Successful in 30s
audit / bun-audit (web) (push) Failing after 26s
audit / docs-site-audit (push) Successful in 26s
audit / pnpm-audit (push) Successful in 11s
arch / build-publish (push) Successful in 9m2s
ci / rust-arm64 (push) Successful in 3m6s
android / android (push) Successful in 10m27s
audit / license-gate (push) Successful in 5m59s
ci / web (push) Successful in 1m30s
ci / bun-nix (push) Successful in 58s
ci / docs-site (push) Successful in 1m56s
release / apple (push) Successful in 9m51s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m5s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 1m52s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 31s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
windows-host / package (push) Successful in 12m31s
windows-host / winget-source (push) Skipped
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 13s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 19s
apple / screenshots (push) Successful in 5m48s
deb / build-publish-client-arm64 (push) Successful in 6m51s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m35s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m45s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m13s
deb / build-publish-host (push) Successful in 11m34s
ci / rust (push) Canceled after 18m48s
deb / build-publish (push) Canceled after 12m54s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 8m0s
nix / flake (push) Canceled after 8m1s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 8m2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 5m37s
windows-host / canary-manifest (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 1m30s
Reviewed-on: #131 |