Files
punktfunk/scripts/punktfunk-steam-session.service
T
enricobuehler 9128bc3836 feat(host): attach to a running gamescope session (Bazzite headless Steam)
Bazzite (and SteamOS-like hosts) run Steam Big Picture inside their OWN
gamescope-session-plus session. Nesting a second gamescope+Steam can't work — the
second Steam sees the first and exits, taking the nested gamescope down with it
(crash in its exit handlers), killing both video and input. The robust model is to
let punktfunk OWN that session: run gamescope-session-plus headless at the client's
resolution (full Steam Deck UI polish: MangoApp, VRR, controller config) and have
the host ATTACH to it rather than spawn its own.

The video half already existed (PUNKTFUNK_GAMESCOPE_NODE=<id> attaches to a
PipeWire node). This finishes it:
- PUNKTFUNK_GAMESCOPE_NODE=auto discovers the gamescope Video/Source node, so the
  (dynamic) node id needn't be hand-wired.
- The attach path now also points the libei injector at the running session's EIS
  socket: find_gamescope_eis_socket() scans XDG_RUNTIME_DIR for gamescope-<N>-ei,
  connect()-probes each (stale dead-session sockets refuse), and writes the newest
  live one to the relay file the injector reads. So input reaches the attached
  session with zero manual config.

scripts/punktfunk-steam-session.service: a systemd --user unit that runs
gamescope-session-plus headless at a configured resolution, with the one-time
headless-appliance setup (linger + multi-user.target) documented inline.

Validated live on bazzite (RTX 4090): the full Steam Big Picture session streams
(1499 frames, p50 ~1ms) with mouse/keyboard injected into it (device resumed, all
caps, emitted=true), node + EIS socket both auto-detected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:55:12 +00:00

42 lines
2.1 KiB
Desktop File

# punktfunk headless Steam session — systemd USER unit (Bazzite / SteamOS-like hosts).
#
# Runs the FULL gamescope-session-plus Steam Deck UI (MangoApp/VRR/controller config — all the
# polish) headless, at your streaming client's resolution, with no physical display. punktfunk's
# host then ATTACHES to it (capture its PipeWire node + inject into its libei socket) instead of
# nesting a second, conflicting Steam — set the host's PUNKTFUNK_GAMESCOPE_NODE=auto +
# PUNKTFUNK_INPUT_BACKEND=gamescope (see scripts/host.env.example).
#
# Prereq — free Steam from the local gaming session (so this owns it), on a headless box:
# sudo loginctl enable-linger $USER # user services run without a graphical login
# sudo systemctl set-default multi-user.target # don't auto-start the local gaming session
# sudo systemctl isolate multi-user.target # stop it now (or reboot)
#
# Install:
# mkdir -p ~/.config/systemd/user && cp scripts/punktfunk-steam-session.service ~/.config/systemd/user/
# # edit SCREEN_WIDTH/HEIGHT below to your client's resolution, then:
# systemctl --user daemon-reload && systemctl --user enable --now punktfunk-steam-session
#
# Revert to local gaming mode anytime:
# systemctl --user disable --now punktfunk-steam-session
# sudo systemctl set-default graphical.target && sudo systemctl isolate graphical.target
[Unit]
Description=punktfunk headless Steam Big Picture session (gamescope-session-plus)
After=pipewire.service pipewire-pulse.service
Wants=pipewire.service
[Service]
# Headless gamescope at the streamed resolution. gamescope-session-plus reads all of these from
# the environment (see /usr/share/gamescope-session-plus/gamescope-session-plus). Set the WIDTH/
# HEIGHT to your client's mode; CUSTOM_REFRESH_RATES advertises selectable rates to Big Picture.
Environment=BACKEND=headless
Environment=SCREEN_WIDTH=5120
Environment=SCREEN_HEIGHT=1440
Environment=CUSTOM_REFRESH_RATES=60,120,240
Environment=STEAM_DISPLAY_REFRESH_LIMITS=60,240
ExecStart=/usr/share/gamescope-session-plus/gamescope-session-plus steam
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target