a9e7c033c3b972ebcb48b85da354e45ccac3dfd6
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db0637928b |
fix(decky): the shortcut liveness guard answered "alive" for every appId
`shortcutStillExists()` extracted the store method before calling it:
const get = appStore?.GetAppOverviewByAppID;
return get(appId) != null;
`GetAppOverviewByAppID` reads the store's own state (`this.m_mapApps`), so
the unbound call throws on the lost `this` — and the function's own
`catch { return true }` swallowed it. The guard therefore returned "still
exists" for EVERY appId. Not a stale-data bug: it never once answered no.
Everything downstream of it was consequently inert. A dangling appId — the
documented hazard this guard exists to catch, since the id outlives the
shortcut in Steam's CEF localStorage across a plugin reinstall — was never
dropped, so `ensureGamepadUiShortcut` always took the reuse branch and
`SetShortcut*`'d a dead id (silent no-ops). The visible library entry never
came back, `recreateShortcuts` reported success having done nothing (its
toast only checks for a non-null appId, and the dead one is non-null), and
"Open Punktfunk" ran `RunGame` on the dead id — Steam answers that with
"Game configuration unavailable".
Call it as a method so `this` survives, and guard the global with `typeof`
first: `appStore` is Steam-injected, and a bare reference to a missing one
is a ReferenceError that optional chaining does not prevent — which would
have landed in the same catch.
Verified against the live Deck that hit this: evaluated both versions over
its actual appIds, and where the old guard says alive/alive, the fixed one
says alive for the live stream shortcut and dead for the dangling UI id —
so the stale key now drops and the entry is recreated on the next mount.
|
||
|
|
0d407a866d |
fix: a host that changed DHCP lease could no longer be streamed from the panel
ci / rust (pull_request) Successful in 7m15s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 4m58s
ci / web (pull_request) Successful in 1m7s
apple / swift (pull_request) Successful in 1m24s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m54s
ci / rust-arm64 (pull_request) Successful in 3m5s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 3m16s
android / android (pull_request) Successful in 5m2s
An adversarial review of this branch found a regression I introduced, plus three smaller defects. All four are fixed here, each verified on .21. **The regression.** `mergeHosts` names a host by its record's stable id, and `hosts list --json` always emits one (`KnownHosts::load` mints ids for every record). So a launch always went out as `punktfunk launch <uuid>` → `ConnectPlan::for_host` → `HostTarget::from(&KnownHost)`, which copies the address stored ON THE RECORD. Meanwhile the panel deliberately renders the LIVE advert's address. Nothing on a Deck ever writes a moved address back — `discover` and `hosts list` are both reads, and only the desktop shells' hosts pages update one. So after any DHCP move the row read "online" at the new address and every press dialled the old one: a 15 s dead connect, or — if a MAC had ever been learned — a black Steam "game" for the full 90 s wake budget. Proven with a stub session binary: `launch abc-123` emitted `--connect 10.0.0.5:9777` for a host answering at `10.0.0.99`. This worked on origin/main, which dialled `toHost(v).host` — the advert's address. The fix restores that without giving up stable ids: `hosts add <new-addr> --fp <known-fp>` now MOVES the matching record instead of filing a second one (the fingerprint is the identity — this is the same rule that makes the verb idempotent), and the panel re-points a host it can see has moved before launching it. Verified: `moved 10.0.0.5:9777 to 10.0.0.99:9777`, one record still, and `launch abc-123` then emits `--connect 10.0.0.99:9777`. **"No hosts yet" was also how a missing client looked.** `_cli_argv()` returning None becomes `client-unavailable`, which the panel dropped on the floor — so a Deck with no client installed was told its network was empty, under a button that launches the client that isn't there. It now says which of the two it is. **The browse worker never exited on a quiet LAN.** `discover_for` drops the receiver and the doc claimed that stops the thread. It does not: the worker parks in `recv()`, and the arms that ignore an event (`SearchStarted`, `ServiceFound`, `SearchStopped`, a v6-only advert) never touch the sender, so on a LAN with no Punktfunk host nothing ever wakes it. Harmless today because the only caller is a short-lived CLI process, but the function invites in-process use, where it would leak a thread and an mDNS daemon per call. Now polled with a 250 ms tick and a check at the top of the loop. Verified: ten back-to-back browses settle back to the baseline thread count. **A `pair=optional` host was recorded as paired.** Every unsaved host now goes through the trust sheet (it has no pin, so it cannot stream without one), but the sheet's only non-PIN action ran `--request-access`, which persists `paired: true` on Ready. An optional host admits anyone who pins its identity — there is no operator decision, so nothing was approved and the same box read "paired" here and "trusted" in the desktop client. Such a host now gets **Connect** instead, which pins and streams without claiming an approval, and the "approve this Deck" toast is no longer shown to someone who has nobody to ask. Also: `PF_CLIENT_BIN` was the one launch-option value never validated — a client installed under a path with a space would split Steam's tokenizer. |
||
|
|
bf2d8505cf |
docs(decky): the gamepad-UI shortcut comment still named PF_HOST
PF_HOST is gone; the browse branch is keyed on PF_BROWSE alone and runs the SESSION binary, which is the one path this rework deliberately did not repoint. Comment only. |
||
|
|
017c37b78a |
feat(decky): rebuild the panel as a launcher — nested cards and request access
What is left of the plugin is what only a Decky plugin can do: start a stream through Steam so gamescope focuses it, and stand in front of the trust decision that gates it. One Quick Access panel, four sections, no route. HOSTS. One `useHosts()` calls discover and hosts-list together and merges them by fingerprint first, address second — so a host that moved DHCP lease still matches its record, and a different box that inherited the old address does not inherit its pairing. The CLI annotates `saved`/`paired` by that same rule, so the two surfaces cannot disagree. Rows sort online first, then most recently used, then by name: the host you streamed last night is the first thing under your thumb, and a host that is off right now never is. `needsPair` is now ONE rule: no pinned fingerprint. The session binary refuses a pinless connect, so a row without one can offer nothing but a button that fails. The old rule also consulted the advertised policy for unsaved hosts, which made the same box read differently before and after being saved. PINNED CARDS render NESTED under their host as `▸ <Profile name>`, not in a section of their own — a card IS a (host, profile) pair, and a row floating free of its host is exactly the "a pinned tile reads as a duplicate host" problem the desktop shells still have. The host's own BOUND profile is deliberately not drawn as a card: it applies silently on the plain row, and showing it twice would suggest the two do different things. This plugin creates, edits and deletes no profile and no card — pin creation belongs where profiles are edited. TRUST SHEET (new, trust.tsx). Request access (default) / Use a PIN instead… / Cancel, in the GTK dialog's order and wording. Request access is not a second ceremony — it saves the host with the fingerprint it ADVERTISED, then launches; the host parks that connect until its operator approves this Deck, admits it, and the stream starts by itself. No fingerprint, no request access. A host typed in by address advertises none, so the sheet offers the PIN path only and says why, rather than showing a button that could only fail. The sheet never TOFUs past a missing fingerprint: that pin is the only thing standing between a 185 s wait and an impostor answering for the host. The sheet is a `showModal` portal, so it captures its callbacks once and never re-renders from panel state — everything it acts on later is read through a ref. Reading a captured value is precisely what made pinning a second game compute from a stale base and clobber the first. LAUNCH PATH. The wrapper's contract becomes PF_REF / PF_PROFILE / PF_REQUEST_ACCESS / PF_BROWSE; PF_HOST, PF_LAUNCH, PF_MGMT and PF_CONNECT_TIMEOUT are gone. A stream is now `punktfunk launch <ref> [--profile <id>] --exec --fullscreen`, and a reference is all that ever rides Steam's launch options — no resolution, bitrate or codec, the same rule the deep-link grammar enforces. Request-access launches run SUPERVISED, without `--exec`: under --exec the CLI becomes the session, so no process survives to see the stream come up and record the approval. Safe for gamescope because focus follows reaper's descendant tree, not a single process, and flatpak-run/bwrap already sit in that tree on every other path. Wake-on-LAN comes out entirely. The plugin used to fire a magic packet itself and then stretch the connect budget to 75 s to cover the host's resume — a workaround for the CLI-less era. `punktfunk launch` runs the real wake-and-wait loop and only dials once the host answers, which is strictly better and deletes a backend method, a frontend call and a shell branch. The console-home branch of the wrapper is untouched on purpose: the shell binary already execs the session for `--browse`, so there is nothing to repoint and no reason to spend a diff there. Everything else in steam.ts — two shortcuts sharing one name (and so one Steam Input configset key), artwork versioning, appId verification, controller config, stopStream — is unchanged. |
||
|
|
a4aa89be31 |
fix(decky): drive a native client install, not only the flatpak
Every headless call went through `flatpak run io.unom.Punktfunk`, and the launch wrapper exec'd the same. On a Deck whose client came from a sysext, a .deb/rpm, an AUR build or a nix profile there is no such app: discovery worked (it is mDNS), and everything that needed the client — pairing, the library fetch, the host store, the stream launch itself — failed. Resolve the client once. The flatpak still wins when it is actually installed, so an existing Deck is byte-for-byte unchanged, and a native `punktfunk-client` is the fallback; `PF_DECKY_CLIENT` forces one on a machine with both. Both kinds share ~/.config/punktfunk — the flatpak's sandbox HOME is the real home — so identity, known-hosts and settings need no divergence. The wrapper takes the resolved binary as PF_CLIENT_BIN and execs it in place of `flatpak run`; kill_stream sends a name-matched SIGTERM where there is no flatpak instance to kill; and the client-update check, which reads the flatpak's tracked remote, simply reports nothing for a native install, whose updates belong to whatever installed it. Installed-ness is now decided by the app's exported directory rather than by the presence of the `flatpak` binary, so a machine that has flatpak but not this app no longer resolves to a client that cannot run. Closes #11 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8c9baf3214 |
fix(decky): recreate the library shortcut when its cached appId is stale
The visible "Punktfunk" shortcut's Steam appId is cached in SteamUI localStorage, which lives in Steam's CEF profile — so it survives a plugin uninstall/reinstall. Deleting the shortcut left the key dangling; on the next mount ensure*Shortcut recalled the dead appId, took the reuse branch, and ran SetShortcut* against a non-existent id (silent no-ops). AddShortcut was never reached, so the entry never came back. The same stale-reuse also made the hidden stream shortcut RunGame a dead gameid. Verify the remembered appId still maps to a live shortcut via appStore.GetAppOverviewByAppID before reusing it; a confident miss falls through to AddShortcut. When appStore is unavailable, assume it exists so a false negative can't spawn a duplicate library entry. Also add a "Recreate library shortcut" recovery button to the QAM About section (recreateShortcuts): drops any dead cached keys and re-ensures. Covers deleting the shortcut without reinstalling, where no mount fires the self-heal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
abc54a7d13 |
fix(decky): actually ship shortcut artwork + Steam Input layout in the published plugin
apple / swift (push) Successful in 1m23s
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 56s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
deb / build-publish-host (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8m58s
The CI workflow assembled the store zip by hand and silently drifted from
scripts/package.sh when
|
||
|
|
56d21f9445 |
fix(client/wol): retry the QUIC dial across the connect budget so wake-from-sleep launches survive
ci / web (push) Successful in 57s
ci / docs-site (push) Successful in 1m3s
apple / swift (push) Successful in 1m19s
decky / build-publish (push) Successful in 40s
ci / bench (push) Successful in 6m51s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 18s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 16s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m6s
deb / build-publish (push) Successful in 9m54s
release / apple (push) Successful in 9m5s
deb / build-publish-host (push) Successful in 9m35s
flatpak / build-publish (push) Failing after 8m19s
android / android (push) Successful in 16m57s
apple / screenshots (push) Successful in 6m29s
arch / build-publish (push) Successful in 17m35s
windows-host / package (push) Successful in 18m46s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
docker / deploy-docs (push) Successful in 26s
ci / rust (push) Successful in 25m24s
Launching from the Decky plugin against a sleeping host fired the Wake-on-LAN packet and dialed immediately — but the dial was a single quinn attempt that gave up after the transport's 8 s idle window, far shorter than a suspend-to-RAM resume. The host woke to find the client already gone (its retransmitted Initials show up host-side as "QUIC accept failed error=timed out"), the stream shortcut exited, and the launch looked cancelled. - punktfunk-core: dial in a loop until the embedder's connect budget is spent — short 3 s attempts keep the Initial cadence dense, so the connect lands within ~a second of the host's network returning. Only silence is retried: pin mismatches, typed rejections, and any real answer surface immediately, and the shutdown flag stops the loop. - decky: stretch the budget to 75 s when a magic packet actually went out (PF_CONNECT_TIMEOUT → --connect-timeout via the wrapper), so the retry window covers the resume; an awake host still connects in <1 s. - host: a clean close before the control handshake is a reachability probe (hosts-page online pips), not a failed session — log it at debug instead of WARN "session ended with error", which buried the real failures in the reporter's log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
60d4653083 |
feat(decky): native-touch controller layout + restructured shortcuts + artwork
Ship a Steam Input controller layout (controller_config/punktfunk.vdf) whose always-on `ts_n` command enables native touchscreen delivery on the Deck, and have the backend auto-install it (apply_controller_config: copy to controller_base/templates + upsert the per-account configset entry, chown to the user, back up first). This is what makes the Deck touchscreen reach the client as native touch under gamescope without disabling Steam Input (impossible on the Deck) — no manual controller setup. Two shortcuts sharing the "Punktfunk" name (so one config key covers both): a hidden stateful stream entry and a visible stateless entry that launches straight into the gamepad UI. Both get full artwork (grid/gridwide/hero/logo/icon, replaced with exported PNGs). Drop the art-generation script. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fb709d68ce |
feat(clients): Wake-on-LAN in apple/linux/windows/android/decky
Each client learns a host's MAC from the mDNS `mac` TXT while it's awake, persists it on the saved-host record, and — when reconnecting to an offline host — sends a magic packet before connecting, plus an explicit "Wake host" action. Apple wraps the C-ABI; linux/windows call the core fn directly (linux also gains a --wake CLI mode); android via a new nativeWakeOnLan JNI export (the mDNS browse record gains a 7th mac field); decky shells out to the linux client's --wake before launching the stream. iOS/tvOS need the managed com.apple.developer.networking.multicast entitlement (pending Apple approval), so the wake path + UI are gated off via PunktfunkConnection.wakeOnLANAvailable and the entitlement is commented out — keeping iOS/tvOS releasable. MAC-learning stays active on every platform so it lights up the moment it's ungated. macOS works today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3122506836 |
feat(decky): pinned-games library + self-update robustness; fix gamepad tab-nav
Decky client batch: - Pinned games / library picker: per-host game grid (GamePickerModal), pin/unpin, one-tap streams surfaced on the Hosts tab and QAM (usePins/streamPin/resolvePinHost, new src/library.tsx). - Self-update + client-update plumbing (main.py check_update, hooks.ts applyUpdate) with a CA-bundle-resolving SSL context and per-channel manifest polling; steam.ts / punktfunkrun.sh launch tweaks. - scripts/test-backend.py harness for the backend RPCs; README refresh. Fix: the fullscreen page wrapped <Tabs> in an overflow-visible box, so Valve's L1/R1 tab slide + autoFocusContents scrollIntoView panned #GamepadUI itself — the whole Steam UI slid left until a tab was clicked. Clip the Tabs wrapper (overflow:hidden), matching Valve's own Tabs containers. (On-glass verification pending — Deck offline this session.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c76a425ee1 |
feat(decky): visible branded Steam shortcut, one-tap client updates, fullscreen-page polish
- The "Punktfunk" shortcut is no longer hidden: it now ships committed
artwork (grid/wide/hero/logo/icon, generated by scripts/gen-steam-art.py
— a pure-stdlib SDF renderer drawing the lens mark + a monoline
"punktfunk" wordmark) applied via SetCustomArtworkForApp /
SetShortcutIcon. Existing installs are unhidden and re-arted once per
ART_VERSION; relaunching the library entry streams to the last host.
- Updates cover the flatpak CLIENT too: check_update compares the
user-scope installed commit against its remote, applyUpdate runs
`flatpak update --user` first (awaited) and the plugin reinstall —
which reloads the panel — last; docs spell out the sudo-less --user
update ("sudo flatpak update" silently skips per-user installs).
- Fullscreen page: DialogButton stretches to 100% width in the gamepad
UI, so the Stream/Pair/Refresh/… actions filled whole rows — sized to
content + right-aligned now; the header drops its Update button (About
tab + QAM banner keep the flow) and the back button gets a real 40px
hit target.
- Settings: the disable-Steam-Input note also shows for Automatic — on a
Deck that now forwards the built-in controller as a Steam Deck pad
(paddles/trackpads/gyro), which needs Steam Input off for the shortcut.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
bfd77a809e |
feat(decky): plugin overhaul — on-Deck update check, exec-bit-free runner, About/host-detail UI, Punktfunk branding
Fixes from live debugging on the Deck: - check_update() was dead on-device: Decky Loader's embedded (PyInstaller) Python has no usable default CA paths, so every HTTPS fetch failed with CERTIFICATE_VERIFY_FAILED. Build the SSL context explicitly: default paths first, then the known system bundles (SteamOS/Arch, Debian, Fedora/Bazzite, openSUSE), then certifi if importable. Verification stays on; the check stays offline-tolerant with its 30-min cache. - "could not chmod runner" on every use: Decky extracts plugin zips without exec bits into a root-owned dir the unprivileged backend can't chmod. The Steam shortcut now launches the runner through /bin/sh with the script as a %command% argument — no exec bit needed, existing shortcuts migrate on reuse, the chmod attempt is gone. UI/structure: - index.tsx (660 lines) split into page/pair/settings/hooks/boundary modules; PluginErrorBoundary kept guarding every surface. - New About section/tab: visible version + channel, explicit check-for-updates (forces past the cache, always toasts an outcome), setup-guide link, leave- chord help, and a Force-stop backstop for a wedged stream. - Host rows open a details modal (address, protocol, pairing policy, paired state, fingerprint). Settings gain 1280×800 (Deck native), Xbox One and DualShock 4 pad types, and a host-compositor picker. - Update flows note the Decky store contact can stall a couple of minutes on networks that blackhole plugins.deckbrew.xyz (observed live). - "Punktfunk" in all user-facing strings; plugin id/paths/env unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
df9470a0f2 |
feat(steam): M4 complete — C-ABI send path, Decky UX, Apple/Android parity
Finish the client side of the Steam Controller / Steam Deck pipeline. - C-ABI (core abi.rs): PunktfunkRichInputEx — a size-prefixed superset of PunktfunkRichInput that can express the second trackpad (surface), a distinct click vs touch, signed coords + pressure — plus punktfunk_connection_send_rich_input2 (the struct_size ABI-skew-guard precedent). The only way a C client (Apple/embedders) can emit a TouchpadEx; the legacy struct + send_rich_input stay byte-for-byte. punktfunk_core.h regenerated. - Decky (clients/decky): a "Steam Deck" gamepad type in Settings + an unmissable Disable-Steam-Input instruction shown when it's selected (in Game Mode Steam Input holds 0x1205, so the SDL HIDAPI Steam driver can't open the Deck's controls until the user disables Steam Input for the shortcut). Plus a best-effort, feature-detected disableSteamInputForShortcut() in launchStream — never blocks/throws; the manual toggle is the documented source of truth. - Apple parity (PunktfunkConnection.swift): GamepadType.steamController/steamDeck (wire 5/6) + name parsing, so the resolved type round-trips. Capture is blocked (GameController never surfaces a 0x28DE HID device). - Android parity (Gamepad.kt): PREF_STEAMCONTROLLER/STEAMDECK + the Valve 0x28DE PIDs in prefFor(). Rich-input capture stays out of scope (no rich-input plane yet) — standard buttons/sticks resolve to the host's Steam Deck pad. Rust workspace clippy/fmt/test green; Decky src/ typechecks clean (only a pre-existing @decky/api dep resolution error remains); Swift/Kotlin compile on their CI. The full pipeline is now BUILT; what remains is validation that needs hardware we don't have (a running Steam on the host, a live Deck client, the Moonlight paddle regression). Not pushed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9faa3f319a |
feat(decky): self-update without the store + Gaming-Mode launch polish, and ship the Steam Deck docs
Plugin self-update (no Decky store): CI publishes a per-channel manifest.json
({version, immutable per-version artifact, sha256}) beside the zip and bakes
update.json {channel, manifest} into the plugin. main.py `check_update` reads the
installed version from package.json (the value Decky reports — not plugin.json),
fetches the channel manifest, and the frontend shows an "Update to vX" button that
drives Decky Loader's own install RPC (root downloads + SHA-256-verifies + hot-reloads).
CI now stamps a plain-numeric semver (0.3.<run> canary / X.Y.Z stable) into
package.json — a -ciN suffix would mis-order under compare-versions.
Linux client: `--fullscreen` (plus SteamDeck/gamescope env fallback) enters GTK
fullscreen on stream start so Gaming-Mode chrome is hidden; native-mode resolution
falls back to the display's first monitor when the window isn't mapped yet (was
dropping to the 1080p floor — wrong on the Deck's 1280×800); add a confirmed
"Remove saved host" action (KnownHosts::remove_by_fp).
Docs: new docs/steam-deck.md (Decky install/pair/stream/self-update/troubleshooting),
wired into meta.json nav, and cross-linked from clients/install-client/channels. This
is the page docs.punktfunk.unom.io/docs/steam-deck — the website's download link
pointed at it before it existed; committing it makes that link resolve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3323c37585 |
feat(decky): full-featured Gaming-Mode client — fullscreen page, pairing, focus-correct launch
The plugin was a QAM launcher whose stream never appeared, with no
pairing. Three fixes, plus a headless --pair mode on the GTK client:
- Stream actually starts (MoonDeck's proven mechanism): gamescope only
focuses the process tree Steam launched via reaper, so a flatpak
spawned from the (root) backend is invisible. The frontend now
registers ONE hidden non-Steam shortcut pointing at bin/punktfunkrun.sh,
passes the host as the shortcut's Steam launch options, and starts it
with SteamClient.Apps.RunGame — gamescope then fullscreen-focuses it.
The wrapper execs `flatpak run io.unom.Punktfunk --connect <host>`.
- Fullscreen page: routerHook.addRoute("/punktfunk") — host list,
per-host Pair/Stream, and a settings section (resolution/refresh/
bitrate/gamepad/mic, written to client-gtk-settings.json).
- Pairing: a gamepad-navigable PIN keypad. The host shows the PIN; the
backend runs the SPAKE2 ceremony headlessly via the client's new
`--pair <PIN> --connect host` CLI mode (app.rs), persisting the host
as paired so the stream then connects silently. Same flatpak =>
shared identity store, verified live (ceremony against a real host).
- Backend (main.py): discover / pair / runner_info / get_settings /
set_settings / kill_stream; uses DECKY_USER_HOME so paths resolve to
the deck user's flatpak install regardless of the plugin's root flag.
CI (decky.yml) and the sideload packager now ship bin/punktfunkrun.sh.
The Steam-shortcut launch and headless-pairing env follow MoonDeck
exactly but need a Deck in Gaming Mode to fully confirm.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|