ci / bun-nix (pull_request) Successful in 32s
ci / docs-site (pull_request) Successful in 1m21s
ci / web (pull_request) Successful in 1m37s
apple / swift (pull_request) Successful in 1m41s
apple / screenshots (pull_request) Skipped
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m19s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m37s
ci / rust-arm64 (pull_request) Successful in 5m50s
android / android (pull_request) Successful in 7m56s
ci / rust (pull_request) Successful in 15m55s
nix / flake (pull_request) Failing after 16m33s
On a Deck in Gaming Mode the Steam menu and the QAM are driven by the SAME physical controller the client forwards, so opening either one moved the game on the host as well as Steam's UI — a second, invisible player. Steam Input masks a normal game here; it cannot mask us, because masking happens on Steam Input's virtual pad and we deliberately forward the REAL one (28DE:1205 — the virtual pad has no gyro, trackpads or paddles). SDL ships the exact behaviour we want and it is on by default: presses are dropped while the process has windows but no keyboard focus, releases still get through. It CANNOT fire on a Deck. gamescope resolves focus per Xwayland ctx and the client sits alone in its own, so the Steam overlay — which lives in the root ctx — never takes our X focus away and no FocusOut is ever generated. Measured on glass: with the QAM open, X input focus inside the client's ctx stayed on its window for the whole 4 s, while GAMESCOPE_FOCUSED_APP flipped to 769 (Steam) and GAMESCOPE_FOCUSED_APP_GFX stayed on the app. So the signal is explicit. `overlay_focus` watches those two atoms on the gamescope root ctx — which is NOT our own $DISPLAY under `--xwayland-count 2`, hence the socket-directory walk and the flatpak filesystem line — and the presenter ORs it with window focus into one `set_masked`. Masking is deliberately not `set_forwarding`: that closes the slot and sends GamepadRemove, so the game would see a controller UNPLUG every time somebody opened the QAM. This keeps every slot open and only stops the transitions, after flushing what the host believes is held so a stick deflected at overlay-open stops steering instead of freezing at its last value. On the way back, held buttons are adopted rather than replayed — the A that picked a QAM row must not fire in the game as it closes — while axes are re-sent, since a stick has no press to ghost and SDL only speaks on change. Fails open throughout: no gamescope, no X, or an unreadable signal all leave forwarding exactly as it was. `PUNKTFUNK_OVERLAY_MASK=0` opts out.
199 lines
12 KiB
TOML
199 lines
12 KiB
TOML
[package]
|
|
name = "pf-client-core"
|
|
description = "Shared client plumbing (Linux + Windows) — session pump, native video decode, PipeWire/WASAPI audio, SDL3 gamepads, trust store, discovery — extracted from the GTK client so the shells and the Vulkan session binary build on one implementation"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
license.workspace = true
|
|
authors.workspace = true
|
|
repository.workspace = true
|
|
|
|
# Linux + Windows: the Vulkan session client builds on both; `cargo build --workspace`
|
|
# stays green on macOS (the Mac client lives in clients/apple) — there this crate is
|
|
# `wol` plus stubs-free emptiness. `wol` is pure std and stays cross-platform, matching
|
|
# the old main.rs. Audio is the one per-OS swap: PipeWire on Linux, WASAPI on Windows
|
|
# (same public surface — see lib.rs).
|
|
[target.'cfg(any(target_os = "linux", windows))'.dependencies]
|
|
punktfunk-core = { path = "../punktfunk-core", features = ["quic"] }
|
|
# Native Vulkan Video decode (WP-C of the native-decode program, HEVC added by M3
|
|
# WP-2, AV1 by M7): auto's TOP rung on both desktop OSes since M9 — for every codec it
|
|
# speaks, AV1 included — also pinnable via `PUNKTFUNK_DECODER=native-vulkan` —
|
|
# video_vk_native.rs, running pf-vkdecode's VkH264Decoder/VkH265Decoder/VkAv1Decoder on
|
|
# the presenter's shared device.
|
|
pf-vkdecode = { path = "../pf-vkdecode" }
|
|
# The one bitstream parser (M1): the SOFTWARE rung reads its per-picture colour
|
|
# signalling, IDR flag and recovery-point SEI from the same `AuPlan` every hardware rung
|
|
# already submits from (`video_software.rs`). That is what makes the swscale BT.601
|
|
# default unrepresentable rather than merely fixed — there is no second colour source
|
|
# left to disagree with.
|
|
pf-bitstream = { path = "../pf-bitstream" }
|
|
async-channel = "2"
|
|
|
|
# M8's software rung, the ladder's last one — no FFmpeg in either half.
|
|
#
|
|
# H.264: openh264 (BSD-2), already a workspace dependency (the HOST's GPU-less encoder,
|
|
# `pf-encode/src/enc/sw.rs`), so the licence posture and the bundled-source build are
|
|
# both already settled and already compiled by every `--workspace` leg.
|
|
#
|
|
# AV1: rav1d (BSD-2) — dav1d itself, ported to Rust by the ISRG/Prossimo memory-safety
|
|
# project. The plan of record names "dav1d"; the `dav1d` crate reaches it through
|
|
# `dav1d-sys`, which is `system-deps`-only (no vendored build): it needs `dav1d.pc` +
|
|
# headers at build time and `libdav1d.so`/`dav1d.dll` at run time on EVERY client
|
|
# package. That is a new system codec dependency added by the milestone family whose
|
|
# §6 excision checklist exists to delete exactly those. rav1d is the same decoder with
|
|
# none of that: pure Rust, no linker, nothing new in any package.
|
|
#
|
|
# ⚠ Both of these are NEW COMPILE COST on the client packaging legs, which is easy to
|
|
# miss because the workspace already built openh264: every client leg is `-p`-scoped and
|
|
# excludes pf-encode (flatpak's `cargo build -p punktfunk-client-linux -p
|
|
# punktfunk-client-session -p punktfunk-cli`, windows.yml/windows-msix.yml's
|
|
# `-p punktfunk-client-windows …`, deb.yml's client job, packaging/nix's
|
|
# `punktfunk-client`), so all of them compile the bundled OpenH264 tree for the FIRST
|
|
# time here. Only the `--workspace` CI legs and the host packages built it before.
|
|
#
|
|
# `default-features = false` drops two things deliberately:
|
|
# * `asm` — rav1d's hand-written assembly needs `nasm` at build time, and it is NOT the
|
|
# same trade openh264 makes next to it: openh264-sys2's `try_compile_nasm` returns
|
|
# quietly when nasm is missing ("Failed to compile NASM files, not using any
|
|
# assembly") and the C build still succeeds, whereas rav1d's build.rs PANICS ("NASM
|
|
# build failed. Make sure you have nasm installed or disable the \"asm\" feature").
|
|
# So turning `asm` on makes nasm a hard build requirement of every client package,
|
|
# and `ci/rust-ci.Dockerfile` — the container the client .deb and the workspace CI
|
|
# build in — does not have it (arch, rpm, nix and the FFmpeg-building noble image
|
|
# all do; the flatpak GNOME SDK and the Windows runner are not provisioned by
|
|
# anything in this tree). Making the rung that only ever runs BECAUSE the GPU
|
|
# already failed a build-breaker for the legs that ship it is the wrong way round.
|
|
# Turn it back on the day every client leg provisions nasm — and expect a large
|
|
# speedup when you do; this is dav1d's asm, and the Rust fallbacks are much slower.
|
|
# * `bitdepth_16` — the CPU rung is 8-bit by contract (`video_software.rs` refuses
|
|
# anything else rather than mis-scaling it), so building the 10/12-bit half would be
|
|
# compiling a path the code refuses to take.
|
|
#
|
|
# One packaging risk this DOESN'T carry: rav1d exports dav1d's C ABI as `#[no_mangle]`
|
|
# symbols (`dav1d_open`, `dav1d_send_data`, …), which could in principle interpose on a
|
|
# real libdav1d loaded into the same process. It cannot here — these are Rust `staticlib`
|
|
# symbols in an executable with no `-rdynamic` and no dynamic export table entry, so the
|
|
# loader never offers them to anyone. That changes if pf-client-core ever becomes a
|
|
# `cdylib` or a leg adds `-rdynamic`/`--export-dynamic`; re-check it then.
|
|
openh264 = "0.9"
|
|
rav1d = { version = "1", default-features = false, features = ["bitdepth_8"] }
|
|
# errno names for rav1d's negated-`c_int` returns (`video_software.rs`): `ENOPROTOOPT` —
|
|
# the code a `bitdepth_8`-only build answers a 10-bit stream with — is 92 on Linux and
|
|
# 123 on Windows, and rav1d re-exports only `Dav1dResult`, so the typed enum that would
|
|
# otherwise name it is out of reach. Already in the tree (rav1d's own dependency).
|
|
libc = "0.2"
|
|
|
|
# Opus for the audio planes. The VIDEO side has no FFmpeg at all since M10: every decode
|
|
# rung is native (pf-vkdecode / pf-dxvadec / pf-vaadec / openh264+rav1d) and the codec
|
|
# vocabulary is `punktfunk_core::quic`'s own `CODEC_*` wire bits. The HOST still encodes
|
|
# with libavcodec (`pf-encode`); nothing in this crate does.
|
|
opus = "0.3"
|
|
|
|
mdns-sd = "0.20"
|
|
|
|
# PyroWave decode (the opt-in wired-LAN wavelet codec, design/pyrowave-codec-plan.md
|
|
# §4.5) — pure Vulkan compute on the presenter's shared device, so it builds wherever the
|
|
# spawned Vulkan session presenter runs: Linux AND Windows (pyrowave-sys covers both; it
|
|
# is an empty stub elsewhere). `ash` only wraps the presenter's existing raw handles
|
|
# (same pinned version as pf-presenter).
|
|
pyrowave-sys = { path = "../pyrowave-sys", optional = true }
|
|
ash = { version = "0.38", optional = true }
|
|
# Game-library fetch from the host's management API over mTLS + fingerprint pinning.
|
|
# `ureq` is small + sync (the host uses it too) and its rustls unifies with the
|
|
# workspace's (quinn's) 0.23; the pinning verifier mirrors core's private `PinVerify`.
|
|
ureq = "2"
|
|
# Signed update-manifest fetch/verify + the install-kind ladder, shared with the host so one
|
|
# trust rule serves both (crates/pf-update-check).
|
|
pf-update-check = { path = "../pf-update-check" }
|
|
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
anyhow = "1"
|
|
tracing = "0.1"
|
|
# Stable ids for profiles and host records (profiles.rs) — the OS RNG only, same version the
|
|
# workspace already resolves for punktfunk-core. No uuid crate: the v4 layout is four lines.
|
|
rand = "0.9"
|
|
|
|
# Gamepads: capture + feedback (full DualSense fidelity — touchpad/motion/triggers/LEDs
|
|
# need the hidapi driver). Linux links the system SDL3; Windows builds it from source
|
|
# (no system SDL3 there — same choice as clients/windows).
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
# `v0_3_49` for `Buffer::requested` — the graph's per-cycle frame ask, without which the
|
|
# playback callback can only size writes from the buffer CEILING (quantum-limit, ~170 ms).
|
|
# Pure cfg gate; needs libpipewire ≥ 0.3.49 (2022-03) at runtime, which every ship target
|
|
# (SteamOS, flatpak runtimes, Arch, Ubuntu ≥ 22.10) clears.
|
|
pipewire = { version = "0.9", features = ["v0_3_49"] }
|
|
sdl3 = { version = "0.18", features = ["hidapi"] }
|
|
# Native VAAPI decode (M6 of the native-decode program): the hand-declared libva buffer
|
|
# layouts, the profile/format/surface decisions, the AuPlan → picparams/IQ/slice
|
|
# conversion and the DRM-PRIME export descriptor that `video_vaapi_native` marshals.
|
|
# Cross-platform on purpose — everything decidable without a device is tested by the
|
|
# ordinary macOS and container gates, exactly as pf-dxvadec does for Windows.
|
|
pf-vaadec = { path = "../pf-vaadec" }
|
|
# libva itself is dlopen'd, never linked (see `video_vaapi_native`'s module docs): the
|
|
# container can then compile and clippy the whole rung without `libva-dev`, and a machine
|
|
# without a VAAPI runtime gets a clean refusal instead of a packaging dependency.
|
|
libloading = "0.8"
|
|
# The gamescope overlay watcher (`overlay_focus`): read two CARDINAL properties off a
|
|
# gamescope root window and block on PropertyNotify. `default-features = false` keeps the
|
|
# pure-Rust `RustConnection` — no libxcb link, so no new C dependency on any client package
|
|
# — the same stance pf-capture and pf-vdisplay already take on this crate. No extension
|
|
# features: root-window properties and an event mask are core X11.
|
|
x11rb = { version = "0.13", default-features = false }
|
|
|
|
[target.'cfg(windows)'.dependencies]
|
|
wasapi = "0.23"
|
|
# Native D3D11VA decode (M5 of the native-decode program): the hand-declared DXVA buffer
|
|
# layouts and the AuPlan → picparams/qmatrix/slice-control conversion that video_d3d11_native
|
|
# submits. Windows-only because the rung is; the crate itself is cross-platform CPU code so
|
|
# its tests run on every CI leg (which is the point — `cfg(windows)` code cannot be tested by
|
|
# the Linux or macOS gates at all).
|
|
pf-dxvadec = { path = "../pf-dxvadec" }
|
|
# Pad-audio correlation (pad_audio.rs): the HID devnode's ContainerID and a render endpoint's
|
|
# stamped PKEY_Device_ContainerId both live in the registry — read-only, which sidesteps COM
|
|
# property stores entirely (the same version the host pins).
|
|
winreg = "0.56"
|
|
sdl3 = { version = "0.18", features = ["hidapi", "build-from-source"] }
|
|
# D3D11 decode-device plumbing (video_d3d11.rs): device/adapter selection, DXVA probes, and
|
|
# the shared NT-handle hand-off ring `video_d3d11_native` fills. Same pinned rev as
|
|
# clients/windows so the workspace builds ONE windows-rs.
|
|
windows = { git = "https://github.com/microsoft/windows-rs", rev = "acb5a1a7441033d9312b16842af02eb0c2b403dc", features = [
|
|
# Features are header-named since windows-rs generates from the SDK headers directly
|
|
# (#4689) — one feature per header, replacing the old `Win32_*` namespace features.
|
|
"d3d11",
|
|
"d3dcommon",
|
|
"dxgi",
|
|
"handleapi",
|
|
# RECT/HMONITOR for DXGI_OUTPUT_DESC1 (the display-HDR volume query).
|
|
"windef",
|
|
# IDXGIResource1::CreateSharedHandle takes an optional SECURITY_ATTRIBUTES.
|
|
"minwinbase",
|
|
# The GlobalAlloc block the clipboard takes ownership of (clipboard.rs).
|
|
"winbase",
|
|
# The OS-clipboard bridge (clipboard.rs): Open/Get/SetClipboardData + the sequence
|
|
# number — all in winuser now.
|
|
"winuser",
|
|
] }
|
|
|
|
[target.'cfg(any(target_os = "linux", windows))'.dev-dependencies]
|
|
# The two platform native rungs' frame-hash parity tests compare decoded surfaces against
|
|
# the libavcodec goldens M5 captured — the same SHA-256 list, and the same crate,
|
|
# pf-vkdecode's Vulkan parity legs use (already in the workspace lock). The goldens are
|
|
# checked-in hashes; nothing links FFmpeg to read them.
|
|
#
|
|
# Windows was the only platform here until the VAAPI rung grew a readback: `cfg(windows)`
|
|
# for `video_d3d11_native::parity`, now `cfg(linux)` as well for
|
|
# `video_vaapi_native::parity`. A DEV dependency, so no shipped binary gains anything —
|
|
# which is also part of why the VAAPI readback cannot reach the production video path.
|
|
sha2 = "0.10"
|
|
|
|
[features]
|
|
# PyroWave client decode ships in every default build (flatpak included; pyrowave-sys is a
|
|
# vendored in-repo tree, offline-safe, and an empty stub off Linux/Windows). The codec is
|
|
# still strictly per-session opt-in (Settings codec pick / PUNKTFUNK_PREFER_PYROWAVE=1).
|
|
default = ["pyrowave"]
|
|
pyrowave = ["dep:pyrowave-sys", "dep:ash"]
|
|
|
|
[lints]
|
|
workspace = true
|