feat(host/vdisplay): per-connect active-session backend selection
ci / web (push) Successful in 26s
ci / docs-site (push) Successful in 29s
apple / swift (push) Successful in 1m16s
ci / bench (push) Successful in 1m34s
deb / build-publish (push) Successful in 4m32s
ci / rust (push) Successful in 7m2s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m23s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m26s
docker / deploy-docs (push) Successful in 18s
ci / web (push) Successful in 26s
ci / docs-site (push) Successful in 29s
apple / swift (push) Successful in 1m16s
ci / bench (push) Successful in 1m34s
deb / build-publish (push) Successful in 4m32s
ci / rust (push) Successful in 7m2s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m23s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m26s
docker / deploy-docs (push) Successful in 18s
Bazzite/SteamOS boxes flip between Steam Gaming Mode (gamescope) and a KDE/GNOME desktop. The host statically read PUNKTFUNK_COMPOSITOR / XDG_CURRENT_DESKTOP once, so switching to Desktop Mode failed the stream, and the gamescope managed-session path stopped+relaunched the autologin per connect — leaking GPU context on F44 (reconnect → black screen). Replace the static read with a runtime probe of the live session and route each connect to the right backend, churn-free: - vdisplay::detect_active_session() probes /proc for the running compositor of our uid (gamescope|kwin_wayland|gnome-shell|sway, desktop outranks a leftover gamescope) + scans the runtime dir for the live wayland-* socket. Returns an ActiveKind + the SessionEnv (WAYLAND_DISPLAY/XDG_RUNTIME_DIR/DBUS/ XDG_CURRENT_DESKTOP) that targets it. - apply_session_env() writes that into the process env per connect (host serves one session at a time), so every backend (capture + input) opens against the live session; apply_input_env() points input at the matching backend and selects gamescope ATTACH (no managed restart) unless PUNKTFUNK_GAMESCOPE_MANAGED. - resolve_compositor() (native path) auto-detects + applies; explicit PUNKTFUNK_COMPOSITOR still wins (legacy/CI/forcing). detect() is now active-aware for the GameStream/mgmt callers too. - Bazzite host.env drops the static gamescope force; documents auto-detection + the optional overrides. Result: Desktop Mode → KWin/Mutter virtual output at the client's mode (churn-free, the reliable path); Gaming Mode → attach to the running gamescope (no SIGSEGV/GPU leak on reconnect). Compiles + clippy-clean; 78 host tests pass. Live validation on the Bazzite box pending (box offline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1499,11 +1499,32 @@ fn pick_compositor(
|
||||
/// async reactor (`spawn_blocking`).
|
||||
fn resolve_compositor(pref: CompositorPref) -> Result<crate::vdisplay::Compositor> {
|
||||
use crate::vdisplay::Compositor;
|
||||
// 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 = std::env::var_os("PUNKTFUNK_COMPOSITOR").is_some();
|
||||
let detected = if overridden {
|
||||
crate::vdisplay::detect().ok()
|
||||
} else {
|
||||
// Auto: detect the LIVE session (Gaming vs Desktop) and retarget the process env at it so
|
||||
// every backend (video capture + input) this connect opens against the active session —
|
||||
// this is the state machine that lets one host follow a Bazzite box across Gaming↔Desktop.
|
||||
let active = crate::vdisplay::detect_active_session();
|
||||
crate::vdisplay::apply_session_env(&active);
|
||||
tracing::info!(
|
||||
active = ?active.kind,
|
||||
wayland = active.env.wayland_display.as_deref().unwrap_or("-"),
|
||||
"detected active graphical session"
|
||||
);
|
||||
crate::vdisplay::compositor_for_kind(active.kind)
|
||||
};
|
||||
let available = crate::vdisplay::available();
|
||||
let detected = crate::vdisplay::detect().ok();
|
||||
let chosen = pick_compositor(pref, &available, detected).ok_or_else(|| {
|
||||
anyhow!("no usable compositor (set PUNKTFUNK_COMPOSITOR or run inside a supported desktop)")
|
||||
anyhow!("no usable compositor (no live graphical session for this uid; set PUNKTFUNK_COMPOSITOR or start a desktop/gaming session)")
|
||||
})?;
|
||||
if !overridden {
|
||||
// Point input at the same backend and select gamescope ATTACH (no churny managed restart).
|
||||
crate::vdisplay::apply_input_env(chosen);
|
||||
}
|
||||
let avail_ids: Vec<&str> = available.iter().map(|c| c.id()).collect();
|
||||
match Compositor::from_pref(pref) {
|
||||
Some(want) if want == chosen => {
|
||||
|
||||
@@ -144,7 +144,262 @@ pub fn available() -> Vec<Compositor> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the compositor to drive: `PUNKTFUNK_COMPOSITOR` override, else `XDG_CURRENT_DESKTOP`.
|
||||
/// The kind of graphical session live for our uid *right now* — the basis for per-connect backend
|
||||
/// selection on a box that flips between Steam Gaming Mode and a KDE/GNOME desktop (Bazzite,
|
||||
/// SteamOS). Detected by probing which compositor process is actually running, not by a static
|
||||
/// env var, so the host follows the box as the user switches sessions.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ActiveKind {
|
||||
/// A `gamescope` session is live (Steam Gaming Mode / `gamescope-session-plus`).
|
||||
Gaming,
|
||||
/// A KWin / Plasma desktop is live.
|
||||
DesktopKde,
|
||||
/// A GNOME / Mutter desktop is live.
|
||||
DesktopGnome,
|
||||
/// A wlroots (Sway / Hyprland) desktop is live.
|
||||
DesktopWlroots,
|
||||
/// No recognized graphical session is running for our uid.
|
||||
None,
|
||||
}
|
||||
|
||||
/// The session environment that points a backend at the [detected](detect_active_session) active
|
||||
/// session: the Wayland socket (for the Wayland-protocol backends), the runtime dir + session bus
|
||||
/// (for PipeWire capture + D-Bus / portal input), and the desktop name (for portal routing). The
|
||||
/// host serves one session at a time, so [`apply_session_env`] writes these into the process env
|
||||
/// per connect and every backend that reads them then opens against the live session.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SessionEnv {
|
||||
/// `WAYLAND_DISPLAY` of the live compositor (`None` for Gaming-attach / Mutter, which are
|
||||
/// PipeWire-node / D-Bus driven and don't talk Wayland to us).
|
||||
pub wayland_display: Option<String>,
|
||||
/// `/run/user/<uid>` — the trustworthy anchor (the default PipeWire daemon + bus live here).
|
||||
pub xdg_runtime_dir: String,
|
||||
/// `DBUS_SESSION_BUS_ADDRESS` (defaults to `unix:path=<runtime>/bus`).
|
||||
pub dbus_session_bus_address: String,
|
||||
/// `XDG_CURRENT_DESKTOP` to advertise (KDE/GNOME/sway/gamescope) — drives portal/EIS routing.
|
||||
pub xdg_current_desktop: Option<String>,
|
||||
}
|
||||
|
||||
/// The live session: its [`ActiveKind`] plus the [`SessionEnv`] to target it.
|
||||
pub struct ActiveSession {
|
||||
pub kind: ActiveKind,
|
||||
pub env: SessionEnv,
|
||||
}
|
||||
|
||||
impl ActiveSession {
|
||||
/// A "nothing live" result carrying just the runtime-dir anchor.
|
||||
fn none() -> ActiveSession {
|
||||
ActiveSession {
|
||||
kind: ActiveKind::None,
|
||||
env: SessionEnv {
|
||||
xdg_runtime_dir: default_runtime_dir(),
|
||||
dbus_session_bus_address: default_bus(&default_runtime_dir()),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The concrete backend that drives a given live-session kind. `None` for [`ActiveKind::None`].
|
||||
pub fn compositor_for_kind(kind: ActiveKind) -> Option<Compositor> {
|
||||
match kind {
|
||||
ActiveKind::Gaming => Some(Compositor::Gamescope),
|
||||
ActiveKind::DesktopKde => Some(Compositor::Kwin),
|
||||
ActiveKind::DesktopGnome => Some(Compositor::Mutter),
|
||||
ActiveKind::DesktopWlroots => Some(Compositor::Wlroots),
|
||||
ActiveKind::None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn default_runtime_dir() -> String {
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| {
|
||||
let uid = unsafe { libc::getuid() };
|
||||
format!("/run/user/{uid}")
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn default_runtime_dir() -> String {
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_default()
|
||||
}
|
||||
|
||||
fn default_bus(runtime: &str) -> String {
|
||||
std::env::var("DBUS_SESSION_BUS_ADDRESS").unwrap_or_else(|_| format!("unix:path={runtime}/bus"))
|
||||
}
|
||||
|
||||
/// Detect the graphical session live for our uid right now (cheap, side-effect-free: a `/proc`
|
||||
/// scan plus a runtime-dir socket scan — well under the handshake timeout). The authority is the
|
||||
/// running compositor process; a desktop compositor outranks a lingering gamescope. Used to route
|
||||
/// each connect to the correct backend, and to derive the [`SessionEnv`] that targets it.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn detect_active_session() -> ActiveSession {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let uid = unsafe { libc::getuid() };
|
||||
let xdg_runtime_dir = default_runtime_dir();
|
||||
let dbus = default_bus(&xdg_runtime_dir);
|
||||
|
||||
// Process probe: the running graphical compositor of THIS uid decides the kind. Priority lets
|
||||
// a real desktop (kwin/gnome/sway) win over a leftover gamescope child. comm names mirror the
|
||||
// `pkill -x` discipline (exact, ≤15 chars so untruncated).
|
||||
let mut kind = ActiveKind::None;
|
||||
let mut best = 0u8;
|
||||
if let Ok(entries) = std::fs::read_dir("/proc") {
|
||||
for e in entries.flatten() {
|
||||
let name = e.file_name();
|
||||
let Some(name) = name.to_str() else { continue };
|
||||
if name.is_empty() || !name.bytes().all(|b| b.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
let pid_path = e.path();
|
||||
let Ok(md) = std::fs::metadata(&pid_path) else {
|
||||
continue;
|
||||
};
|
||||
if md.uid() != uid {
|
||||
continue;
|
||||
}
|
||||
let Ok(comm) = std::fs::read_to_string(pid_path.join("comm")) else {
|
||||
continue;
|
||||
};
|
||||
let (k, prio) = match comm.trim() {
|
||||
"gamescope" | "gamescope-wl" => (ActiveKind::Gaming, 1),
|
||||
"kwin_wayland" => (ActiveKind::DesktopKde, 4),
|
||||
"gnome-shell" => (ActiveKind::DesktopGnome, 4),
|
||||
"sway" | "Hyprland" | "hyprland" | "river" => (ActiveKind::DesktopWlroots, 4),
|
||||
_ => continue,
|
||||
};
|
||||
if prio > best {
|
||||
best = prio;
|
||||
kind = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wayland-protocol backends (KWin, wlroots) need the live socket; Gaming-attach and Mutter are
|
||||
// node/D-Bus driven and don't.
|
||||
let wayland_display = match kind {
|
||||
ActiveKind::DesktopKde | ActiveKind::DesktopWlroots => {
|
||||
find_wayland_socket(&xdg_runtime_dir, uid)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let xdg_current_desktop = match kind {
|
||||
ActiveKind::DesktopKde => Some("KDE".to_string()),
|
||||
ActiveKind::DesktopGnome => Some("GNOME".to_string()),
|
||||
ActiveKind::DesktopWlroots => Some("sway".to_string()),
|
||||
ActiveKind::Gaming => Some("gamescope".to_string()),
|
||||
ActiveKind::None => None,
|
||||
};
|
||||
ActiveSession {
|
||||
kind,
|
||||
env: SessionEnv {
|
||||
wayland_display,
|
||||
xdg_runtime_dir,
|
||||
dbus_session_bus_address: dbus,
|
||||
xdg_current_desktop,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn detect_active_session() -> ActiveSession {
|
||||
ActiveSession::none()
|
||||
}
|
||||
|
||||
/// Find the live `wayland-*` socket in `runtime` for our uid (skipping `.lock` sidecars). Trust a
|
||||
/// valid inherited `WAYLAND_DISPLAY` first; otherwise take the newest-mtime socket we own (a
|
||||
/// desktop session normally exposes exactly one).
|
||||
#[cfg(target_os = "linux")]
|
||||
fn find_wayland_socket(runtime: &str, uid: u32) -> Option<String> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
if let Ok(w) = std::env::var("WAYLAND_DISPLAY") {
|
||||
if !w.is_empty() {
|
||||
let p = if w.starts_with('/') {
|
||||
std::path::PathBuf::from(&w)
|
||||
} else {
|
||||
std::path::Path::new(runtime).join(&w)
|
||||
};
|
||||
if p.exists() {
|
||||
return Some(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut cands: Vec<(std::time::SystemTime, String)> = Vec::new();
|
||||
for e in std::fs::read_dir(runtime).ok()?.flatten() {
|
||||
let name = e.file_name().to_string_lossy().into_owned();
|
||||
if !name.starts_with("wayland-") || name.ends_with(".lock") {
|
||||
continue;
|
||||
}
|
||||
let Ok(md) = e.metadata() else { continue };
|
||||
if md.uid() != uid {
|
||||
continue;
|
||||
}
|
||||
let mtime = md.modified().unwrap_or(std::time::UNIX_EPOCH);
|
||||
cands.push((mtime, name));
|
||||
}
|
||||
cands.sort_by_key(|(m, _)| std::cmp::Reverse(*m));
|
||||
cands.into_iter().next().map(|(_, n)| n)
|
||||
}
|
||||
|
||||
/// Write a detected session's [`SessionEnv`] into the process env so every backend (video capture
|
||||
/// and input alike) that reads `WAYLAND_DISPLAY` / `XDG_RUNTIME_DIR` / `DBUS_SESSION_BUS_ADDRESS` /
|
||||
/// `XDG_CURRENT_DESKTOP` at open time targets the live session. The host serves one session at a
|
||||
/// time, so a process-global write is sound; the next connect re-detects and re-applies. Same
|
||||
/// `set_var` discipline already used for `PUNKTFUNK_GAMESCOPE_APP` on the launch path.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn apply_session_env(active: &ActiveSession) {
|
||||
let e = &active.env;
|
||||
std::env::set_var("XDG_RUNTIME_DIR", &e.xdg_runtime_dir);
|
||||
std::env::set_var("DBUS_SESSION_BUS_ADDRESS", &e.dbus_session_bus_address);
|
||||
if let Some(w) = &e.wayland_display {
|
||||
std::env::set_var("WAYLAND_DISPLAY", w);
|
||||
}
|
||||
if let Some(d) = &e.xdg_current_desktop {
|
||||
std::env::set_var("XDG_CURRENT_DESKTOP", d);
|
||||
}
|
||||
// Mutter on NVIDIA has no working dmabuf capture sync — force SHM there; the KWin/gamescope
|
||||
// tiled/LINEAR paths keep zero-copy.
|
||||
if active.kind == ActiveKind::DesktopGnome {
|
||||
std::env::set_var("PUNKTFUNK_FORCE_SHM", "1");
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn apply_session_env(_active: &ActiveSession) {}
|
||||
|
||||
/// Route input to match the chosen video backend (they must not diverge), via the highest-priority
|
||||
/// `PUNKTFUNK_INPUT_BACKEND` knob the injector honors. For gamescope, also select **attach** (no
|
||||
/// churny host-managed restart) unless the operator explicitly opted into the managed session with
|
||||
/// `PUNKTFUNK_GAMESCOPE_MANAGED` — attaching to the running session avoids the per-connect
|
||||
/// stop/relaunch that leaked GPU context (the reconnect-black-screen on Bazzite F44).
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn apply_input_env(chosen: Compositor) {
|
||||
let backend = match chosen {
|
||||
Compositor::Gamescope => "gamescope",
|
||||
Compositor::Kwin | Compositor::Mutter => "libei",
|
||||
Compositor::Wlroots => "wlr",
|
||||
};
|
||||
std::env::set_var("PUNKTFUNK_INPUT_BACKEND", backend);
|
||||
if chosen == Compositor::Gamescope {
|
||||
// Managed = the operator opted in (new `PUNKTFUNK_GAMESCOPE_MANAGED`, or legacy
|
||||
// `PUNKTFUNK_GAMESCOPE_SESSION` set explicitly). Otherwise ATTACH to the running session.
|
||||
let managed = std::env::var_os("PUNKTFUNK_GAMESCOPE_MANAGED").is_some()
|
||||
|| std::env::var_os("PUNKTFUNK_GAMESCOPE_SESSION").is_some();
|
||||
if managed {
|
||||
if std::env::var_os("PUNKTFUNK_GAMESCOPE_SESSION").is_none() {
|
||||
std::env::set_var("PUNKTFUNK_GAMESCOPE_SESSION", "steam");
|
||||
}
|
||||
std::env::remove_var("PUNKTFUNK_GAMESCOPE_NODE");
|
||||
} else {
|
||||
std::env::remove_var("PUNKTFUNK_GAMESCOPE_SESSION");
|
||||
std::env::set_var("PUNKTFUNK_GAMESCOPE_NODE", "auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn apply_input_env(_chosen: Compositor) {}
|
||||
|
||||
/// Detect the compositor to drive: explicit `PUNKTFUNK_COMPOSITOR` override (legacy / CI / forcing
|
||||
/// 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 Ok(v) = std::env::var("PUNKTFUNK_COMPOSITOR") {
|
||||
return match v.trim().to_ascii_lowercase().as_str() {
|
||||
@@ -159,6 +414,10 @@ pub fn detect() -> Result<Compositor> {
|
||||
}
|
||||
};
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(c) = compositor_for_kind(detect_active_session().kind) {
|
||||
return Ok(c);
|
||||
}
|
||||
let desktop = std::env::var("XDG_CURRENT_DESKTOP")
|
||||
.unwrap_or_default()
|
||||
.to_ascii_uppercase();
|
||||
@@ -173,7 +432,8 @@ pub fn detect() -> Result<Compositor> {
|
||||
Ok(Compositor::Wlroots)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"could not detect compositor from XDG_CURRENT_DESKTOP='{desktop}'; set PUNKTFUNK_COMPOSITOR"
|
||||
"could not detect compositor: no live graphical session for this uid and \
|
||||
XDG_CURRENT_DESKTOP='{desktop}'; set PUNKTFUNK_COMPOSITOR"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -245,3 +505,45 @@ mod kwin;
|
||||
mod mutter;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wlroots;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn active_kind_maps_to_its_backend() {
|
||||
assert_eq!(
|
||||
compositor_for_kind(ActiveKind::Gaming),
|
||||
Some(Compositor::Gamescope)
|
||||
);
|
||||
assert_eq!(
|
||||
compositor_for_kind(ActiveKind::DesktopKde),
|
||||
Some(Compositor::Kwin)
|
||||
);
|
||||
assert_eq!(
|
||||
compositor_for_kind(ActiveKind::DesktopGnome),
|
||||
Some(Compositor::Mutter)
|
||||
);
|
||||
assert_eq!(
|
||||
compositor_for_kind(ActiveKind::DesktopWlroots),
|
||||
Some(Compositor::Wlroots)
|
||||
);
|
||||
// No live session → no backend; the caller turns this into a handshake error / fallback.
|
||||
assert_eq!(compositor_for_kind(ActiveKind::None), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_active_session_is_side_effect_free_and_terminates() {
|
||||
// A pure probe of /proc + the runtime dir: it must not panic and must return promptly on
|
||||
// any box (CI has no graphical session → ActiveKind::None, with the runtime-dir anchor).
|
||||
let a = detect_active_session();
|
||||
assert!(!a.env.xdg_runtime_dir.is_empty());
|
||||
// Wayland sockets are only resolved for the Wayland-protocol desktops.
|
||||
if matches!(
|
||||
a.kind,
|
||||
ActiveKind::Gaming | ActiveKind::DesktopGnome | ActiveKind::None
|
||||
) {
|
||||
assert!(a.env.wayland_display.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-13
@@ -1,26 +1,25 @@
|
||||
# punktfunk host config for Bazzite (~/.config/punktfunk/host.env).
|
||||
#
|
||||
# Bazzite ships gamescope, PipeWire and the NVIDIA driver, so the default backend here is
|
||||
# gamescope: the host spawns a headless gamescope per session at the client's exact mode and
|
||||
# captures its PipeWire node — no separate desktop session to bring up. Set PUNKTFUNK_GAMESCOPE_APP
|
||||
# to what you want to run inside it (e.g. `steam -gamepadui` for a SteamOS-like couch session).
|
||||
# The compositor + input backend are AUTO-DETECTED per connect from the ACTIVE session: the host
|
||||
# follows the box as you flip between Steam Gaming Mode (gamescope — attached to the running
|
||||
# session, no churn) and a KDE/GNOME Desktop (KWin/Mutter virtual output at the client's mode).
|
||||
# So nothing here forces a backend — only the trustworthy anchors stay.
|
||||
|
||||
XDG_RUNTIME_DIR=/run/user/1000
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
||||
|
||||
# gamescope backend: spawned per session, no compositor login required.
|
||||
PUNKTFUNK_COMPOSITOR=gamescope
|
||||
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||
PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui
|
||||
|
||||
# gamescope hosts its own EIS input socket — input lands in the nested session.
|
||||
PUNKTFUNK_INPUT_BACKEND=gamescope
|
||||
|
||||
# GPU zero-copy capture (dmabuf -> CUDA -> NVENC). Auto-falls back to CPU if unavailable.
|
||||
PUNKTFUNK_ZEROCOPY=1
|
||||
|
||||
#RUST_LOG=info
|
||||
|
||||
# To drive the full Plasma/GNOME desktop instead of a nested gamescope, switch to:
|
||||
# PUNKTFUNK_COMPOSITOR=kwin (and run inside a KDE session — WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP set)
|
||||
# PUNKTFUNK_INPUT_BACKEND=libei
|
||||
# --- Optional overrides (default is active-session auto-detection) ---
|
||||
# Force a specific backend for testing (skips auto-detect + env retargeting):
|
||||
# PUNKTFUNK_COMPOSITOR=kwin|mutter|wlroots|gamescope
|
||||
# PUNKTFUNK_INPUT_BACKEND=libei|wlr|gamescope|uinput
|
||||
# Opt into the host-MANAGED gamescope session (spawns gamescope-session-plus at the client mode,
|
||||
# stops the autologin gaming session for the duration) instead of attaching to the running one:
|
||||
# PUNKTFUNK_GAMESCOPE_MANAGED=1
|
||||
# PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
# punktfunk host configuration (~/.config/punktfunk/host.env) — consumed by punktfunk-host.service.
|
||||
#
|
||||
# The compositor + input backend are AUTO-DETECTED per connect from the live session (the host
|
||||
# probes which compositor is actually running and retargets WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP/
|
||||
# DBUS at it), so a box that flips between Steam Gaming Mode and a KDE/GNOME desktop is followed
|
||||
# automatically. The blocks below are OPTIONAL OVERRIDES — uncomment one only to force a backend
|
||||
# (this also skips the per-connect env retargeting). The anchors XDG_RUNTIME_DIR + DBUS stay.
|
||||
|
||||
# Session / compositor environment (headless KWin example).
|
||||
XDG_RUNTIME_DIR=/run/user/1000
|
||||
|
||||
Reference in New Issue
Block a user