feat(host/gamescope): headless game mode that follows the box + matches the client
apple / swift (push) Successful in 1m2s
android / android (push) Successful in 4m43s
ci / rust (push) Successful in 4m53s
ci / web (push) Successful in 54s
ci / docs-site (push) Successful in 57s
apple / screenshots (push) Successful in 5m6s
deb / build-publish (push) Successful in 2m31s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
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 4s
windows-host / package (push) Successful in 9m2s
ci / bench (push) Successful in 4m41s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m6s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m43s
apple / swift (push) Successful in 1m2s
android / android (push) Successful in 4m43s
ci / rust (push) Successful in 4m53s
ci / web (push) Successful in 54s
ci / docs-site (push) Successful in 57s
apple / screenshots (push) Successful in 5m6s
deb / build-publish (push) Successful in 2m31s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
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 4s
windows-host / package (push) Successful in 9m2s
ci / bench (push) Successful in 4m41s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m6s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m43s
Make Steam game mode work on a display-less streaming host and stream it at the client's resolution: * Ship /etc/gamescope-session-plus/sessions.d/steam (packaging/bazzite/ gamescope-headless-session, installed by the RPM + Arch PKGBUILD): fall back to gamescope's headless backend when no display is connected, so "Switch to Game Mode" boots offscreen instead of crashing on the missing panel (and 5-striking back to desktop). No-op on display-attached boxes; only sets unset values so the host's per-client mode still wins. * Default Bazzite/SteamOS to ATTACH (PUNKTFUNK_GAMESCOPE_ATTACH=1 in host.env): the box owns its session (Desktop<->Game, persistent), the host follows + captures it and never tears it down — so switching is rock-solid and a disconnect leaves the box in its mode (reconnect returns there). * Resize-on-attach (gamescope.rs): on connect, ensure the box's own game-mode session runs at the CLIENT's resolution — reuse it when already matching (fast path, no restart), else reconfigure + restart the box's own autologin gamescope-session-plus@<client> at the client mode (cooperative: no competing unit, so no autologin-respawn fight). Detect the live gamescope's -W/-H via argv[0] in /proc (its /proc/<pid>/exe is unreadable for that process). Validated live on a headless bazzite-deck-nvidia box: game mode boots headless + stable (0 strikes); the host attaches + streams video/audio/EIS input; a 5120x1440 client reuses the matching session and streams at 5120x1440. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
//! `inject/libei.rs`) — wired and live-validated.
|
//! `inject/libei.rs`) — wired and live-validated.
|
||||||
|
|
||||||
use super::{Mode, VirtualDisplay, VirtualOutput};
|
use super::{Mode, VirtualDisplay, VirtualOutput};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -110,12 +110,11 @@ impl VirtualDisplay for GamescopeDisplay {
|
|||||||
// PUNKTFUNK_GAMESCOPE_NODE=<id|auto>; "auto" discovers the gamescope `Video/Source` node.
|
// PUNKTFUNK_GAMESCOPE_NODE=<id|auto>; "auto" discovers the gamescope `Video/Source` node.
|
||||||
if let Ok(id) = std::env::var("PUNKTFUNK_GAMESCOPE_NODE") {
|
if let Ok(id) = std::env::var("PUNKTFUNK_GAMESCOPE_NODE") {
|
||||||
let node_id: u32 = if id.trim().eq_ignore_ascii_case("auto") {
|
let node_id: u32 = if id.trim().eq_ignore_ascii_case("auto") {
|
||||||
find_gamescope_node().ok_or_else(|| {
|
// Attach to the box-owned game-mode session, but FIRST make it run at the connecting
|
||||||
anyhow!(
|
// client's resolution (the box is headless, so its game-mode mode is ours to set).
|
||||||
"PUNKTFUNK_GAMESCOPE_NODE=auto but no running gamescope Video/Source node \
|
// Reuse if it already matches (fast, no restart); otherwise relaunch the box's own
|
||||||
was found — is the headless gamescope/Steam session up?"
|
// session at the client mode. Without this the client gets the box's default mode.
|
||||||
)
|
ensure_box_gamescope_mode(mode)?
|
||||||
})?
|
|
||||||
} else {
|
} else {
|
||||||
id.parse()
|
id.parse()
|
||||||
.context("PUNKTFUNK_GAMESCOPE_NODE must be a node id or 'auto'")?
|
.context("PUNKTFUNK_GAMESCOPE_NODE must be a node id or 'auto'")?
|
||||||
@@ -368,6 +367,150 @@ fn create_managed_session_steamos(mode: Mode) -> Result<VirtualOutput> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ATTACH at the CLIENT's resolution: ensure the box's own game-mode session is running at `mode`'s
|
||||||
|
/// output size, then return its capture node. Reuses the running session if it already matches (no
|
||||||
|
/// restart — the rock-solid fast path a stable client always hits); otherwise reconfigures + restarts
|
||||||
|
/// the box's OWN autologin `gamescope-session-plus@<client>` unit at the client mode. Restarting the
|
||||||
|
/// box's own unit (rather than spawning a competing one) avoids the autologin-respawn fight the old
|
||||||
|
/// MANAGED path hit. A headless box has no physical panel, so its game-mode resolution is ours to set;
|
||||||
|
/// Steam restarts only on an actual resolution CHANGE.
|
||||||
|
fn ensure_box_gamescope_mode(mode: Mode) -> Result<u32> {
|
||||||
|
let target = (mode.width, mode.height);
|
||||||
|
// Fast path: already at the client's resolution — just attach to the live node.
|
||||||
|
if current_gamescope_output_size() == Some(target) {
|
||||||
|
if let Some(node) = find_gamescope_node() {
|
||||||
|
tracing::info!(
|
||||||
|
w = mode.width,
|
||||||
|
h = mode.height,
|
||||||
|
node,
|
||||||
|
"gamescope: box game-mode session already at the client's resolution — reusing"
|
||||||
|
);
|
||||||
|
return Ok(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(unit) = running_autologin_gamescope_unit() else {
|
||||||
|
// No box-owned autologin session to reconfigure (a bare/foreign gamescope): attach to
|
||||||
|
// whatever node exists, accepting its resolution.
|
||||||
|
return find_gamescope_node().ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"no running gamescope Video/Source node — is the headless game mode up? \
|
||||||
|
(put the box into Steam Game Mode)"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
tracing::info!(
|
||||||
|
from = ?current_gamescope_output_size(),
|
||||||
|
to_w = mode.width,
|
||||||
|
to_h = mode.height,
|
||||||
|
hz = mode.refresh_hz,
|
||||||
|
%unit,
|
||||||
|
"gamescope: relaunching the box game-mode session at the client's resolution"
|
||||||
|
);
|
||||||
|
// The session reads SCREEN_WIDTH/HEIGHT (+ CUSTOM_REFRESH_RATES) from the user-manager
|
||||||
|
// environment; set them and restart the box's own unit.
|
||||||
|
systemctl_user(&[
|
||||||
|
"set-environment",
|
||||||
|
&format!("SCREEN_WIDTH={}", mode.width),
|
||||||
|
&format!("SCREEN_HEIGHT={}", mode.height),
|
||||||
|
&format!("CUSTOM_REFRESH_RATES={}", mode.refresh_hz.max(1)),
|
||||||
|
]);
|
||||||
|
systemctl_user(&["restart", &unit]);
|
||||||
|
// Wait for the relaunched session to come up at the new size and publish its capture node. The
|
||||||
|
// node appears when gamescope is up (well before Steam finishes booting); the caller's
|
||||||
|
// first-frame retry absorbs Steam's cold start.
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(45);
|
||||||
|
loop {
|
||||||
|
if current_gamescope_output_size() == Some(target) {
|
||||||
|
if let Some(node) = find_gamescope_node() {
|
||||||
|
tracing::info!(
|
||||||
|
node,
|
||||||
|
w = mode.width,
|
||||||
|
h = mode.height,
|
||||||
|
"gamescope: box game-mode session relaunched at the client's resolution"
|
||||||
|
);
|
||||||
|
return Ok(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
bail!(
|
||||||
|
"box game-mode session did not come up at {}x{} within 45s after relaunch \
|
||||||
|
(Steam may still be booting)",
|
||||||
|
mode.width,
|
||||||
|
mode.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output (capture) resolution `-W <w> -H <h>` of the running `gamescope` binary, parsed from its
|
||||||
|
/// `/proc/<pid>/cmdline`. `None` if no gamescope is running or the flags aren't present.
|
||||||
|
fn current_gamescope_output_size() -> Option<(u32, u32)> {
|
||||||
|
for entry in std::fs::read_dir("/proc").ok()?.flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let Some(pid) = name.to_str() else { continue };
|
||||||
|
if !pid.bytes().all(|b| b.is_ascii_digit()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Ok(raw) = std::fs::read(format!("/proc/{pid}/cmdline")) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let args: Vec<String> = raw
|
||||||
|
.split(|&b| b == 0)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| String::from_utf8_lossy(s).into_owned())
|
||||||
|
.collect();
|
||||||
|
// Match the gamescope BINARY by argv[0]'s basename — NOT /proc/<pid>/exe, which is commonly
|
||||||
|
// unreadable for the gamescope process (returns empty). The session wrapper scripts run as
|
||||||
|
// bash/sh (argv[0] != gamescope), so they're excluded; the -W/-H presence check below is the
|
||||||
|
// final filter.
|
||||||
|
let is_gamescope = args
|
||||||
|
.first()
|
||||||
|
.map(|a0| a0.rsplit('/').next().unwrap_or(a0) == "gamescope")
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !is_gamescope {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let flag = |names: &[&str]| -> Option<u32> {
|
||||||
|
args.iter().enumerate().find_map(|(i, a)| {
|
||||||
|
names
|
||||||
|
.contains(&a.as_str())
|
||||||
|
.then(|| args.get(i + 1).and_then(|v| v.parse().ok()))
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if let (Some(w), Some(h)) = (
|
||||||
|
flag(&["-W", "--output-width"]),
|
||||||
|
flag(&["-H", "--output-height"]),
|
||||||
|
) {
|
||||||
|
return Some((w, h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The running autologin gaming-mode unit (`gamescope-session-plus@<client>.service`), if any — the
|
||||||
|
/// box's own game-mode session, which [`ensure_box_gamescope_mode`] reconfigures + restarts.
|
||||||
|
fn running_autologin_gamescope_unit() -> Option<String> {
|
||||||
|
let out = Command::new("systemctl")
|
||||||
|
.args([
|
||||||
|
"--user",
|
||||||
|
"list-units",
|
||||||
|
"--type=service",
|
||||||
|
"--state=running",
|
||||||
|
"--no-legend",
|
||||||
|
"--plain",
|
||||||
|
"gamescope-session-plus@*.service",
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
String::from_utf8_lossy(&out.stdout)
|
||||||
|
.lines()
|
||||||
|
.filter_map(|l| l.split_whitespace().next())
|
||||||
|
.find(|u| u.starts_with("gamescope-session-plus@") && u.ends_with(".service"))
|
||||||
|
.map(|u| u.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop every running autologin gaming-mode session (`gamescope-session-plus@*.service`) so its
|
/// Stop every running autologin gaming-mode session (`gamescope-session-plus@*.service`) so its
|
||||||
/// single-instance Steam is free for our own host-managed session. Records the units so
|
/// single-instance Steam is free for our own host-managed session. Records the units so
|
||||||
/// [`schedule_restore_tv_session`] can restart them on disconnect. Our own session is the transient
|
/// [`schedule_restore_tv_session`] can restart them on disconnect. Our own session is the transient
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ package_punktfunk-host() {
|
|||||||
'xdg-desktop-portal-wlr: portal for the headless Sway session helper'
|
'xdg-desktop-portal-wlr: portal for the headless Sway session helper'
|
||||||
'punktfunk-web: browser management console (device pairing + status)')
|
'punktfunk-web: browser management console (device pairing + status)')
|
||||||
install=punktfunk-host.install
|
install=punktfunk-host.install
|
||||||
|
# User-editable config: the headless game-mode drop-in (see below) — don't clobber local edits.
|
||||||
|
backup=('etc/gamescope-session-plus/sessions.d/steam')
|
||||||
local R; R="$(_repo)"; local T="$srcdir/target/release"
|
local R; R="$(_repo)"; local T="$srcdir/target/release"
|
||||||
|
|
||||||
install -Dm0755 "$T/punktfunk-host" "$pkgdir/usr/bin/punktfunk-host"
|
install -Dm0755 "$T/punktfunk-host" "$pkgdir/usr/bin/punktfunk-host"
|
||||||
@@ -100,6 +102,11 @@ package_punktfunk-host() {
|
|||||||
install -Dm0644 "$R/scripts/host.env.example" "$pkgdir/usr/share/punktfunk/host.env.example"
|
install -Dm0644 "$R/scripts/host.env.example" "$pkgdir/usr/share/punktfunk/host.env.example"
|
||||||
install -Dm0644 "$R/packaging/bazzite/host.env" "$pkgdir/usr/share/punktfunk/host.env.bazzite"
|
install -Dm0644 "$R/packaging/bazzite/host.env" "$pkgdir/usr/share/punktfunk/host.env.bazzite"
|
||||||
install -Dm0644 "$R/packaging/kde/host.env" "$pkgdir/usr/share/punktfunk/host.env.kde"
|
install -Dm0644 "$R/packaging/kde/host.env" "$pkgdir/usr/share/punktfunk/host.env.kde"
|
||||||
|
# Headless GAME-mode fix: gamescope-session-plus drop-in that uses the headless backend when no
|
||||||
|
# display is connected (so SteamOS/Bazzite "Switch to Game Mode" works on a display-less streaming
|
||||||
|
# host). No-op on display-attached boxes; sourced as /etc/gamescope-session-plus/sessions.d/steam.
|
||||||
|
install -Dm0644 "$R/packaging/bazzite/gamescope-headless-session" \
|
||||||
|
"$pkgdir/etc/gamescope-session-plus/sessions.d/steam"
|
||||||
install -Dm0644 "$R/api/openapi.json" "$pkgdir/usr/share/punktfunk/openapi.json"
|
install -Dm0644 "$R/api/openapi.json" "$pkgdir/usr/share/punktfunk/openapi.json"
|
||||||
install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-MIT"
|
install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-MIT"
|
||||||
install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-APACHE"
|
install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-APACHE"
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# punktfunk: headless game-mode fallback for gamescope-session-plus.
|
||||||
|
#
|
||||||
|
# Installed as /etc/gamescope-session-plus/sessions.d/steam. The gamescope-session-plus launcher
|
||||||
|
# SOURCES this (shell, with `set -a` so assignments auto-export) AFTER its /usr/share defaults, so it
|
||||||
|
# can override the session's gamescope flags.
|
||||||
|
#
|
||||||
|
# Why: on a box with NO connected display (a dedicated streaming host), the stock Steam game mode runs
|
||||||
|
# gamescope's DRM backend against a physical panel (`--prefer-output *,eDP-1`). With nothing to scan
|
||||||
|
# out, gamescope crashes on launch; after 5 strikes Bazzite/SteamOS force-selects the desktop session
|
||||||
|
# and "Switch to Game Mode" appears broken. Falling back to gamescope's HEADLESS backend makes game
|
||||||
|
# mode render entirely offscreen and expose a PipeWire node, which the punktfunk host captures and
|
||||||
|
# streams — full gamescope game mode (per-game res / FSR / HDR / VRR / frame-limit), no monitor needed.
|
||||||
|
#
|
||||||
|
# Safe by construction:
|
||||||
|
# * NO-OP when any display is connected -> the normal DRM game mode runs unchanged.
|
||||||
|
# * Only sets values that are still unset (`: "${VAR:=...}"`), so the punktfunk host's per-client
|
||||||
|
# mode (SCREEN_WIDTH/SCREEN_HEIGHT injected via systemd-run for a managed session) still wins.
|
||||||
|
if ! grep -qx connected /sys/class/drm/*/status 2>/dev/null; then
|
||||||
|
: "${BACKEND:=headless}"
|
||||||
|
: "${SCREEN_WIDTH:=1920}"
|
||||||
|
: "${SCREEN_HEIGHT:=1080}"
|
||||||
|
fi
|
||||||
@@ -20,13 +20,25 @@ PUNKTFUNK_ZEROCOPY=1
|
|||||||
# PUNKTFUNK_COMPOSITOR=kwin|mutter|wlroots|gamescope
|
# PUNKTFUNK_COMPOSITOR=kwin|mutter|wlroots|gamescope
|
||||||
# PUNKTFUNK_INPUT_BACKEND=libei|wlr|gamescope|uinput
|
# PUNKTFUNK_INPUT_BACKEND=libei|wlr|gamescope|uinput
|
||||||
#
|
#
|
||||||
# In Gaming Mode the host MANAGES a gamescope-session-plus at the CLIENT's resolution by default
|
# GAME MODE = ATTACH (the box owns its session; the host follows). The box decides whether it's in
|
||||||
# (tears the TV's autologin down on connect; restores it on a debounced idle, reused on a quick
|
# Steam Gaming Mode or a Desktop — you switch with the normal Steam UI / "Switch to Desktop". The
|
||||||
# reconnect). To instead ATTACH to the running TV session at its own mode (couch-on-TV — gaming
|
# host just ATTACHES to whatever's live and captures it; it never tears the session down or relaunches
|
||||||
# stays live on the panel, no Steam restart), set:
|
# it. So switching Desktop<->Game is rock-solid, and when you disconnect the box STAYS in its current
|
||||||
# PUNKTFUNK_GAMESCOPE_ATTACH=1
|
# mode — reconnecting drops you right back where you were. The streamed resolution in game mode is the
|
||||||
# PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui # only for an ad-hoc bare-spawn fallback
|
# box's gamescope mode (see SCREEN_WIDTH/HEIGHT in /etc/gamescope-session-plus/sessions.d/steam).
|
||||||
|
PUNKTFUNK_GAMESCOPE_ATTACH=1
|
||||||
|
#
|
||||||
|
# Opt OUT to the MANAGED model instead (host tears the box's gamescope down on connect and launches
|
||||||
|
# its OWN at the CLIENT's exact resolution; restores on a debounced idle). Client-mode-following, but
|
||||||
|
# it does not coexist with a box-owned game-mode session — pick one:
|
||||||
|
# PUNKTFUNK_GAMESCOPE_MANAGED=1 # (and remove PUNKTFUNK_GAMESCOPE_ATTACH above)
|
||||||
#
|
#
|
||||||
# Follow a Gaming<->Desktop switch MID-STREAM (rebuild the backend in place, no reconnect). This is
|
# Follow a Gaming<->Desktop switch MID-STREAM (rebuild the backend in place, no reconnect). This is
|
||||||
# ON BY DEFAULT on Bazzite/SteamOS (the host detects the platform); set =0 to disable it:
|
# ON BY DEFAULT on Bazzite/SteamOS (the host detects the platform); set =0 to disable it:
|
||||||
# PUNKTFUNK_SESSION_WATCH=0
|
# PUNKTFUNK_SESSION_WATCH=0
|
||||||
|
#
|
||||||
|
# HEADLESS GAME MODE: on a box with no display attached, Bazzite's "Switch to Game Mode" normally
|
||||||
|
# crashes (gamescope's DRM backend has no panel to drive). The host package ships
|
||||||
|
# /etc/gamescope-session-plus/sessions.d/steam, which auto-falls-back to gamescope's HEADLESS backend
|
||||||
|
# when no display is connected — so game mode boots offscreen and streams, with no config here. It's a
|
||||||
|
# no-op on display-attached boxes. (The host then auto-detects Gaming and streams it.)
|
||||||
|
|||||||
@@ -233,6 +233,13 @@ install -Dm0644 packaging/kde/host.env %{buildroot}%{_datadir}/%
|
|||||||
# screencast/virtual-output grant ships as io.unom.Punktfunk.Host.desktop, installed above).
|
# screencast/virtual-output grant ships as io.unom.Punktfunk.Host.desktop, installed above).
|
||||||
install -d %{buildroot}%{_datadir}/%{name}/bazzite
|
install -d %{buildroot}%{_datadir}/%{name}/bazzite
|
||||||
install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh
|
install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh
|
||||||
|
# Headless GAME-mode fix: a gamescope-session-plus sessions.d drop-in that falls back to gamescope's
|
||||||
|
# headless backend when no display is connected (so "Switch to Game Mode" works on a display-less
|
||||||
|
# streaming host instead of crashing + 5-striking back to desktop). No-op on display-attached boxes.
|
||||||
|
# Sourced by gamescope-session-plus as /etc/gamescope-session-plus/sessions.d/steam (after its
|
||||||
|
# /usr/share defaults). Harmless on non-gamescope systems (the file is simply never read).
|
||||||
|
install -Dm0644 packaging/bazzite/gamescope-headless-session \
|
||||||
|
%{buildroot}/etc/gamescope-session-plus/sessions.d/steam
|
||||||
install -Dm0644 api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json
|
install -Dm0644 api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json
|
||||||
|
|
||||||
%if %{with web}
|
%if %{with web}
|
||||||
@@ -262,6 +269,9 @@ install -Dm0644 web/web.env.example %{buildroot}%{_datadir}/punkt
|
|||||||
%{_userunitdir}/punktfunk-host.service
|
%{_userunitdir}/punktfunk-host.service
|
||||||
%{_userunitdir}/punktfunk-kde-session.service
|
%{_userunitdir}/punktfunk-kde-session.service
|
||||||
%{_datadir}/applications/io.unom.Punktfunk.Host.desktop
|
%{_datadir}/applications/io.unom.Punktfunk.Host.desktop
|
||||||
|
%dir /etc/gamescope-session-plus
|
||||||
|
%dir /etc/gamescope-session-plus/sessions.d
|
||||||
|
%config(noreplace) /etc/gamescope-session-plus/sessions.d/steam
|
||||||
%dir %{_datadir}/%{name}
|
%dir %{_datadir}/%{name}
|
||||||
%{_datadir}/%{name}/*
|
%{_datadir}/%{name}/*
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user