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>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a punktfunk-host .deb for Ubuntu/Debian hosts.
|
||||
#
|
||||
# Mirrors the Fedora RPM (../rpm/punktfunk.spec): the host binary + the uinput udev rule
|
||||
# + the systemd *user* unit + headless session helpers + example config + the OpenAPI doc.
|
||||
#
|
||||
# Runtime Depends are computed by `dpkg-shlibdeps` from the binary's actual DT_NEEDED, NOT
|
||||
# hand-listed: the binary pulls a large transitive lib closure (most of it via ffmpeg) and
|
||||
# the exact soname package names (libavcodec62, libpipewire-0.3-0t64, …) drift across distro
|
||||
# releases — shlibdeps tracks them automatically and pins them to whatever the BUILD distro
|
||||
# ships. Build this inside the Ubuntu 26.04 rust-ci image so those names match the target
|
||||
# boxes exactly. `--ignore-missing-info` drops libcuda.so.1 (the NVIDIA driver lib, linked via
|
||||
# FFI): on a GPU-less builder it resolves to no package, and we must never hard-depend on a
|
||||
# specific libnvidia-compute-<ver> anyway — NVENC/EGL come from the driver, out of band.
|
||||
#
|
||||
# Usage: VERSION=0.0.1~ci42.gdeadbee [ARCH=amd64] bash packaging/debian/build-deb.sh
|
||||
# Output: dist/punktfunk-host_<version>_<arch>.deb
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${VERSION:?set VERSION (e.g. 0.0.1 or 0.0.1~ci42.gdeadbee)}"
|
||||
ARCH="${ARCH:-amd64}"
|
||||
PKG="punktfunk-host"
|
||||
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOTDIR"
|
||||
|
||||
BIN="target/release/$PKG"
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "==> building $PKG (release)"
|
||||
cargo build --release -p "$PKG" --locked
|
||||
fi
|
||||
|
||||
STAGE="$(mktemp -d)"
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
DOCDIR="$STAGE/usr/share/doc/$PKG"
|
||||
SHAREDIR="$STAGE/usr/share/$PKG"
|
||||
|
||||
# --- file layout (matches the RPM %install) ----------------------------------
|
||||
install -Dm0755 "$BIN" "$STAGE/usr/bin/$PKG"
|
||||
install -Dm0644 scripts/60-punktfunk.rules "$STAGE/usr/lib/udev/rules.d/60-punktfunk.rules"
|
||||
install -Dm0644 scripts/punktfunk-host.service "$STAGE/usr/lib/systemd/user/punktfunk-host.service"
|
||||
install -Dm0755 scripts/headless/run-headless-kde.sh "$SHAREDIR/headless/run-headless-kde.sh"
|
||||
install -Dm0755 scripts/headless/run-headless-sway.sh "$SHAREDIR/headless/run-headless-sway.sh"
|
||||
install -Dm0644 scripts/host.env.example "$SHAREDIR/host.env.example"
|
||||
install -Dm0644 packaging/bazzite/host.env "$SHAREDIR/host.env.bazzite"
|
||||
install -Dm0644 docs/api/openapi.json "$SHAREDIR/openapi.json"
|
||||
install -Dm0644 LICENSE-MIT "$DOCDIR/LICENSE-MIT"
|
||||
install -Dm0644 LICENSE-APACHE "$DOCDIR/LICENSE-APACHE"
|
||||
install -Dm0644 README.md "$DOCDIR/README.md"
|
||||
|
||||
# Debian copyright + changelog (cheap, keeps the package well-formed).
|
||||
cat > "$DOCDIR/copyright" <<EOF
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: punktfunk
|
||||
Source: https://git.unom.io/unom/punktfunk
|
||||
|
||||
Files: *
|
||||
Copyright: punktfunk contributors
|
||||
License: MIT or Apache-2.0
|
||||
Dual-licensed. Full texts in /usr/share/doc/$PKG/LICENSE-MIT and
|
||||
/usr/share/doc/$PKG/LICENSE-APACHE.
|
||||
EOF
|
||||
printf '%s (%s) stable; urgency=medium\n\n * Automated build %s.\n\n -- unom <noreply@anthropic.com> %s\n' \
|
||||
"$PKG" "$VERSION" "$VERSION" "$(date -uR 2>/dev/null || echo 'Thu, 01 Jan 1970 00:00:00 +0000')" \
|
||||
| gzip -9n > "$DOCDIR/changelog.Debian.gz"
|
||||
|
||||
# --- dependencies ------------------------------------------------------------
|
||||
# Auto: the binary's directly-linked shared libs (libcuda ignored, see header).
|
||||
SHLIB_TMP="$(mktemp -d)"
|
||||
mkdir -p "$SHLIB_TMP/debian"
|
||||
cat > "$SHLIB_TMP/debian/control" <<EOF
|
||||
Source: $PKG
|
||||
|
||||
Package: $PKG
|
||||
Architecture: any
|
||||
Depends: \${shlibs:Depends}
|
||||
EOF
|
||||
SHDEPS_RAW="$(cd "$SHLIB_TMP" && dpkg-shlibdeps -O --ignore-missing-info "$ROOTDIR/$BIN" 2>/dev/null \
|
||||
| sed -n 's/^shlibs:Depends=//p')"
|
||||
rm -rf "$SHLIB_TMP"
|
||||
[ -n "$SHDEPS_RAW" ] || { echo "dpkg-shlibdeps produced no deps — is dpkg-dev installed?" >&2; exit 1; }
|
||||
|
||||
# Drop the NVIDIA driver lib unconditionally. --ignore-missing-info already skips libcuda on a
|
||||
# GPU-less builder (stub, no owning package), but on a box WITH the driver shlibdeps resolves
|
||||
# libcuda.so.1 -> libnvidia-compute-<ver> and would pin that exact driver build. NVENC/EGL are
|
||||
# provided by whatever driver the host runs, so this must never be a package dependency.
|
||||
SHDEPS="$(printf '%s' "$SHDEPS_RAW" | tr ',' '\n' | sed 's/^ *//; s/ *$//' \
|
||||
| grep -ivE '^(libnvidia-compute|libcuda)' | awk 'NF' | paste -sd ',' - | sed 's/,/, /g')"
|
||||
[ -n "$SHDEPS" ] || { echo "no deps left after filtering — unexpected" >&2; exit 1; }
|
||||
|
||||
# Manual additions shlibdeps can't see:
|
||||
# - libei1: input injection (libei) is loaded at runtime, not in DT_NEEDED.
|
||||
# - pipewire/wireplumber: runtime services (the daemon + session manager), not linked libs.
|
||||
DEPENDS="$SHDEPS, libei1, pipewire, wireplumber"
|
||||
# ffmpeg: Ubuntu's ffmpeg ships the NVENC-enabled libav* the binary links AND is the encoder
|
||||
# runtime; the libav* sonames are already hard Depends via shlibdeps, so the ffmpeg metapackage
|
||||
# is a Recommends. gamescope = a ready compositor backend; pipewire-pulse = desktop audio.
|
||||
RECOMMENDS="ffmpeg, gamescope, pipewire-pulse"
|
||||
SUGGESTS="kwin-wayland, mutter"
|
||||
|
||||
INSTALLED_KB="$(du -k -s "$STAGE" | cut -f1)"
|
||||
|
||||
install -d "$STAGE/DEBIAN"
|
||||
cat > "$STAGE/DEBIAN/control" <<EOF
|
||||
Package: $PKG
|
||||
Version: $VERSION
|
||||
Architecture: $ARCH
|
||||
Maintainer: unom <noreply@anthropic.com>
|
||||
Installed-Size: $INSTALLED_KB
|
||||
Section: net
|
||||
Priority: optional
|
||||
Homepage: https://git.unom.io/unom/punktfunk
|
||||
Depends: $DEPENDS
|
||||
Recommends: $RECOMMENDS
|
||||
Suggests: $SUGGESTS
|
||||
Description: Low-latency desktop/game streaming host (Moonlight + punktfunk/1)
|
||||
punktfunk is a Linux-first, low-latency desktop and game streaming host. It speaks
|
||||
the Moonlight/GameStream protocol (pair a stock Moonlight client) and its own native
|
||||
punktfunk/1 protocol (GF(2^16) Leopard FEC + AES-GCM, mid-stream mode renegotiation,
|
||||
client microphone passthrough). Each session gets a virtual output at the client's
|
||||
exact resolution and refresh via a per-compositor backend (KWin, gamescope, Mutter,
|
||||
Sway/wlroots), captured zero-copy (dmabuf -> CUDA -> NVENC). Input (mouse, keyboard,
|
||||
gamepads) is injected back into the session.
|
||||
.
|
||||
NVENC + GPU EGL come from the NVIDIA driver (libnvidia-encode / libEGL_nvidia),
|
||||
installed out of band. After install: add yourself to the 'input' group for virtual
|
||||
gamepads, then `systemctl --user enable --now punktfunk-host`.
|
||||
EOF
|
||||
|
||||
cat > "$STAGE/DEBIAN/postinst" <<'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ "$1" = "configure" ]; then
|
||||
# Pick up the /dev/uinput rule without a reboot (best-effort, no-op in containers).
|
||||
udevadm control --reload-rules 2>/dev/null || true
|
||||
udevadm trigger --subsystem-match=misc 2>/dev/null || true
|
||||
echo "punktfunk-host installed. Add yourself to the 'input' group for virtual gamepads:"
|
||||
echo " sudo usermod -aG input \"\$USER\" # then re-login"
|
||||
echo "Config: mkdir -p ~/.config/punktfunk && cp /usr/share/punktfunk-host/host.env.example ~/.config/punktfunk/host.env"
|
||||
echo "Enable: systemctl --user enable --now punktfunk-host"
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod 0755 "$STAGE/DEBIAN/postinst"
|
||||
|
||||
mkdir -p dist
|
||||
OUT="dist/${PKG}_${VERSION}_${ARCH}.deb"
|
||||
dpkg-deb --root-owner-group --build "$STAGE" "$OUT" >/dev/null
|
||||
echo "built $OUT"
|
||||
echo " Depends: $DEPENDS"
|
||||
dpkg-deb -I "$OUT" | sed -n 's/^/ /p' | grep -E 'Version|Installed-Size' || true
|
||||
Reference in New Issue
Block a user