Files
punktfunk/crates/punktfunk-host/Cargo.toml
T
enricobuehler 4cf6c33ea4 Merge origin/main (shared clipboard Phase 1) into the local W6.2 + rumble-D line
Reconciles the two diverged mains: the local stack (W6.1/W6.2 crate decomposition,
lid-closed fixes, ABR climb gating, rumble root-fix A-D incl. the W7 core module
splits) x origin/main (clipboard wire ABI v8 + pf-clipboard + the macOS client).

Textual conflicts were trivial (host Cargo.toml dep union; Cargo.lock + the
cbindgen header regenerate). The real work was structural: the local line's W7
splits dissolved the two files the clipboard's Phase 0 landed in, so the merge
left both layouts side by side. Resolved by re-homing the clipboard delta into
the split layout and deleting the stale flat files:

- quic/msgs.rs -> quic/clip.rs (the 0x40-0x44 messages, CLIP_* vocabulary,
  codecs) + HOST_CAP_CLIPBOARD into quic/caps.rs beside GAMEPAD_STATE; mod.rs
  declares clip and fixes the split-by-concern doc.
- client.rs -> the client/ split: CtrlRequest clip variants + Negotiated.host_caps
  (control.rs), CLIP_EVENT_QUEUE (planes.rs), clip channel ends (worker.rs),
  NativeClient clip surface + channels (mod.rs), control-task encode/decode arms +
  the clipboard task spawn (pump.rs).

Verified on macOS: core 174/174 (merged rumble + clipboard suites), pf-clipboard
clippy -D warnings clean, fmt applied, header regenerated. Host-side compile
rides the Linux/Windows legs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:19:56 +02:00

296 lines
17 KiB
TOML

