cargo tree -p punktfunk-client-session finds no ffmpeg. The host still does, which is the whole point: pf-encode keeps libavcodec unconditionally and no host workflow, packaging script or licence file was touched. Deleted: crates/pf-ffvk, video_vulkan.rs, video_vaapi.rs, video_libav.rs, the libavcodec half of video_d3d11.rs, the av_log machinery, ffmpeg::codec::Id as the decoder's vocabulary (the quic CODEC_* wire constants now serve, which is why the evidence table was keyed on them), DecodedImage::VkFrame and ::Dmabuf, the presenter's AVVkFrame lane, and the ffmpeg-fallback feature with everything behind it. DrmFrameGuard collapses from an enum to a newtype, which removes an unsafe impl Send. Roughly 25,000 lines. Then the CI, packaging, licensing and docs work the plan's §6 lists: the Windows workflows lose FFMPEG_DIR, PF_FFVK_VULKAN_INCLUDE and their PATH prepend; the MSIX loses its DLL wildcard; the client .deb stops emitting libav sonames on its own because depends come from dpkg-shlibdeps; arch, flatpak and nix drop the dependency; and the README's "FFmpeg 7 or 8" contract narrows to the host. Three defects reached users' machines in the first cut, and none was in the deletion itself. All three desktop Settings UIs offer vulkan, vaapi and d3d11va as stored decoder values, so those strings sit in shipped settings files today. Refusing them by name — which is the correct rule for a stale pin — would have bricked every upgraded client whose owner ever touched that dropdown. They now migrate onto the native rung for the same hardware family, at decoder construction AND at each dialog's lookup, because a legacy value that matches no preset displays as "Automatic" and silently rewrites the user's preference on the next save. M9's evidence filter was deleted on the argument that with no libavcodec twin below, barring an unproven rung removes hardware decode rather than moving down one rung. That is true on Windows and false on Linux for Intel and every unknown vendor id, where prefer_vulkan_first is false and the order is native-vaapi → native-vk: a rung that has decoded nothing anywhere sitting above one that is 250/250 on three drivers. Every Intel Linux desktop would have moved from libavcodec VAAPI, shipping for years, onto pf-vaadec by default — and a rung that constructs and then produces wrong pixels leaves only by the error-streak demotion, which this codebase already documents as not tripping on the B580's strobing. The filter is restored as a narrow, pure, testable rule: an unproven rung yields to a proven one, and to nothing else. Windows deliberately passes no rung below, because that vendor family is the one with a measured wrong-pixel report against Vulkan decode, and trading no evidence for evidence of corruption is the wrong direction. And the notices still said FFmpeg was bundled. The root file is what both desktop clients include_str! and what the MSIX ships, three lines under the new card saying no FFmpeg is bundled; Apple's Acknowledgements said it too, on iOS, tvOS and macOS. The generator now emits four per-client files scoped by transitive closure — 0 FFmpeg mentions in each, verified — while the root file keeps it for the host. That also ends the standing false attribution of ffmpeg-next, GTK4, windows-rs and the NVENC SDK to an iPhone. Windows has no reachable box, so it was compiled instead: a cross clippy at -D warnings on x86_64 and aarch64-pc-windows-msvc with the C toolchain stubbed so build scripts run without linking. That gate immediately caught an include_str! path one directory too deep, which nothing else could have. Gates: container clippy -D warnings, 160 tests, workspace check, both Windows targets clean, client ffmpeg count 0 and host 2. The four decode crates are untouched, so the hardware rungs' 250/250 stands. ⚠ Owed and unrun: no GPU has executed any of this milestone. M8's on-glass software check, M7's D3D11 and VAAPI AV1 hardware legs, and M9's field bake all still want hardware, and the bake window and criteria remain the user's.
223 lines
12 KiB
Bash
223 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
# Build the punktfunk-client .deb (the native GTK4 client) for Ubuntu/Debian desktops.
|
|
#
|
|
# Counterpart to build-deb.sh (the host package); same conventions: runtime Depends are
|
|
# computed by dpkg-shlibdeps from the binaries' DT_NEEDED (GTK4/libadwaita, SDL3, the
|
|
# PipeWire/Opus sonames), so build inside the Ubuntu 26.04 rust-ci image to pin
|
|
# the package names the target boxes ship. The client links no NVIDIA libs — no filter
|
|
# needed.
|
|
#
|
|
# NO libav* here since M10 (design/client-native-decode.md §6): the client decodes with
|
|
# pf-vkdecode / pf-vaadec (libva is dlopen'd, never linked) / openh264+rav1d, so nothing in
|
|
# either binary has an FFmpeg DT_NEEDED and shlibdeps stops emitting `libavcodec…` on its
|
|
# own — there is no list here to prune, which is exactly the property to keep. That also
|
|
# ends the soname coupling that forced the host package's BUNDLE_FFMPEG dance: a client
|
|
# .deb built on 26.04 no longer names a libavcodec the target box must have.
|
|
# The HOST package (build-deb.sh) is unchanged — it encodes with libavcodec and still
|
|
# depends on / bundles it.
|
|
#
|
|
# Usage: VERSION=0.0.1~ci42.gdeadbee [ARCH=amd64] [TARGET=<rust triple>] \
|
|
# bash packaging/debian/build-client-deb.sh
|
|
# Output: dist/punktfunk-client_<version>_<arch>.deb
|
|
#
|
|
# TARGET cross-compiles (and moves the binaries under target/<triple>/release). Set it
|
|
# together with ARCH — e.g. ARCH=arm64 TARGET=aarch64-unknown-linux-gnu inside the
|
|
# ci/rust-ci-arm64cross.Dockerfile image, which carries the matching :arm64 sysroot.
|
|
set -euo pipefail
|
|
|
|
VERSION="${VERSION:?set VERSION (e.g. 0.0.1 or 0.0.1~ci42.gdeadbee)}"
|
|
ARCH="${ARCH:-amd64}"
|
|
TARGET="${TARGET:-}"
|
|
PKG="punktfunk-client"
|
|
CRATE="punktfunk-client-linux"
|
|
ROOTDIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
cd "$ROOTDIR"
|
|
|
|
# Cargo drops a cross build under target/<triple>/release, a native one straight in
|
|
# target/release.
|
|
if [ -n "$TARGET" ]; then
|
|
OUTDIR="target/$TARGET/release"
|
|
CARGO_TARGET_ARGS=(--target "$TARGET")
|
|
else
|
|
OUTDIR="target/release"
|
|
CARGO_TARGET_ARGS=()
|
|
fi
|
|
|
|
BIN="$OUTDIR/$PKG"
|
|
# The Vulkan/Skia session streamer the shell execs for a connect — shipped alongside the shell
|
|
# (the shell resolves it as its /usr/bin sibling), or desktop streaming breaks.
|
|
SESSION_BIN="$OUTDIR/punktfunk-session"
|
|
# The headless CLI (design/client-architecture-split.md §4) ships with every client.
|
|
CLI_BIN="$OUTDIR/punktfunk"
|
|
# Test every binary this script goes on to install, not a subset. It used to check only $BIN and
|
|
# $SESSION_BIN while installing three, so a caller that pre-built exactly those two (deb.yml did)
|
|
# satisfied the guard, skipped this build, and then died on `install: No such file or directory`
|
|
# for $CLI_BIN — a confusing way to say "the CLI was never built". The arm64 leg never hit it
|
|
# because it pre-builds nothing, so the guard always fired there and built all three.
|
|
# pf-update joins the list: the client ships its own copy of the root helper for one-tap
|
|
# updates (`punktfunk-client --apply-update`). Same guard rule as above — every binary this
|
|
# script installs is tested here, so a pre-built subset can't skip the build and then die on
|
|
# `install: No such file or directory`.
|
|
UPDATE_BIN="$OUTDIR/pf-update"
|
|
if [ ! -x "$BIN" ] || [ ! -x "$SESSION_BIN" ] || [ ! -x "$CLI_BIN" ] || [ ! -x "$UPDATE_BIN" ]; then
|
|
echo "==> building $CRATE + punktfunk-client-session + punktfunk-cli + pf-update (release${TARGET:+ for $TARGET})"
|
|
cargo build --release --locked "${CARGO_TARGET_ARGS[@]}" -p "$CRATE" -p punktfunk-client-session \
|
|
-p punktfunk-cli -p pf-update
|
|
fi
|
|
|
|
STAGE="$(mktemp -d)"
|
|
trap 'rm -rf "$STAGE"' EXIT
|
|
DOCDIR="$STAGE/usr/share/doc/$PKG"
|
|
|
|
# --- file layout --------------------------------------------------------------
|
|
install -Dm0755 "$BIN" "$STAGE/usr/bin/$PKG"
|
|
install -Dm0755 "$SESSION_BIN" "$STAGE/usr/bin/punktfunk-session"
|
|
install -Dm0755 "$CLI_BIN" "$STAGE/usr/bin/punktfunk"
|
|
install -Dm0644 packaging/linux/io.unom.Punktfunk.desktop \
|
|
"$STAGE/usr/share/applications/io.unom.Punktfunk.desktop"
|
|
# The app icon the desktop entry (and the About dialog) name. Without it the launcher falls
|
|
# back to a generic monitor glyph, which is what shipped until now.
|
|
install -Dm0644 packaging/linux/icons/hicolor/scalable/apps/io.unom.Punktfunk.svg \
|
|
"$STAGE/usr/share/icons/hicolor/scalable/apps/io.unom.Punktfunk.svg"
|
|
# DualSense hidraw access (full pad fidelity through SDL's HIDAPI driver).
|
|
install -Dm0644 scripts/70-punktfunk-client.rules \
|
|
"$STAGE/usr/lib/udev/rules.d/70-punktfunk-client.rules"
|
|
# UDP receive-buffer tuning (32 MB) — without it the kernel clamps the client's SO_RCVBUF and
|
|
# high-bitrate streams overflow it at the receiver (measured: 4 MB cap = 31.6% loss at 2 Gbps,
|
|
# 32 MB = 0%). systemd-sysctl applies it at boot; the postinst applies it on install.
|
|
install -Dm0644 scripts/99-punktfunk-client-net.conf \
|
|
"$STAGE/usr/lib/sysctl.d/99-punktfunk-client-net.conf"
|
|
# One-tap client updates (`punktfunk-client --apply-update`, which is what the Decky plugin
|
|
# runs): the same root helper the host package ships, under the CLIENT's own paths + its own
|
|
# unit and polkit rule. Separate paths because dpkg refuses two packages owning one file, and a
|
|
# client-only box must be able to install this without punktfunk-host. Opt-in = joining the
|
|
# (shipped-empty) punktfunk-update group; postinst creates it.
|
|
install -Dm0755 "$UPDATE_BIN" "$STAGE/usr/libexec/punktfunk/pf-update-client"
|
|
install -Dm0644 packaging/linux/punktfunk-client-update.service \
|
|
"$STAGE/usr/lib/systemd/system/punktfunk-client-update.service"
|
|
sed -i 's#/usr/libexec/punktfunk/pf-update#/usr/libexec/punktfunk/pf-update-client#' \
|
|
"$STAGE/usr/lib/systemd/system/punktfunk-client-update.service"
|
|
install -Dm0644 packaging/linux/49-punktfunk-client-update.rules \
|
|
"$STAGE/usr/share/polkit-1/rules.d/49-punktfunk-client-update.rules"
|
|
# Install-kind + channel marker for the CLIENT, read by `punktfunk-client --check-update`
|
|
# (planning: host-update-from-web-console.md §4.1). Its own directory, matching the RPM. A
|
|
# canary build's version carries `~ciN`; anything else is stable.
|
|
case "$VERSION" in
|
|
*~ci*) _pf_client_channel=canary ;;
|
|
*) _pf_client_channel=stable ;;
|
|
esac
|
|
printf 'apt %s\n' "$_pf_client_channel" | \
|
|
install -Dm0644 /dev/stdin "$STAGE/usr/share/punktfunk-client/install-kind"
|
|
install -Dm0644 LICENSE-MIT "$DOCDIR/LICENSE-MIT"
|
|
install -Dm0644 LICENSE-APACHE "$DOCDIR/LICENSE-APACHE"
|
|
install -Dm0644 README.md "$DOCDIR/README.md"
|
|
# Third-party crate attributions (regenerate with scripts/gen-third-party-notices.sh).
|
|
#
|
|
# The CLIENT-scoped copy, not the workspace-wide one at the repo root: the root file is the host's
|
|
# and still lists ffmpeg-next plus the full FFmpeg licence text, while this package links no FFmpeg
|
|
# at all since M10. It is the same file the GTK shell shows on its About → Legal page, so the
|
|
# installed doc and the running app say the same thing. Falls back to the root file only if the
|
|
# generated copy is missing (an old checkout), because shipping no attribution at all is worse.
|
|
if [ -f clients/linux/THIRD-PARTY-NOTICES.txt ]; then
|
|
install -Dm0644 clients/linux/THIRD-PARTY-NOTICES.txt "$DOCDIR/THIRD-PARTY-NOTICES.txt"
|
|
elif [ -f THIRD-PARTY-NOTICES.txt ]; then
|
|
echo "warning: clients/linux/THIRD-PARTY-NOTICES.txt missing — shipping the workspace-wide" \
|
|
"file, which attributes host-only dependencies to the client" >&2
|
|
install -Dm0644 THIRD-PARTY-NOTICES.txt "$DOCDIR/THIRD-PARTY-NOTICES.txt"
|
|
fi
|
|
|
|
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: unom and the 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 <packages@unom.io> %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 --------------------------------------------------------------
|
|
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
|
|
# Resolve DT_NEEDED for BOTH the shell and the session streamer (dpkg-shlibdeps merges).
|
|
SHDEPS="$(cd "$SHLIB_TMP" && dpkg-shlibdeps -O --ignore-missing-info \
|
|
"$ROOTDIR/$BIN" "$ROOTDIR/$SESSION_BIN" 2>/dev/null \
|
|
| sed -n 's/^shlibs:Depends=//p')"
|
|
rm -rf "$SHLIB_TMP"
|
|
[ -n "$SHDEPS" ] || { echo "dpkg-shlibdeps produced no deps — is dpkg-dev installed?" >&2; exit 1; }
|
|
|
|
# Manual addition shlibdeps can't see: the session streamer loads libvulkan at runtime (ash,
|
|
# dlopen — not a DT_NEEDED), and streaming can't start without it, so it's a hard Depends.
|
|
SHDEPS="$SHDEPS, libvulkan1"
|
|
|
|
# Manual additions shlibdeps can't see: the PipeWire daemon + session manager are runtime
|
|
# services (audio playback / mic capture degrade gracefully without them — Recommends).
|
|
# NOT pipewire-pulse: the client speaks native PipeWire (audio.rs → libpipewire-0.3) and never
|
|
# opens a Pulse socket, and the shim Conflicts with pulseaudio — so recommending it only nags
|
|
# users who run real PulseAudio, in exchange for nothing the client can use.
|
|
RECOMMENDS="pipewire, wireplumber"
|
|
|
|
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 <packages@unom.io>
|
|
Installed-Size: $INSTALLED_KB
|
|
Section: net
|
|
Priority: optional
|
|
Homepage: https://git.unom.io/unom/punktfunk
|
|
Depends: $SHDEPS
|
|
Recommends: $RECOMMENDS
|
|
Description: Low-latency desktop/game streaming client (punktfunk/1, GTK4)
|
|
The native Linux client for punktfunk, a Linux-first low-latency desktop and
|
|
game streaming stack. Discovers hosts on the LAN (mDNS), trusts them via
|
|
certificate pinning with a SPAKE2 PIN pairing ceremony, and streams HEVC video
|
|
(GF(2^16) Leopard FEC + AES-GCM over UDP, QUIC control plane) with Opus audio,
|
|
microphone passthrough, and full gamepad support including DualSense touchpad,
|
|
motion, adaptive triggers and lightbar through SDL3.
|
|
.
|
|
The host creates a virtual output at exactly this client's resolution and
|
|
refresh rate — no scaling. See the punktfunk-host package for the host side.
|
|
EOF
|
|
|
|
cat > "$STAGE/DEBIAN/postinst" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
if [ "$1" = "configure" ]; then
|
|
# The (empty) opt-in group for one-tap client updates — nobody is auto-added. The host
|
|
# package's postinst creates the same group; addgroup is idempotent, so whichever is
|
|
# configured first wins and the other is a no-op.
|
|
getent group punktfunk-update >/dev/null 2>&1 || addgroup --system punktfunk-update 2>/dev/null || true
|
|
# Pick up the DualSense hidraw rule without a reboot (best-effort, no-op in containers).
|
|
udevadm control --reload-rules 2>/dev/null || true
|
|
udevadm trigger --subsystem-match=hidraw 2>/dev/null || true
|
|
update-desktop-database /usr/share/applications 2>/dev/null || true
|
|
# Apply the UDP recv-buffer tuning now (also auto-applied at boot by systemd-sysctl).
|
|
sysctl -p /usr/lib/sysctl.d/99-punktfunk-client-net.conf >/dev/null 2>&1 || true
|
|
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: $SHDEPS"
|
|
dpkg-deb -I "$OUT" | sed -n 's/^/ /p' | grep -E 'Version|Installed-Size' || true
|