refactor(host/W6.1): extract the config() global into the pf-host-config leaf crate
Third de-coupling for the host crate carve (plan §W6.1 leaf). HostConfig + the config() OnceLock (config.rs, pure std, zero deps) move to a new pf-host-config leaf so every subsystem crate (pf-encode/pf-capture/pf-vdisplay/pf-gpu) can read process config WITHOUT depending on the orchestrator. 34 crate::config::config() call sites across 19 files repoint to pf_host_config::config(). thread_qos stays in the host for now (it calls session_tuning::on_hot_thread — its own leaf-ification rides the encode carve). Granular-crate decision (supersedes the plan's single pf-media): split capture/encode/ vdisplay into separate crates rather than one broad crate — the capture↔encode cycle is broken by a shared frame-types leaf, and vdisplay→encode (can_open_another_session) is a legal one-way edge since encode never references vdisplay. Verified: Linux (home-worker-5) clippy -p pf-host-config -p punktfunk-host --all-targets -D warnings; Windows (192.168.1.158) clippy --features nvenc,amf-qsv --all-targets green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1626,7 +1626,7 @@ mod pipewire {
|
||||
// advertisement with every modifier its device samples from, so compositors that
|
||||
// never allocate LINEAR (Mutter+NVIDIA) still negotiate zero-copy dmabufs.
|
||||
#[cfg(feature = "pyrowave")]
|
||||
if vaapi_passthrough && crate::config::config().encoder_pref.as_str() == "pyrowave" {
|
||||
if vaapi_passthrough && pf_host_config::config().encoder_pref.as_str() == "pyrowave" {
|
||||
for m in crate::encode::pyrowave_capture_modifiers(
|
||||
crate::zerocopy::drm_fourcc(PixelFormat::Bgrx).unwrap(),
|
||||
) {
|
||||
|
||||
@@ -1522,7 +1522,7 @@ impl Capturer for IddPushCapturer {
|
||||
// NVENC encodes N on the ASIC. We hand a rotating `OUT_RING` of output textures, so this is safe.
|
||||
// `PUNKTFUNK_IDD_DEPTH` overrides (1 disables pipelining; clamp to ≤ OUT_RING so a frame in flight
|
||||
// always has its own texture).
|
||||
crate::config::config().idd_depth.clamp(1, OUT_RING)
|
||||
pf_host_config::config().idd_depth.clamp(1, OUT_RING)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
//! `HostConfig` — the host's runtime knobs parsed ONCE from the environment, instead of the ~68 scattered
|
||||
//! `env::var` reads recomputed at every call site (some up to 8×, which lets capture + encode silently
|
||||
//! disagree on the resolved backend — plan §2.4). The service / launcher loads `host.env` into the process
|
||||
//! environment before the host starts, and **for the knobs captured here the environment is constant for the
|
||||
//! process lifetime**, so a lazily-parsed global is equivalent to "parsed once at startup".
|
||||
//!
|
||||
//! **Goal-1 stages 1–2** (`design/windows-host-rewrite.md` §2.2): stage 1 stood this up; stage 2 migrated the
|
||||
//! genuinely-constant operator/dispatch knobs onto it (the dispatch-disagreement bug class:
|
||||
//! `encoder_pref`, `render_adapter`, the vdisplay backend select — plus the plan-named
|
||||
//! `idd_depth`/`zerocopy`/`ten_bit`/`four_four_four` and the multi-site `perf`/`compositor`/
|
||||
//! `video_source`/`gamepad`). `SessionPlan` (stage 3) consumes it as the single owner of the
|
||||
//! capture/topology/encoder decision.
|
||||
//!
|
||||
//! **What is deliberately NOT here (and must stay a live `env::var` read):**
|
||||
//! - **Runtime-mutated session vars.** On Linux, [`crate::vdisplay::apply_session_env`] rewrites the process
|
||||
//! env on *every connect* so one host follows a Bazzite box across Gaming↔Desktop: `WAYLAND_DISPLAY`,
|
||||
//! `XDG_CURRENT_DESKTOP`, `XDG_RUNTIME_DIR`, `DBUS_SESSION_BUS_ADDRESS`, and the *derived* `PUNKTFUNK_*`
|
||||
//! vars `INPUT_BACKEND`, `GAMESCOPE_SESSION`/`GAMESCOPE_NODE`, `KWIN_VIRTUAL_PRIMARY`,
|
||||
//! `MUTTER_VIRTUAL_PRIMARY`, `FORCE_SHM` (+ `GAMESCOPE_APP` on the launch path). Parsing these once would
|
||||
//! freeze them at startup and silently break session-following — they are NOT constant.
|
||||
//! - **Single-use local tuning** read exactly where it is used (no resolve-once benefit, and a parse with a
|
||||
//! call-site-local default/clamp): e.g. `FEC_PCT` (two *different* semantics — GameStream default-20 vs
|
||||
//! punktfunk/1 `Option`/clamp-90), `VIDEO_DROP`, `VBV_FRAMES`, `SPLIT_ENCODE`, `PACE_BURST_KB`, the
|
||||
//! `capture/dxgi.rs` timing knobs, the `*_LIVE` test gates.
|
||||
//! - **Path / genuinely-dynamic reads**: the config-dir resolution, `PATH` executable search, the
|
||||
//! env-forward-to-child loop, `PUNKTFUNK_MGMT_TOKEN`, `PUNKTFUNK_HOST_CMD`, `PUNKTFUNK_RENDER_NODE`.
|
||||
//!
|
||||
//! `PUNKTFUNK_ZEROCOPY` note: this field is a **tri-state override** (`None` = unset). Unset defers to
|
||||
//! the per-vendor default in `encode/ffmpeg_win.rs::zerocopy_enabled` (AMF on — on-glass validated
|
||||
//! 2026-07-06; QSV off until validated on Intel glass); an explicit value forces it (`0|false|off|no`
|
||||
//! = off, anything else = on, so the old presence-style `=1` keeps working). The Linux `zerocopy`
|
||||
//! module keeps its own *truthy* parser (`1|true|yes|on`) — the two are independent features that
|
||||
//! share a name; do NOT conflate them.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Resolved host configuration. Holds the genuinely-constant operator/dispatch knobs (see module docs for
|
||||
/// what is deliberately excluded). Fields read on only one platform are kept alive cross-platform by the
|
||||
/// derived `Debug` impl, so the parser can stay a single platform-neutral function.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HostConfig {
|
||||
/// `PUNKTFUNK_ENCODER` — explicit encoder-backend override (lowercased; empty = auto-detect by GPU vendor).
|
||||
pub encoder_pref: String,
|
||||
/// `PUNKTFUNK_RENDER_ADAPTER` — discrete render-GPU pin by description substring (`Some` even when empty:
|
||||
/// the empty string still counts as "set" for the presence checks, and the value reader filters it).
|
||||
pub render_adapter: Option<String>,
|
||||
/// `PUNKTFUNK_IDD_DEPTH` — IDD-push pipeline depth override (default 2; the call site clamps to its `OUT_RING`).
|
||||
pub idd_depth: usize,
|
||||
/// `PUNKTFUNK_ZEROCOPY` — Windows D3D11 zero-copy encode input override. `None` (unset) defers to
|
||||
/// the per-vendor default (AMF on, QSV off — see module docs and `encode/ffmpeg_win.rs`).
|
||||
pub zerocopy: Option<bool>,
|
||||
/// `PUNKTFUNK_10BIT` — host policy gate for 10-bit encode (HEVC Main10 / AV1 10-bit).
|
||||
/// **Default ON** (since 10-bit went probe-gated end-to-end, 2026-07-16): the host merely
|
||||
/// *allows* 10-bit — a session only becomes 10-bit when the client advertised `VIDEO_CAP_10BIT`
|
||||
/// (behind its HDR setting + display-capability gate), the codec supports it (HEVC/AV1), and
|
||||
/// the GPU/backend passed the encode probe (`can_encode_10bit`) — otherwise 8-bit SDR.
|
||||
/// `PUNKTFUNK_10BIT=0`/`false`/`off`/`no` disables. Independent of `four_four_four` (depth vs chroma).
|
||||
pub ten_bit: bool,
|
||||
/// `PUNKTFUNK_444` — host policy gate for full-chroma HEVC 4:4:4 (Range Extensions).
|
||||
/// **Default ON** (since the pipeline went zero-copy + honest end-to-end, 2026-07-10): the
|
||||
/// host merely *allows* 4:4:4 — a session only becomes 4:4:4 when the client explicitly
|
||||
/// advertised it (a client-side setting, default OFF), the codec is HEVC, the capture can
|
||||
/// deliver full chroma, and the GPU/driver passed the encode probe — otherwise 4:2:0.
|
||||
/// `PUNKTFUNK_444=0`/`false`/`off`/`no` disables. Independent of `ten_bit` (chroma vs depth).
|
||||
pub four_four_four: bool,
|
||||
/// `PUNKTFUNK_PERF` — per-stage timing instrumentation.
|
||||
pub perf: bool,
|
||||
/// `PUNKTFUNK_VIDEO_SOURCE` — GameStream video source select (`virtual` / `portal` / unset → synthetic).
|
||||
pub video_source: Option<String>,
|
||||
/// `PUNKTFUNK_COMPOSITOR` — explicit compositor override (operator/CI/test). NOT the runtime-detected
|
||||
/// session — this one is a constant operator knob; `apply_session_env` never writes it.
|
||||
pub compositor: Option<String>,
|
||||
/// `PUNKTFUNK_GAMEPAD` — client/operator virtual-pad backend preference (fed to `pick_gamepad`).
|
||||
pub gamepad: Option<String>,
|
||||
/// `PUNKTFUNK_VDISPLAY` — Windows virtual-display backend. The pf-vdisplay IddCx driver is now the only
|
||||
/// backend (the legacy SudoVDA backend was removed), so this is currently informational — kept for the
|
||||
/// shipped `host.env` and as a forward seam if a second backend is ever added.
|
||||
pub vdisplay: Option<String>,
|
||||
/// `PUNKTFUNK_GAMESCOPE_STEAM` — opt the bare headless gamescope spawn into its Steam
|
||||
/// integration mode (`--steam`). Managed gamescope-session-plus/SteamOS sessions own their
|
||||
/// own flags and do not consult this.
|
||||
pub gamescope_steam: bool,
|
||||
/// `PUNKTFUNK_RECOVER_SESSION_CMD` — operator hook fired (debounced) when a client connects while NO
|
||||
/// graphical session is live for this uid: the state a compositor crash leaves behind (gnome-shell
|
||||
/// SIGSEGV → GDM greeter, whose auto-login is once-per-boot, so the box would otherwise need a walk-up
|
||||
/// or reboot). Typically `sudo -n systemctl restart gdm` with a matching NOPASSWD sudoers rule, or
|
||||
/// `systemctl restart display-manager` under a polkit rule — with auto-login enabled the restart brings
|
||||
/// the desktop back and the client's retry lands in it. Unset/empty = disabled (the default).
|
||||
pub recover_session_cmd: Option<String>,
|
||||
/// `PUNKTFUNK_ON_CONNECT_CMD` — zero-config mirror of a `client.connected` hook
|
||||
/// (`crate::hooks`): fired detached with the event JSON on stdin + `PF_EVENT_*` env when a
|
||||
/// client connects, on either plane. The full hook surface (filters, webhooks, debounce)
|
||||
/// lives in `hooks.json`. Unset/empty = disabled (the default).
|
||||
pub on_connect_cmd: Option<String>,
|
||||
/// `PUNKTFUNK_ON_DISCONNECT_CMD` — the `client.disconnected` sibling of
|
||||
/// [`Self::on_connect_cmd`].
|
||||
pub on_disconnect_cmd: Option<String>,
|
||||
}
|
||||
|
||||
impl HostConfig {
|
||||
fn from_env() -> Self {
|
||||
// Presence flag: set ⇒ true. Matches the original `var_os(k).is_some()` reads (and the few
|
||||
// `var(k).is_ok()` flag reads, which coincide for every real-world value).
|
||||
let flag = |k: &str| std::env::var_os(k).is_some();
|
||||
// String value: `var(k).ok()` — `Some` (possibly empty) when set with valid UTF-8, else `None`.
|
||||
let val = |k: &str| std::env::var(k).ok();
|
||||
Self {
|
||||
// (`PUNKTFUNK_IDD_PUSH` was removed: IDD-push is the sole Windows capture path, so the knob
|
||||
// only split dispatch — capture ignored it while the vdisplay manager obeyed it, and `=0`
|
||||
// produced dead-swap-chain reuse on reconnect. A stale setting in an old host.env is ignored.)
|
||||
encoder_pref: std::env::var("PUNKTFUNK_ENCODER")
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase(),
|
||||
render_adapter: val("PUNKTFUNK_RENDER_ADAPTER"),
|
||||
idd_depth: val("PUNKTFUNK_IDD_DEPTH")
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(2),
|
||||
zerocopy: val("PUNKTFUNK_ZEROCOPY").map(|s| {
|
||||
!matches!(
|
||||
s.trim().to_ascii_lowercase().as_str(),
|
||||
"0" | "false" | "off" | "no"
|
||||
)
|
||||
}),
|
||||
// Default ON, explicit-off grammar (mirrors `four_four_four`: the client's HDR setting
|
||||
// is the real per-session switch; the encode probe keeps incapable GPUs honest at 8-bit).
|
||||
ten_bit: val("PUNKTFUNK_10BIT")
|
||||
.map(|s| {
|
||||
!matches!(
|
||||
s.trim().to_ascii_lowercase().as_str(),
|
||||
"0" | "false" | "off" | "no"
|
||||
)
|
||||
})
|
||||
.unwrap_or(true),
|
||||
// Default ON, explicit-off grammar (the client's own 4:4:4 setting — default OFF —
|
||||
// is the real switch; see the field doc).
|
||||
four_four_four: val("PUNKTFUNK_444")
|
||||
.map(|s| {
|
||||
!matches!(
|
||||
s.trim().to_ascii_lowercase().as_str(),
|
||||
"0" | "false" | "off" | "no"
|
||||
)
|
||||
})
|
||||
.unwrap_or(true),
|
||||
perf: flag("PUNKTFUNK_PERF"),
|
||||
video_source: val("PUNKTFUNK_VIDEO_SOURCE"),
|
||||
compositor: val("PUNKTFUNK_COMPOSITOR"),
|
||||
gamepad: val("PUNKTFUNK_GAMEPAD"),
|
||||
vdisplay: val("PUNKTFUNK_VDISPLAY"),
|
||||
gamescope_steam: val("PUNKTFUNK_GAMESCOPE_STEAM").is_some_and(|s| {
|
||||
matches!(
|
||||
s.trim().to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
)
|
||||
}),
|
||||
recover_session_cmd: val("PUNKTFUNK_RECOVER_SESSION_CMD")
|
||||
.filter(|s| !s.trim().is_empty()),
|
||||
on_connect_cmd: val("PUNKTFUNK_ON_CONNECT_CMD").filter(|s| !s.trim().is_empty()),
|
||||
on_disconnect_cmd: val("PUNKTFUNK_ON_DISCONNECT_CMD").filter(|s| !s.trim().is_empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The process-wide host configuration, parsed once on first access.
|
||||
pub fn config() -> &'static HostConfig {
|
||||
static CFG: OnceLock<HostConfig> = OnceLock::new();
|
||||
CFG.get_or_init(HostConfig::from_env)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ impl Codec {
|
||||
// keeps the bit off (no Vulkan device to open).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
let pyro = if !matches!(
|
||||
crate::config::config().encoder_pref.as_str(),
|
||||
pf_host_config::config().encoder_pref.as_str(),
|
||||
"software" | "sw" | "openh264"
|
||||
) {
|
||||
punktfunk_core::quic::CODEC_PYROWAVE
|
||||
@@ -53,7 +53,7 @@ impl Codec {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if matches!(
|
||||
crate::config::config().encoder_pref.as_str(),
|
||||
pf_host_config::config().encoder_pref.as_str(),
|
||||
"software" | "sw" | "openh264"
|
||||
) {
|
||||
return punktfunk_core::quic::CODEC_H264;
|
||||
@@ -83,7 +83,7 @@ impl Codec {
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
||||
{
|
||||
let _ = GPU_SUPERSET;
|
||||
match crate::config::config().encoder_pref.as_str() {
|
||||
match pf_host_config::config().encoder_pref.as_str() {
|
||||
"software" | "sw" | "openh264" => punktfunk_core::quic::CODEC_H264,
|
||||
_ => punktfunk_core::quic::CODEC_HEVC,
|
||||
}
|
||||
@@ -262,7 +262,7 @@ fn open_video_backend(
|
||||
// AMD/Intel → VAAPI (one libavcodec backend for both). Auto-detect by default so a single
|
||||
// Linux binary serves any GPU; `PUNKTFUNK_ENCODER` forces a specific backend (and surfaces
|
||||
// its errors crisply instead of silently trying the other).
|
||||
let pref = crate::config::config().encoder_pref.as_str();
|
||||
let pref = pf_host_config::config().encoder_pref.as_str();
|
||||
// AMD/Intel opener. Default = libav VAAPI. With `--features vulkan-encode` +
|
||||
// PUNKTFUNK_VULKAN_ENCODE, an HEVC session instead opens the raw Vulkan Video backend (real
|
||||
// RFI loss recovery the VAAPI path can't express); a failed open falls back to VAAPI so the
|
||||
@@ -708,7 +708,7 @@ pub(crate) fn pyrowave_capture_modifiers(fourcc: u32) -> Vec<u64> {
|
||||
/// passthrough for VAAPI vs the EGL→CUDA import for NVENC).
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn linux_zero_copy_is_vaapi() -> bool {
|
||||
match crate::config::config().encoder_pref.as_str() {
|
||||
match pf_host_config::config().encoder_pref.as_str() {
|
||||
"nvenc" | "nvidia" | "cuda" => false,
|
||||
"vaapi" | "amd" | "intel" => true,
|
||||
// PyroWave ingests the raw capture dmabuf itself (Vulkan import + compute CSC) on ANY
|
||||
@@ -945,7 +945,7 @@ enum GpuVendor {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn windows_resolved_backend() -> WindowsBackend {
|
||||
// Resolved ONCE in HostConfig (Goal-1) — was re-read from PUNKTFUNK_ENCODER on every call.
|
||||
match crate::config::config().encoder_pref.as_str() {
|
||||
match pf_host_config::config().encoder_pref.as_str() {
|
||||
"nvenc" | "hw" | "nvidia" | "cuda" => WindowsBackend::Nvenc,
|
||||
"amf" | "amd" => WindowsBackend::Amf,
|
||||
"qsv" | "intel" => WindowsBackend::Qsv,
|
||||
@@ -974,7 +974,7 @@ pub(crate) fn resolved_backend_is_gpu() -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn resolved_backend_is_gpu() -> bool {
|
||||
!matches!(
|
||||
crate::config::config().encoder_pref.as_str(),
|
||||
pf_host_config::config().encoder_pref.as_str(),
|
||||
"software" | "sw" | "openh264"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -809,7 +809,7 @@ impl DmabufInner {
|
||||
// Sampled breakdown of this synchronous submit under PUNKTFUNK_PERF: push = descriptor
|
||||
// build + buffersrc (the per-frame DRM→VA import happens inside hwmap on the pull path),
|
||||
// pull = buffersink (VPP CSC + any sync), send = avcodec_send_frame. One line per ~2 s.
|
||||
let sample = crate::config::config().perf && self.frames % 120 == 0;
|
||||
let sample = pf_host_config::config().perf && self.frames % 120 == 0;
|
||||
self.frames += 1;
|
||||
let t0 = std::time::Instant::now();
|
||||
let t_push: std::time::Duration;
|
||||
|
||||
@@ -122,7 +122,7 @@ impl WinVendor {
|
||||
/// open-failure fallback only catches *setup* errors; a derive that opens but maps wrong would
|
||||
/// corrupt silently, so it stays opt-in per the probe-never-assume rule).
|
||||
fn zerocopy_enabled(vendor: WinVendor) -> bool {
|
||||
crate::config::config()
|
||||
pf_host_config::config()
|
||||
.zerocopy
|
||||
.unwrap_or(matches!(vendor, WinVendor::Amf))
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ pub const SERVER_CODEC_MODE_SUPPORT: u32 = SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8;
|
||||
/// whenever the desktop is HDR, and a client HDR request makes the GameStream video path proactively
|
||||
/// enable advanced color on the per-session virtual display so PQ flows even from an SDR desktop.
|
||||
pub fn host_hdr_capable() -> bool {
|
||||
cfg!(target_os = "windows") && crate::config::config().ten_bit
|
||||
cfg!(target_os = "windows") && pf_host_config::config().ten_bit
|
||||
}
|
||||
|
||||
/// Stable host identity + advertised capabilities, shared across control-plane handlers.
|
||||
|
||||
@@ -162,7 +162,7 @@ fn run(
|
||||
// request and capture it (no scaling). Self-contained — deliberately NOT pooled in
|
||||
// `video_cap`, since a reconnect at a different resolution needs a freshly-sized output; the
|
||||
// output is released when this capturer drops at stream end (RAII via its keepalive).
|
||||
if crate::config::config().video_source.as_deref() == Some("virtual") {
|
||||
if pf_host_config::config().video_source.as_deref() == Some("virtual") {
|
||||
// Per-app prep steps (RFC §6): the entry's own `prep` plus a custom library title's,
|
||||
// run synchronously BEFORE the virtual output opens or anything launches (an HDR
|
||||
// toggle / sink switch must land first — and gamescope's nested launch happens inside
|
||||
@@ -249,7 +249,7 @@ fn run(
|
||||
tracing::info!("video source: reusing capturer");
|
||||
c
|
||||
}
|
||||
None if crate::config::config().video_source.as_deref() == Some("portal") => {
|
||||
None if pf_host_config::config().video_source.as_deref() == Some("portal") => {
|
||||
tracing::info!("video source: portal desktop capture");
|
||||
capture::open_portal_monitor().context("open portal capturer")?
|
||||
}
|
||||
@@ -676,7 +676,7 @@ fn stream_body(
|
||||
|
||||
// Per-stage timing (PUNKTFUNK_PERF=1): max µs/stage per second + unique vs re-encoded frames,
|
||||
// to pinpoint stalls. `unique` counts genuinely-new captured frames (vs re-encoded holds).
|
||||
let perf = crate::config::config().perf;
|
||||
let perf = pf_host_config::config().perf;
|
||||
let (mut mx_cap, mut mx_enc, mut mx_pkt, mut mx_send, mut uniq) =
|
||||
(0u128, 0u128, 0u128, 0u128, 0u32);
|
||||
// Web-console stats accumulation (active when `perf` OR a capture is armed): per-stage vectors
|
||||
|
||||
@@ -420,7 +420,7 @@ impl GpuPrefStore {
|
||||
}
|
||||
|
||||
/// The process-wide preference store (config-dir file), loaded once on first access — the same
|
||||
/// global-accessor shape as [`crate::config::config`], because selection happens deep inside
|
||||
/// global-accessor shape as [`pf_host_config::config`], because selection happens deep inside
|
||||
/// capture/encode setup where no app state is threaded.
|
||||
pub(crate) fn prefs() -> &'static GpuPrefStore {
|
||||
static STORE: OnceLock<GpuPrefStore> = OnceLock::new();
|
||||
@@ -535,7 +535,7 @@ pub(crate) fn pick(
|
||||
pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
||||
let gpus = enumerate();
|
||||
let pref = prefs().get();
|
||||
let env = crate::config::config()
|
||||
let env = pf_host_config::config()
|
||||
.render_adapter
|
||||
.clone()
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
@@ -298,8 +298,8 @@ fn dispatch(
|
||||
// The two env-var mirrors (`PUNKTFUNK_ON_CONNECT_CMD` / `PUNKTFUNK_ON_DISCONNECT_CMD`) —
|
||||
// the zero-config siblings of `PUNKTFUNK_RECOVER_SESSION_CMD` for the simplest cases.
|
||||
let mirror = match kind {
|
||||
"client.connected" => crate::config::config().on_connect_cmd.clone(),
|
||||
"client.disconnected" => crate::config::config().on_disconnect_cmd.clone(),
|
||||
"client.connected" => pf_host_config::config().on_connect_cmd.clone(),
|
||||
"client.disconnected" => pf_host_config::config().on_disconnect_cmd.clone(),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(cmd) = mirror {
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn default_backend() -> Backend {
|
||||
}
|
||||
}
|
||||
// An explicit compositor pick (set per connect / mid-stream) is the strongest signal.
|
||||
let compositor = crate::config::config().compositor.clone();
|
||||
let compositor = pf_host_config::config().compositor.clone();
|
||||
if let Some(c) = compositor.as_deref() {
|
||||
let c = c.trim();
|
||||
if c.eq_ignore_ascii_case("gamescope") {
|
||||
@@ -176,7 +176,7 @@ pub(crate) use service::InjectorService;
|
||||
/// (`org.gnome.Mutter.RemoteDesktop`), the same direct API the Mutter video backend uses.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn libei_ei_source() -> libei::EiSource {
|
||||
let gnome = crate::config::config()
|
||||
let gnome = pf_host_config::config()
|
||||
.compositor
|
||||
.as_deref()
|
||||
.is_some_and(|v| v.trim().eq_ignore_ascii_case("mutter"))
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
mod audio;
|
||||
mod capture;
|
||||
mod config;
|
||||
mod detect;
|
||||
mod devtest;
|
||||
mod discovery;
|
||||
|
||||
@@ -139,7 +139,7 @@ pub(crate) fn gpu_state() -> GpuState {
|
||||
preferred_id,
|
||||
preferred_name,
|
||||
preferred_available,
|
||||
env_override: crate::config::config()
|
||||
env_override: pf_host_config::config()
|
||||
.render_adapter
|
||||
.clone()
|
||||
.filter(|s| !s.is_empty()),
|
||||
|
||||
@@ -57,7 +57,7 @@ pub(super) fn resolve_compositor(
|
||||
crate::vdisplay::cancel_pending_tv_restore();
|
||||
// Explicit operator override (legacy / CI / forcing a backend for a test) wins and is assumed
|
||||
// to come with a hand-set env — don't retarget the process env in that case.
|
||||
let overridden = crate::config::config().compositor.is_some();
|
||||
let overridden = pf_host_config::config().compositor.is_some();
|
||||
let detected = if overridden {
|
||||
crate::vdisplay::detect().ok()
|
||||
} else {
|
||||
|
||||
@@ -200,7 +200,7 @@ fn degrade_steam_on_conflict(chosen: GamepadPref) -> GamepadPref {
|
||||
/// Resolve the client's gamepad-backend preference (the env/logging shell around
|
||||
/// [`pick_gamepad`]). Always concrete — the `Welcome` reports what the session will drive.
|
||||
pub(super) fn resolve_gamepad(pref: GamepadPref) -> GamepadPref {
|
||||
let env = crate::config::config().gamepad.clone();
|
||||
let env = pf_host_config::config().gamepad.clone();
|
||||
let chosen = pick_gamepad(
|
||||
pref,
|
||||
env.as_deref(),
|
||||
|
||||
@@ -207,7 +207,7 @@ pub(super) async fn negotiate(
|
||||
// Welcome, exactly like the 4:4:4 gate below, so `color` reflects what we'll really emit —
|
||||
// the honest-downgrade channel: a GPU/backend that can't 10-bit yields 8-bit AND an SDR
|
||||
// label that matches the stream.
|
||||
let host_wants_10bit = crate::config::config().ten_bit;
|
||||
let host_wants_10bit = pf_host_config::config().ten_bit;
|
||||
let client_supports_10bit = hello.video_caps & punktfunk_core::quic::VIDEO_CAP_10BIT != 0;
|
||||
// The GPU probe may open a tiny encoder on first use, so run it off the reactor like the
|
||||
// 4:4:4 probe below (blocking probes → spawn_blocking), short-circuited behind the cheap
|
||||
@@ -239,7 +239,7 @@ pub(super) async fn negotiate(
|
||||
// what we'll really emit — the honest-downgrade channel: if any gate fails the client is
|
||||
// told 4:2:0 before it builds its decoder. The probe opens a tiny encoder; it runs only
|
||||
// when the earlier gates pass and is cached after the first.
|
||||
let host_wants_444 = crate::config::config().four_four_four;
|
||||
let host_wants_444 = pf_host_config::config().four_four_four;
|
||||
let client_supports_444 = hello.video_caps & punktfunk_core::quic::VIDEO_CAP_444 != 0;
|
||||
// The active capturer must be able to deliver a full-chroma (RGB) source — the honest-downgrade
|
||||
// gate. Linux's portal capturer can; the Windows IDD-push path delivers subsampled NV12/P010
|
||||
|
||||
@@ -873,7 +873,7 @@ pub(super) fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||
let _ = &launch;
|
||||
|
||||
let perf = crate::config::config().perf;
|
||||
let perf = pf_host_config::config().perf;
|
||||
// Microburst cap (applied in send_loop/paced_submit): a frame ≤ the cap bursts out
|
||||
// immediately; only a bigger frame's overflow is spread. `None` = auto — max(128 KB, the
|
||||
// AU's wire bytes / 4), so the burst stays a bounded fraction of high-rate frames instead
|
||||
@@ -956,7 +956,7 @@ pub(super) fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
// place when the box flips Gaming↔Desktop. When not spawned, session_rx just stays empty.
|
||||
let mut compositor = compositor;
|
||||
let (session_tx, session_rx) = std::sync::mpsc::channel::<SessionSwitch>();
|
||||
let watch = session_watch_enabled() && crate::config::config().compositor.is_none();
|
||||
let watch = session_watch_enabled() && pf_host_config::config().compositor.is_none();
|
||||
let _watcher = if watch {
|
||||
tracing::info!("session watcher on — following a mid-stream Gaming↔Desktop switch");
|
||||
let stop = stop.clone();
|
||||
@@ -1461,7 +1461,7 @@ pub(super) fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
let (new_cap, new_enc, new_frame, new_interval, new_node_id, new_display_gen) = loop {
|
||||
// Follow the active session unless an explicit PUNKTFUNK_COMPOSITOR pin forbids
|
||||
// retargeting (then we stick to the pinned backend and just rebuild it).
|
||||
if crate::config::config().compositor.is_none() {
|
||||
if pf_host_config::config().compositor.is_none() {
|
||||
let active = crate::vdisplay::detect_active_session();
|
||||
// A4: fold any compositor-instance change into the epoch/invalidation before we
|
||||
// rebuild, so the rebuild's acquire won't reuse a dead-instance node.
|
||||
|
||||
@@ -205,7 +205,7 @@ fn resolve_encoder() -> EncoderBackend {
|
||||
// capture (`EncoderBackend::Software.is_gpu() == false` → `output_format().gpu = false`), so the
|
||||
// portal capturer delivers CPU RGB. Everything else stays `PlatformAuto` (NVENC/VAAPI resolved
|
||||
// inside `encode::open_video`).
|
||||
match crate::config::config().encoder_pref.as_str() {
|
||||
match pf_host_config::config().encoder_pref.as_str() {
|
||||
"software" | "sw" | "openh264" => EncoderBackend::Software,
|
||||
_ => EncoderBackend::PlatformAuto,
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ pub fn with_env_lock<R>(f: impl FnOnce() -> R) -> R {
|
||||
/// a backend for a test), else the **live session** ([`detect_active_session`] — so a Bazzite box
|
||||
/// follows Gaming↔Desktop switches), else a last-resort `XDG_CURRENT_DESKTOP` read.
|
||||
pub fn detect() -> Result<Compositor> {
|
||||
if let Some(v) = crate::config::config().compositor.as_deref() {
|
||||
if let Some(v) = pf_host_config::config().compositor.as_deref() {
|
||||
return match v.trim().to_ascii_lowercase().as_str() {
|
||||
"kwin" | "kde" | "plasma" => Ok(Compositor::Kwin),
|
||||
// `hyprland` names the distinct backend (D1); `wlroots`/`sway`/`wlr` stay wlroots-proper.
|
||||
@@ -317,7 +317,7 @@ pub(crate) mod layout;
|
||||
pub fn resolve_topology(t: policy::Topology) -> policy::Topology {
|
||||
match t {
|
||||
policy::Topology::Auto => {
|
||||
if crate::config::config().compositor.is_some() {
|
||||
if pf_host_config::config().compositor.is_some() {
|
||||
policy::Topology::Extend
|
||||
} else {
|
||||
policy::Topology::Exclusive
|
||||
|
||||
@@ -1408,7 +1408,7 @@ fn spawn(w: u32, h: u32, hz: u32, cmd: Option<&str>, log: &std::path::Path) -> R
|
||||
let app = shape_dedicated_command(&app);
|
||||
let relay = ei_socket_file();
|
||||
let _ = std::fs::remove_file(&relay); // stale socket path from a previous session
|
||||
let steam_mode = crate::config::config().gamescope_steam;
|
||||
let steam_mode = pf_host_config::config().gamescope_steam;
|
||||
let mut cmd = Command::new("gamescope");
|
||||
add_bare_gamescope_args(&mut cmd, w, h, hz, steam_mode);
|
||||
cmd.args([
|
||||
|
||||
@@ -418,7 +418,7 @@ pub fn apply_session_env(_active: &ActiveSession) {}
|
||||
/// handshake error tell the client to simply retry.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn try_recover_session() -> bool {
|
||||
let Some(cmd) = crate::config::config().recover_session_cmd.clone() else {
|
||||
let Some(cmd) = pf_host_config::config().recover_session_cmd.clone() else {
|
||||
return false;
|
||||
};
|
||||
static LAST_LAUNCH: std::sync::Mutex<Option<std::time::Instant>> = std::sync::Mutex::new(None);
|
||||
|
||||
Reference in New Issue
Block a user