[package]
name = "punktfunk-host"
description = "punktfunk Linux streaming host: virtual display, capture, encode, input injection"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
punktfunk-core = { path = "../punktfunk-core", features = ["quic"] }
# Config-dir + owner-private file helpers (moved out of the gamestream junk drawer, plan §W6).
pf-paths = { path = "../pf-paths" }
# Process-wide host config global, extracted to a leaf crate (plan §W6).
pf-host-config = { path = "../pf-host-config" }
# GPU vendor/adapter detection + selection, extracted to a leaf crate (plan §W6).
pf-gpu = { path = "../pf-gpu" }
# Linux GPU zero-copy plumbing (CUDA/EGL/Vulkan dmabuf import + the isolated worker), extracted
# to a leaf crate (plan §W6). Compiles empty on non-Linux, so it lives in the main deps.
pf-zerocopy = { path = "../pf-zerocopy" }
# Shared frame/format vocabulary (CapturedFrame/PixelFormat/…), HDR metadata, thread QoS, and the
# Windows DXGI capture identity — the leaf both capture and encode speak (plan §W6).
pf-frame = { path = "../pf-frame" }
# Windows display-topology helpers (CCD/GDI mode-set, PnP monitor devnodes, display-change watch),
# extracted to a leaf crate (plan §W6). Empty on non-Windows, so it lives in the main deps.
pf-win-display = { path = "../pf-win-display" }
# Video encode backends (NVENC/VAAPI/AMF/QSV/Vulkan-Video/PyroWave/openh264) behind one Encoder
# trait, extracted to a subsystem crate (plan §W6). The host's nvenc/amf-qsv/vulkan-encode/pyrowave
# features forward here (see [features]); the heavy encoder deps (ffmpeg-next, the NVENC SDK,
# openh264, pyrowave-sys) moved with it.
pf-encode = { path = "../pf-encode" }
# Frame capture backends (Linux PipeWire portal + Windows IDD direct-push) behind one Capturer
# trait, extracted to a subsystem crate (plan §W6). The facade (src/capture.rs) hands it the
# pre-resolved encode facts + the sealed-channel delivery closure so the edge stays one-way.
pf-capture = { path = "../pf-capture" }
# Input injection backends (per-OS injectors + the virtual-gamepad HID stack), extracted to a
# subsystem crate (plan §W6). Consumes core::input; the heavy input deps (wayland/reis/xkbcommon/
# usbip) moved with it.
pf-inject = { path = "../pf-inject" }
# Virtual-display orchestration (per-compositor Linux backends + the Windows IddCx driver backend),
# extracted to a subsystem crate (plan §W6). The DDC panel control + KWin zkde protocol moved with it.
pf-vdisplay = { path = "../pf-vdisplay" }
# Shared clipboard (design/clipboard-and-file-transfer.md §4): the per-OS session-clipboard
# backends + coordinator. Portable dep — the crate's facade (policy / ClipCoordCmd / start)
# compiles everywhere; the backends are cfg-gated inside it.
pf-clipboard = { path = "../pf-clipboard" }
# M3 native control plane (the `punktfunk/1` QUIC handshake; data plane stays native-thread UDP).
quinn = "0.11"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# The log ring (log_capture.rs) normalizes `log`-crate events off the bridge's "log" shim target
# back to the real module path, so the console's target column and the ring's noise gate see
# `mdns_sd::…` instead of "log".
tracing-log = "0.2"
axum = "0.8"
mdns-sd = "0.20"
# Wake-on-LAN: report the host's wake-capable NIC MAC(s) to clients via the mDNS `mac` TXT record.
# `mac_address` reads a NIC's hardware address; `if-addrs` maps the routed IP to its interface name.
mac_address = "1"
if-addrs = "0.13"
tokio = { version = "1", features = ["full"] }
parking_lot = "0.12"
rsa = "0.9"
sha2 = { version = "0.10", features = ["oid"] }
aes = "0.8"
aes-gcm = "0.10"
cbc = { version = "0.1", features = ["alloc"] }
rand = "0.8"
hex = "0.4"
# Cover-art delivery in the game library: encode Lutris's local JPEGs into `data:` URLs and decode
# the Epic launcher's base64 `catcache.bin`. Cross-platform (Linux Lutris art + Windows Epic art).
base64 = "0.22"
# Blocking HTTP for the library cover-art warmer (no-auth GOG api.gog.com + Xbox displaycatalog),
# run on a background thread off the hot path. `ureq` is small + sync (no tokio here) and bundles
# webpki roots (no system cert dependency). Cross-platform so the fetch/parse code is compiled +
# checked everywhere even though only the Windows GOG/Xbox providers need it today.
ureq = "2"
rcgen = { version = "0.13", default-features = false, features = ["aws_lc_rs", "pem"] }
x509-parser = "0.16"
# Only used for the plain-HTTP nvhttp listener (`bind().serve()`); HTTPS/mTLS is hand-rolled over
# tokio-rustls (axum-server can't surface the peer cert), so we do NOT enable `tls-rustls` — that
# feature is what pulled the unmaintained `rustls-pemfile` (security-review dep hygiene).
axum-server = "0.8"
rustls = "0.23"
# Manual HTTPS+mTLS serve loop for the mgmt API (axum-server can't surface the peer cert): a
# tokio-rustls handshake exposes the client cert, then hyper serves the axum Router with the
# verified fingerprint injected as a request extension. Versions match the workspace lock.
tokio-rustls = "0.26"
hyper = { version = "1", features = ["server", "http1", "http2"] }
hyper-util = { version = "0.1", features = ["server", "server-auto", "tokio", "service"] }
tower = { version = "0.5", features = ["util"] }
# Stream combinators for the mgmt API's SSE event feed (`GET /api/v1/events`) — already in the
# tree transitively (axum/hyper) and as a Linux target dep; control plane only.
futures-util = "0.3"
# Webhook signing (X-Punktfunk-Signature: sha256=<hex HMAC>) for operator hooks; pairs with
# the existing sha2. Already in the lockfile transitively.
hmac = "0.12"
rusty_enet = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Management/control-plane REST API + OpenAPI (control pane, M2). `axum_extras` wires
# utoipa into axum 0.8 extractors; utoipa-axum collects `#[utoipa::path]` routes into the
# spec; utoipa-scalar serves the interactive docs. Codegen-friendly: the spec is emitted
# verbatim by the `openapi` subcommand. Control plane only — never the per-frame path.
utoipa = { version = "5", features = ["axum_extras"] }
utoipa-axum = "0.2"
utoipa-scalar = { version = "0.3", features = ["axum"] }
[dev-dependencies]
# Drive the management API router in-process (no socket) in the handler tests.
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"
# Disposable directory fixtures for the Steam local-librarycache scan tests (library.rs).
tempfile = "3"
# Emit `log`-crate records through the tracing-log bridge in the log_capture tests.
log = "0.4"
# Opus encode for the host->client audio plane — stereo (`opus::Encoder`) AND 5.1/7.1 surround
# (`opus::MSEncoder`, the safe multistream API the crate exposes; no `audiopus_sys` needed). The
# crate vendors libopus (cmake-built from source — no system lib, no vcpkg), so it builds on Windows
# MSVC too (needs CMake + NASM, both on the box). Both platforms that have an audio-capture backend.
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
opus = "0.3"
[target.'cfg(target_os = "linux")'.dependencies]
# `screencast` gates the ScreenCast portal module; `remote_desktop` adds the RemoteDesktop
# portal we use for libei input on KWin/GNOME; `tokio` is the default runtime.
# `open_pipe_wire_remote` is unconditional, so ashpd's own `pipewire` feature is not
# needed — we drive PipeWire with the `pipewire` crate below.
ashpd = { version = "0.13", features = ["screencast", "remote_desktop"] }
libc = "0.2"
# Must match the pipewire crate ashpd 0.13 links (libspa/pipewire-sys `links` key is
# unique per build), i.e. 0.9 — NOT the 0.10 the setup doc mentions.
pipewire = "0.9"
# ashpd 0.13 uses the tokio runtime; a current-thread runtime drives the one-time
# portal handshake (control plane — never the per-frame path).
tokio = { version = "1", features = ["rt", "rt-multi-thread", "net", "time"] }
# Input injection into headless Sway via the wlroots virtual-input Wayland protocols
# (uinput won't reach a compositor running with WLR_LIBINPUT_NO_DEVICES=1).
wayland-client = "0.31"
wayland-protocols-wlr = { version = "0.3", features = ["client"] }
wayland-protocols-misc = { version = "0.3", features = ["client"] }
# `xdg-output` (zxdg_output_v1): the per-output *logical* geometry (post-scale size + global
# position), used by the KWin fake_input backend to map absolute coordinates under display scaling.
wayland-protocols = { version = "0.32", features = ["client"] }
# Codegen for KDE's `zkde_screencast_unstable_v1` (vendored in `protocols/`): create a KWin
# virtual output sized to the client's resolution and get its PipeWire node (KRdp's path).
# `wayland-backend` is referenced by the generated interface tables.
wayland-scanner = "0.31"
wayland-backend = "0.3"
# Parse `pw-dump` JSON to find gamescope's PipeWire node (gamescope backend).
serde_json = "1"
# Read the Lutris library DB (`pga.db`) for the Lutris store provider. `bundled` vendors + compiles
# SQLite (cc, already needed for ffmpeg/opus) so there's no system libsqlite3 runtime dependency —
# clean for the deb/rpm/flatpak packaging. Opened read-only/immutable (Lutris may hold it open).
rusqlite = { version = "0.40", features = ["bundled"] }
# Builds/validates the xkb keymap uploaded to the virtual keyboard + tracks modifier state.
xkbcommon = "0.8"
# libei (EI sender) for the portable input path on KWin/GNOME (RemoteDesktop portal).
# The `tokio` feature wires reis's event stream into tokio's reactor.
reis = { version = "0.6.1", features = ["tokio"] }
# `StreamExt::next` on reis's tokio event stream in the libei worker loop.
futures-util = "0.3"
# Zero-copy capture (plan §9): EGL imports the PipeWire dmabuf, CUDA maps it, NVENC encodes
# it with no CPU roundtrip. `khronos-egl` (dynamic = load the NVIDIA libEGL at runtime) gives
# eglCreateImage + the dma_buf import; the CUDA driver API (EGL interop) and libgbm are linked
# via hand-rolled FFI in `src/zerocopy/` (no Rust crate exposes the EGL-interop driver calls).
khronos-egl = { version = "6", features = ["dynamic"] }
# Vulkan bridge for LINEAR dmabufs (gamescope): import via VK_EXT_external_memory_dma_buf,
# GPU-copy into an exportable allocation, export OPAQUE_FD → cuImportExternalMemory (the
# officially-supported CUDA pairing; raw dmabuf fds are rejected by the desktop driver).
ash = "0.38"
# `libcuda.so.1` is dlopen'd at runtime (NOT link-time) so one Linux binary runs on NVIDIA
# (zero-copy via CUDA) AND on AMD/Intel (VAAPI, no NVIDIA driver present) — see `zerocopy::cuda`.
libloading = "0.8"
# Vendored + trimmed `usbip` server core (no libusb) — presents a virtual Steam Deck over USB/IP
# so the local `vhci_hcd` attaches it: the shippable, Secure-Boot-clean, Steam-Input-promotable
# virtual-Deck transport on non-SteamOS hosts (`inject/linux/steam_usbip.rs`). See the crate's NOTICE.
usbip-sim = { path = "vendor/usbip-sim" }
[target.'cfg(target_os = "windows")'.dependencies]
# Windows host backends. `windows` covers the Win32/CCD APIs the SudoVDA virtual-display backend
# drives (SetupAPI device enumeration, DeviceIoControl IOCTLs, QueryDisplayConfig name resolution);
# capture/encode/input/audio backends extend the feature set as they land.
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Security",
# ConvertStringSecurityDescriptorToSecurityDescriptorW — the SDDL on the virtual-DualSense
# shared-memory section (inject/dualsense_windows.rs) so the UMDF host can open it.
"Win32_Security_Authorization",
"Win32_Devices_DeviceAndDriverInstallation",
# SwDeviceCreate/SwDeviceClose — the per-session virtual-DualSense devnode
# (inject/dualsense_windows.rs).
"Win32_Devices_Enumeration_Pnp",
"Win32_Devices_Display",
"Win32_Storage_FileSystem",
"Win32_System_IO",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
"Win32_System_StationsAndDesktops",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_Gdi",
# Windows.Graphics.Capture (WGC) backend — composed-desktop capture (overlay/MPO-correct HDR).
"Foundation",
"Graphics",
"Graphics_Capture",
"Graphics_DirectX",
"Graphics_DirectX_Direct3D11",
"Win32_System_WinRT_Direct3D11",
"Win32_System_WinRT_Graphics_Capture",
# WGC runs under SYSTEM via interactive-user impersonation (WGC won't activate as SYSTEM).
"Win32_System_RemoteDesktop",
# Two-process secure-desktop design: the SYSTEM host spawns the WGC helper in the interactive
# user session (CreateProcessAsUserW) with stdout/stdin redirected to anonymous pipes.
"Win32_System_Threading",
"Win32_System_Pipes",
"Win32_System_Environment",
# Force-composed-flip overlay: a topmost layered window on the Winlogon desktop disqualifies the
# secure desktop's fullscreen independent-flip so Desktop Duplication can capture it.
"Win32_System_LibraryLoader",
# VirtualProtect — for the inline patch of the win32u GPU-preference shim (Apollo's MinHook port:
# the hybrid-GPU output-reparenting hook that keeps Desktop Duplication stable on a 4090+iGPU box).
# See capture/windows/dxgi.rs `install_gpu_pref_hook`. No trampoline (we fully replace the fn) → no detour
# crate / no C length-disassembler dep; a 12-byte absolute-jmp prologue patch suffices.
"Win32_System_Memory",
# Per-monitor-v2 DPI awareness — IDXGIOutput5::DuplicateOutput1 (the modern capture path Apollo
# uses; FP16/format-list, robust to overlay/format churn) requires the process to be DPI-aware.
"Win32_UI_HiDpi",
# Windows service supervisor (src/service.rs): a kill-on-close job object so a service crash never
# orphans the SYSTEM host it launched into the interactive session.
"Win32_System_JobObjects",
# CoCreateInstance(PolicyConfigClient) — set the default audio playback/recording endpoints via the
# undocumented IPolicyConfig (audio/windows/audio_control.rs) so mic + desktop audio auto-wire.
"Win32_System_Com",
# SetUnhandledExceptionFilter + EXCEPTION_POINTERS — the last-resort native-crash logger
# (src/windows/crash.rs); Kernel gates the CONTEXT type EXCEPTION_POINTERS embeds.
"Win32_System_Diagnostics_Debug",
"Win32_System_Kernel",
# CreateToolhelp32Snapshot/Process32*W — the conflicting-streaming-host process scan
# (src/detect/windows.rs): is Sunshine/Apollo/... running alongside us?
"Win32_System_Diagnostics_ToolHelp",
] }
# The SCM plumbing for the `service` subcommand (define_windows_service! / dispatcher / control
# handler / ServiceManager install). Wraps the Win32 service API; the supervision loop itself uses
# the `windows` crate above.
windows-service = "0.7"
# Read the GOG.com install registry (HKLM\SOFTWARE\WOW6432Node\GOG.com\Games) for the GOG store
# provider — ergonomic + correct-by-construction vs. hand-rolled Reg* FFI for subkey enumeration.
winreg = "0.56"
# Parse each Xbox/Game-Pass game's MicrosoftGame.config (GDK manifest XML) for the Xbox store
# provider — a small read-only DOM is all we need (Identity/Executable/ShellVisuals/StoreId).
roxmltree = "0.21"
# WASAPI loopback audio capture (default render endpoint -> 48 kHz stereo f32 for the Opus path).
wasapi = "0.23"
# Shared host<->driver wire contract for the pf-vdisplay IddCx virtual-display backend
# (vdisplay/pf_vdisplay.rs): the control-plane IOCTL codes + `#[repr(C)] Pod` request/reply structs,
# defined ONCE so host<->driver ABI drift is a compile error. `bytemuck` serializes those structs
# to/from the DeviceIoControl byte buffers.
pf-driver-proto = { path = "../pf-driver-proto" }
bytemuck = { version = "1.19", features = ["derive"] }
# The encode feature flags now FORWARD to the pf-encode subsystem crate (the heavy encoder deps —
# ffmpeg-next, the NVENC SDK, openh264, pyrowave-sys — moved there, plan §W6). Selecting a feature
# on the host turns on the matching backend inside pf-encode.
[features]
# PyroWave ships in every default build (the codec stays strictly opt-in per session — a client
# must explicitly prefer CODEC_PYROWAVE; nothing changes for normal HEVC/AV1 sessions).
default = ["pyrowave"]
# NVENC hardware encode (Linux CUDA + Windows D3D11). OFF by default; entry points resolved at
# RUNTIME from the driver DLL/so, so the same binary starts fine on AMD/Intel boxes. Build the GPU
# host with `--features nvenc`.
nvenc = ["pf-encode/nvenc"]
# AMD/Intel hardware encode on Windows (AMF/QSV via ffmpeg-next). OFF by default: needs a `FFMPEG_DIR`
# (BtbN lgpl-shared with `*_amf`/`*_qsv`) at build and bundles the FFmpeg DLLs at runtime. Build the
# all-vendor GPU host with `--features nvenc,amf-qsv`.
amf-qsv = ["pf-encode/amf-qsv"]
# Raw Vulkan Video HEVC/AV1 encode on Linux (AMD/Intel) — real reference-frame-invalidation loss
# recovery via explicit DPB reference slots (design/linux-vulkan-video-encode.md). OFF by default;
# reuses pf-encode's `ash` bindings (no new dep). Runtime-gated further by PUNKTFUNK_VULKAN_ENCODE.
vulkan-encode = ["pf-encode/vulkan-encode"]
# PyroWave — the opt-in wired-LAN intra-only wavelet codec (design/pyrowave-codec-plan.md). Builds
# the vendored codec from source in pf-encode. ON by default (see `default`); sessions reach it only
# through explicit client opt-in.
pyrowave = ["pf-encode/pyrowave"]
# Build-time icon/version-info embedding (build.rs; Windows dev/CI hosts only — Linux packaging
# builds of this crate never execute the winresource block).
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"