080c55dbf7
apple / swift (push) Successful in 1m5s
ci / rust (push) Failing after 1m29s
windows-host / package (push) Failing after 1m11s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m4s
android / android (push) Successful in 3m35s
apple / screenshots (push) Successful in 5m30s
deb / build-publish (push) Successful in 3m18s
decky / build-publish (push) Successful in 27s
ci / bench (push) Successful in 4m39s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m38s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m23s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 52s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m24s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m7s
docker / deploy-docs (push) Failing after 12m53s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
Remove DXGI Desktop Duplication (DuplCapturer), Windows.Graphics.Capture
(WgcCapturer), the two-process SYSTEM+helper relay (virtual_stream_relay /
HelperRelay / DesktopWatcher / composed_flip), and the five source files that
implemented them. IDD direct-push is now the sole Windows capture path; the
session topology is always SingleProcess.
Deleted files: wgc.rs, wgc_relay.rs, desktop_watch.rs, composed_flip.rs,
windows/wgc_helper.rs (+ wgc-helper subcommand in main.rs).
dxgi.rs is kept but carved to shared GPU primitives only (make_device,
HdrP010Converter, VideoConverter, install_gpu_pref_hook, WinCaptureTarget,
pack_luid) — ~2237 lines of DDA-only code removed; imports cleaned.
capture.rs: IDD-push open failure fails the session cleanly (no fallback).
Adds capturer_supports_444() — returns false on Windows (IDD-push 4:4:4 is a
follow-up), replacing the stale single_process gate in 4:4:4 negotiation.
session_plan.rs: CaptureBackend{Dda,Wgc} and SessionTopology::TwoProcessRelay
removed. config.rs: no_helper/force_helper/no_wgc/capture_backend/secure_dda
removed. merged_env_block relocated from wgc_relay to windows/interactive.rs.
Linux cargo check clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
7.5 KiB
Rust
117 lines
7.5 KiB
Rust
//! `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: `idd_push`,
|
||
//! `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 uses **presence** semantics (`var_os(..).is_some()`) to match the
|
||
//! Windows `encode/ffmpeg_win.rs` reader. 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_IDD_PUSH` — IDD direct-push monitor mode (the per-session monitor + ring recreate and
|
||
/// the discrete-render-GPU pin in [`crate::vdisplay::manager`]). IDD-push is the sole Windows capture
|
||
/// path (DXGI Desktop Duplication and the WGC relay were removed), so this should stay on — the
|
||
/// installer's `host.env` sets it. **Value-aware** (`0`/`false`/`no`/`off`/empty ⇒ off, else on);
|
||
/// unset ⇒ off. NOT a bare presence flag (so an operator can turn it OFF with `=0`).
|
||
pub idd_push: bool,
|
||
/// `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` — opt into the Windows D3D11 zero-copy encode path (presence semantics; see module docs).
|
||
pub zerocopy: bool,
|
||
/// `PUNKTFUNK_10BIT` — host policy gate for HEVC Main10 (only honored when the client also advertised 10-bit).
|
||
pub ten_bit: bool,
|
||
/// `PUNKTFUNK_444` — host policy gate for full-chroma HEVC 4:4:4 (Range Extensions). Honored only
|
||
/// when the client also advertised 4:4:4, the codec is HEVC, and the GPU/driver supports a 4:4:4
|
||
/// encode (probed) — otherwise the session stays 4:2:0. 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>,
|
||
}
|
||
|
||
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 {
|
||
// Value-aware (not a bare presence flag): the shipped default `host.env` turns it ON, and an
|
||
// operator turns it OFF with `PUNKTFUNK_IDD_PUSH=0` (a `var_os` presence check would read `=0`
|
||
// as "on"). Unset ⇒ off (the dev / non-pf-driver default).
|
||
idd_push: match std::env::var("PUNKTFUNK_IDD_PUSH") {
|
||
Ok(v) => !matches!(
|
||
v.trim().to_ascii_lowercase().as_str(),
|
||
"" | "0" | "false" | "no" | "off"
|
||
),
|
||
Err(_) => false,
|
||
},
|
||
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: flag("PUNKTFUNK_ZEROCOPY"),
|
||
ten_bit: flag("PUNKTFUNK_10BIT"),
|
||
four_four_four: flag("PUNKTFUNK_444"),
|
||
perf: flag("PUNKTFUNK_PERF"),
|
||
video_source: val("PUNKTFUNK_VIDEO_SOURCE"),
|
||
compositor: val("PUNKTFUNK_COMPOSITOR"),
|
||
gamepad: val("PUNKTFUNK_GAMEPAD"),
|
||
vdisplay: val("PUNKTFUNK_VDISPLAY"),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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)
|
||
}
|