Files
punktfunk/packaging
enricobuehler a95984bb4f feat(client-linux): feature parity with the Swift client
Everything the macOS app does that stage 1 lacked, before any new
feature work (user directive):

- Input capture is now a deliberate, reversible STATE (Moonlight-
  style): engaged on stream start and click-into-video (the engaging
  click is suppressed), released by Ctrl+Alt+Shift+Q (toggles) or
  focus loss; held keys/buttons are flushed host-side on release;
  cursor hiding + shortcut inhibition follow the state; HUD hint when
  released. Per-session window handlers disconnect with the page.
- Gamepads: app-lifetime SDL service (GamepadManager parity) — pad
  list + "Forwarded controller" pin in Settings (auto = most recent),
  "Automatic" pad TYPE resolves from the physical pad at connect;
  DualSense touchpad contacts + ~250 Hz motion samples on the 0xCC
  plane (Swift GamepadWire scale constants); feedback grows adaptive-
  trigger replay and player LEDs via raw DS5 effects packets (the
  wire's 11-byte blocks drop into SDL_SendGamepadEffect verbatim);
  held pad state zeroed on pad switch/detach. sdl3 "hidapi" feature.
- Microphone uplink: PipeWire capture -> Opus 20 ms -> 0xCB datagrams
  (validated live: host received 711 mic packets), Settings toggle.
- Speed test per saved host (Swift's "Test Network Speed…"): 2 s
  probe burst, goodput/loss + recommended ~70 % bitrate, one-tap apply.
- Settings: host compositor preference (sent in the Hello), native-
  display resolution/refresh resolved from the window's monitor at
  connect (new default), bitrate ceiling to 3 Gbit/s.
- Hosts page: saved/trusted hosts section for direct pinned reconnect
  (mDNS not required), rebuilt on every page return.

Deliberately not ported: audio device pickers (PipeWire routing owns
this on Linux), resize-to-request_mode (not wired in Swift either),
pointer-lock relative mouse (stage-2 presenter, needs raw Wayland).
DualSense fidelity needs a physical pad to live-verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:11:52 +00:00
..

Packaging punktfunk for Fedora / Bazzite

The punktfunk host is Linux-only and links system FFmpeg (NVENC), PipeWire, Opus and the NVIDIA driver. This directory packages it for the Fedora Atomic / Bazzite world (rpm-ostree + bootc), where most of those deps are already present.

👉 End-to-end Bazzite setup walkthrough (install → udev/group → host.env → service → firewall → verify → troubleshooting): bazzite/README.md. This file is the higher-level packaging rationale.

packaging/
  rpm/punktfunk.spec      # the RPM (builds punktfunk-host from source with cargo)
  bazzite/host.env        # gamescope-default config for a Bazzite appliance
  bazzite/README.md       # step-by-step Bazzite setup guide
  bootc/Containerfile     # bake punktfunk into a Bazzite-based atomic image
  copr/                   # COPR build-from-SCM settings

What's needed beyond base Fedora

Dependency Where it comes from
ffmpeg-libs with NVENC RPM Fusion nonfree (ffmpeg, not ffmpeg-free)
NVIDIA driver (libnvidia-encode, libEGL_nvidia) Bazzite -nvidia images ship it; plain Fedora: akmod-nvidia + xorg-x11-drv-nvidia-cuda
gamescope, PipeWire, wireplumber Bazzite ships these; plain Fedora: dnf install gamescope pipewire wireplumber
opus, libei Fedora base / updates

On Bazzite the only genuinely new runtime bits are ffmpeg-libs (RPM Fusion) + opus + libei — the rest of the stack is already there. The default backend is gamescope (packaging/bazzite/host.env), which the host spawns headless per session — no desktop login.

Option A — COPR (per-host, rpm-ostree install)

  1. Create a COPR project, enable build-from-SCM pointing at this repo, spec path packaging/rpm/punktfunk.spec (see copr/README.md). Under External Repositories add RPM Fusion nonfree so ffmpeg-devel resolves at build time.
  2. On the Bazzite host:
    # RPM Fusion (for the NVENC ffmpeg) — usually already enabled on Bazzite
    rpm-ostree install \
      https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
      https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
    # enable the COPR + install punktfunk
    sudo wget -O /etc/yum.repos.d/_copr_punktfunk.repo \
      https://copr.fedorainfracloud.org/coprs/enricobuehler/punktfunk/repo/fedora-$(rpm -E %fedora)/
    rpm-ostree install punktfunk
    systemctl reboot
    

Option B — bootc (image-based, atomic)

Layer punktfunk into a Bazzite image once, then rebase any number of hosts onto it — no per-host drift. See bootc/Containerfile:

podman build -t ghcr.io/<you>/bazzite-punktfunk -f packaging/bootc/Containerfile .
podman push  ghcr.io/<you>/bazzite-punktfunk
# on the target:
sudo bootc switch ghcr.io/<you>/bazzite-punktfunk && systemctl reboot

First-run setup (either option)

ujust add-user-to-input-group           # virtual gamepads need /dev/uinput (then re-login).
                                        # On Bazzite use ujust, NOT `usermod -aG input` (atomic OS — it won't stick).
mkdir -p ~/.config/punktfunk
cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env   # edit (gamescope app, etc.)
systemctl --user enable --now punktfunk-host

Pair a stock Moonlight client (mDNS-discovered), or connect the native punktfunk/1 client.

Why not Flatpak?

The host needs unsandboxed access the zero-copy NVENC path, /dev/uinput, the PipeWire graph and the compositor's privileged protocols — a Flatpak sandbox fights all of these. An RPM (or the bootc layer) installs into the host system where those just work.

Building the SRPM/RPM locally (Fedora only)

git archive --format=tar.gz --prefix=punktfunk-0.0.1/ -o ~/rpmbuild/SOURCES/punktfunk-0.0.1.tar.gz HEAD
rpmbuild -ba packaging/rpm/punktfunk.spec     # needs the BuildRequires from the spec

(Not buildable on Debian/Ubuntu — use a Fedora toolbox/container or COPR.)