b3f98a5d7d
ci / rust (push) Successful in 2m7s
ci / web (push) Successful in 26s
ci / docs-site (push) Successful in 29s
apple / swift (push) Successful in 1m15s
ci / bench (push) Successful in 1m35s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m20s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m52s
docker / deploy-docs (push) Successful in 16s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m23s
A Decky Loader plugin so a Steam Deck / SteamOS box can launch the punktfunk client from Gaming Mode using REAL Steam UI components (it runs inside Steam's CEF, so the panel is built from @decky/ui — the literal Big Picture primitives, not a replica). - Frontend (src/index.tsx, @decky/api + @decky/ui): a Quick Access Menu panel — Refresh → discover hosts, a native list (name, ip:port, pairing flag), tap to connect with a status toast, Disconnect. - Backend (main.py): discover() shells `avahi-browse -rpt _punktfunk._udp` and parses the host's advertised TXT keys (proto/fp/pair/id from discovery.rs), dedup by id preferring IPv4; connect() resolves + spawns `punktfunk-client --connect host:port` (gamescope composites its video like a game), tracking the child; disconnect() terminates it. - Mirrors the current official Decky template (the API moved to @decky/ui + @decky/api). Frontend builds clean (pnpm build → dist/index.js); main.py py_compiles. dist/ + node_modules gitignored — build on the Deck per README. Spike scope: launcher only, runtime untested (no Deck here). Next on this track: the in-stream Quick-Access overlay (volume/disconnect/stats over the running stream) and a fuller real-components UI. Client decode on the AMD Deck is the existing VAAPI path; the host-encode VAAPI gap is separate (NVIDIA host = NVENC). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
95 lines
4.3 KiB
Markdown
95 lines
4.3 KiB
Markdown
# punktfunk Decky plugin (SteamOS / Steam Deck)
|
|
|
|
A **[Decky Loader](https://decky.xyz/)** plugin that adds a **punktfunk** panel to the Steam
|
|
Deck's Quick Access Menu (the QAM, opened with the `…` button), so you can launch the
|
|
punktfunk streaming client from **Gaming Mode** without dropping to the desktop.
|
|
|
|
Because Decky plugins run inside Steam's CEF, the panel is built from real Steam UI
|
|
primitives (`@decky/ui`: `PanelSection`, `PanelSectionRow`, `ButtonItem`, `Field`,
|
|
`Spinner`) — so it looks and feels native to Gaming Mode.
|
|
|
|
> **Spike / launcher only.** This is a minimal but functional first cut: discover hosts,
|
|
> connect, disconnect. It launches the existing native GTK4 client
|
|
> (`punktfunk-client`) over the top of Gaming Mode. An in-stream overlay (latency / bitrate
|
|
> HUD, mid-session controls) and a fuller real-Steam-components UI are the next steps.
|
|
> Runtime behavior on a real Deck is **untested** — only the build is verified here.
|
|
|
|
## What it does
|
|
|
|
1. **Refresh** — browses the LAN over mDNS for punktfunk/1 hosts (the `_punktfunk._udp`
|
|
service) via the backend `discover()`.
|
|
2. **Lists discovered hosts** — name, `ip:port`, and a lock icon for whether pairing is
|
|
required (`pair=required` in the host's TXT record).
|
|
3. **Connect** — selecting a host calls `connect(host, port)`, which launches
|
|
`punktfunk-client --connect host:port`; a toast and the status line reflect the result.
|
|
4. **Disconnect** — `disconnect()` terminates the launched client.
|
|
|
|
## Architecture
|
|
|
|
| File | Role |
|
|
| --- | --- |
|
|
| `src/index.tsx` | Frontend QAM panel (`@decky/ui` + `@decky/api`). |
|
|
| `main.py` | Backend `Plugin` class: `discover` / `connect` / `disconnect` / `status` exposed over the Decky bridge. |
|
|
| `plugin.json` | Decky plugin manifest. |
|
|
| `decky.pyi` | Type stub for the injected `decky` module (vendored from the template). |
|
|
|
|
### Discovery (`discover()`)
|
|
|
|
Shells out to **`avahi-browse -rpt _punktfunk._udp`** (SteamOS and Bazzite ship
|
|
`avahi-daemon`; this avoids bundling python-zeroconf):
|
|
|
|
- `-r` resolve services, `-p` parseable output, `-t` terminate after the cache dump.
|
|
- Resolved records start with `=` and are semicolon-separated:
|
|
`=;iface;protocol;name;type;domain;hostname;address;port;txt`.
|
|
- The `txt` column is space-separated, quoted `"key=value"` tokens. We read the keys the
|
|
host advertises (`crates/punktfunk-host/src/discovery.rs`): `proto`, `fp`, `pair`, `id`.
|
|
- Records are deduped on the `id` TXT key (a host re-advertises per interface and across
|
|
IPv4/IPv6), preferring the IPv4 address for the user-facing host string.
|
|
|
|
### Client launch (`connect()`)
|
|
|
|
The client binary `punktfunk-client` is resolved in order: `PATH` → `/usr/bin` →
|
|
`/usr/local/bin` → `~/.local/bin` → a `flatpak run earth.buehler.punktfunk.Client`
|
|
fallback. The resolved argv and a clear `client-not-found` error surface to the UI. The
|
|
child PID is tracked so `disconnect()` (and plugin `_unload`) can terminate it.
|
|
|
|
> **TODO:** pin the canonical SteamOS install path once a Deck packaging story for
|
|
> `punktfunk-client` is settled (likely a flatpak, since SteamOS `/usr` is read-only).
|
|
|
|
## Prerequisites
|
|
|
|
- **Decky Loader** installed on the Deck (https://decky.xyz/).
|
|
- **`punktfunk-client`** (the GTK4/libadwaita Linux client, crate `punktfunk-client-linux`)
|
|
installed and runnable on the Deck — via `.deb`/RPM/flatpak, or symlinked into
|
|
`~/.local/bin`.
|
|
- **avahi** (`avahi-daemon` + `avahi-browse`) for discovery — present on SteamOS/Bazzite.
|
|
- A punktfunk/1 host on the LAN (`punktfunk-host serve --native` or `m3-host`).
|
|
|
|
## Build
|
|
|
|
```sh
|
|
pnpm install
|
|
pnpm build # rollup → dist/index.js
|
|
```
|
|
|
|
(`npm install && npm run build` also works.)
|
|
|
|
## Install on the Deck
|
|
|
|
Copy the built plugin directory to the Deck and restart Decky:
|
|
|
|
```sh
|
|
# the dir must contain: dist/, main.py, plugin.json, package.json
|
|
rsync -a --exclude node_modules clients/decky/ deck@<deck-ip>:~/homebrew/plugins/punktfunk/
|
|
# then, on the Deck, restart Decky Loader (Settings → Developer → "Restart" / reboot)
|
|
```
|
|
|
|
The **punktfunk** panel then appears in the Quick Access Menu.
|
|
|
|
## Limitations / next steps
|
|
|
|
- Launcher only — no in-stream overlay yet; the client owns the full session once launched.
|
|
- mDNS discovery depends on `avahi-browse`; no manual "add host by IP" entry yet.
|
|
- Pairing (PIN ceremony) is handled by the launched client, not the panel.
|
|
- Not yet tested on real Deck hardware.
|