The Goal

I have a Linux desktop that doubles as my workstation and gaming machine. The CPU is a Ryzen 7900X - which has integrated graphics - paired with a discrete RX 9070 XT for gaming. Two monitors sit on my desk connected to the motherboard’s video output (iGPU), and a long HDMI cable runs from the RX 9070 XT to a TV in the living room.

The idea is simple: play games on the TV without touching what’s running on the desk. On Windows this would mean “just connect the TV and extend the desktop.” On Linux, I want something better - a fully independent gaming session on the TV, with Steam Big Picture UI, HDR, VRR, and gamepad-only control, while my Wayland desktop on the desk keeps running untouched.

Linux Multi-Seat

Linux (via logind) supports multiple seats - independent sets of input and output devices with their own graphical session. Normally everything runs on seat0. The goal here is to assign the dGPU and the TV to seat1, then run a dedicated Steam + gamescope session on it, fully isolated from my Sway desktop on seat0.

Step 1: Assign the dGPU to seat1

The first step is telling udev which DRM device belongs to seat1, but first you need to know which card node corresponds to which GPU. The stable way is to cross-reference by-path symlinks with lspci:

$ ls -la /dev/dri/by-path/
lrwxrwxrwx ... pci-0000:03:00.0-card -> ../card1
lrwxrwxrwx ... pci-0000:03:00.0-render -> ../renderD128
lrwxrwxrwx ... pci-0000:0f:00.0-card -> ../card2
lrwxrwxrwx ... pci-0000:0f:00.0-render -> ../renderD129

$ lspci | grep -i VGA
03:00.0 VGA compatible controller: ... Navi 48 [Radeon RX 9070/9070 XT] (rev c0)
0f:00.0 VGA compatible controller: ... Raphael (rev c2)

So card1 is the RX 9070 XT and card2 is the Ryzen iGPU. Note there is no card0 on this machine - that gap is normal and depends on driver probe order at boot.

# /etc/udev/rules.d/72-seat-gaming.rules
SUBSYSTEM=="drm", KERNEL=="card1", TAG+="seat1", ENV{ID_SEAT}="seat1"
SUBSYSTEM=="drm", KERNEL=="card1-*", TAG+="seat1", ENV{ID_SEAT}="seat1"

The second rule covers connector nodes (card1-HDMI-A-1, etc.), which also need the seat tag for logind to fully hand the device over.

Step 2: Keep Sway off the dGPU

Without intervention, wlroots (the backend Sway uses) probes all available DRM devices at startup. On my machine the iGPU ends up as card2, so I pin Sway to it system-wide:

# /etc/environment
WLR_DRM_DEVICES=/dev/dri/card2

Without this, Sway would claim the RX 9070 XT and the seat1 session would fight it for the device.

Step 3: The Gamescope Session Script

Steam runs inside gamescope - Valve’s gaming-focused Wayland compositor. It handles HDR, VRR (adaptive sync), resolution scaling, and all the things you want when outputting to a TV. The session is launched by the script below, registered as a LightDM session type:

#!/bin/bash
# /usr/local/bin/gamescope-steam-session

sleep 3

exec &> /tmp/gamescope-session.log

export WLR_LIBINPUT_NO_DEVICES=1
export PULSE_SINK="alsa_output.pci-0000_03_00.1.hdmi-stereo-extra3"

echo "=== Starting gamescope session ==="
echo "User: $(whoami)"
echo "XDG_SESSION_ID: $XDG_SESSION_ID"
echo "XDG_SEAT: $XDG_SEAT"
env | sort

while true; do
    echo "=== Launching gamescope $(date) ==="

    gamescope \
        --backend drm \
        -W 3840 -H 2160 \
        -r 120 \
        -f \
        -e \
        --prefer-output HDMI-A-1 \
        --prefer-vk-device 1002:7550 \
        --force-grab-cursor \
        --adaptive-sync \
        --hdr-enabled \
        -- steam-native -gamepadui

    echo "=== Gamescope exited with code $? ==="
    sleep 3
done

A few things worth calling out:

  • WLR_LIBINPUT_NO_DEVICES=1 - seat1 has no keyboard or mouse assigned, but wlroots still complains about missing input devices without this flag
  • --backend drm - gamescope runs as a top-level KMS/DRM compositor directly on the hardware, not nested inside another Wayland session; required here since seat1 has no other compositor
  • -f - fullscreen
  • -e - enables Steam integration mode; gamescope embeds into Steam’s own compositor flow, which is what makes the Big Picture overlay and in-game features work correctly
  • --force-grab-cursor - keeps the cursor captured inside gamescope so it doesn’t escape to an empty seat with no display manager
  • --prefer-output HDMI-A-1 - selects which output on the dGPU to use; useful if the card has multiple connectors
  • PULSE_SINK - routes audio to the HDMI output on the RX 9070 XT, which goes to the TV; find your own sink name with pactl list sinks short, the exact string is machine-specific
  • --prefer-vk-device 1002:7550 - PCI ID of the RX 9070 XT; tells gamescope which Vulkan device to use when both GPUs are visible
  • --adaptive-sync and --hdr-enabled - VRR and HDR, the TV supports both
  • -W 3840 -H 2160 -r 120 - 4K at 120 Hz
  • steam-native -gamepadui - Steam in Big Picture mode, fully navigable with a controller
  • The while true loop exists because Steam occasionally crashes; without it the entire session would die silently and require a manual LightDM restart to recover
  • sleep 3 at the top gives the DRM subsystem time to finish handing the device over after logind claims the seat; the session script starts immediately after autologin and the device isn’t always ready yet
  • env | sort dumps the full environment to the log on startup - debug scaffolding left in because it’s been useful for troubleshooting session issues

For LightDM to recognize gamescope-steam as a valid session name, the script needs a corresponding .desktop entry:

# /usr/share/wayland-sessions/gamescope-steam.desktop
[Desktop Entry]
Name=Gamescope Steam
Comment=Steam Big Picture in Gamescope
Exec=/usr/local/bin/gamescope-steam-session
Type=Application
DesktopNames=gamescope

Step 4: LightDM Configuration

LightDM spawns both sessions. The key setting is logind-load-seats=true, which tells LightDM to create a display manager seat for every seat logind knows about - including seat1 after the udev rules fire:

# /etc/lightdm/lightdm.conf

[LightDM]
logind-load-seats=true

[Seat:*]
greeter-session=lightdm-gtk-greeter

[Seat:seat0]
user-session=sway

[Seat:seat1]
autologin-user=oleksandr
autologin-session=gamescope-steam
autologin-user-timeout=0
greeter-session=lightdm-gtk-greeter

seat1 autologins immediately into the gamescope session - no greeter needed since there’s no keyboard or mouse at the TV.

What Works

Booting the machine brings up Sway on the desk monitors and a Steam Big Picture UI on the TV simultaneously. Picking up a controller and starting a game just works. VRR and HDR come through gamescope correctly. Audio goes to the TV over HDMI.

The DualSense connects over Bluetooth and just worked without any seat configuration on my end - I didn’t have to manually attach it to seat1 or touch any udev rules for it. Your mileage may vary depending on controller type and connection method.

What Doesn’t

The setup is rigid. Because LightDM owns both seats from boot and manages their lifecycle, there’s no clean way to reconfigure things on the fly:

GPU passthrough to a VM. I occasionally pass the RX 9070 XT through to a Windows VM for CAD software. That means detaching the GPU from seat1. Currently the only way to get seat1 back afterwards is to restart LightDM - which also kills my entire Sway session on seat0. Every time I want to do CAD work and then go back to TV gaming, I lose all my open windows.

Playing at the desk instead of the TV. Sometimes I want to run Steam on seat0 and play on my desk monitors. That means killing the gamescope session on seat1 first (otherwise the GPU is busy). But bringing seat1 back later again requires a full LightDM restart.

In short: the TV session is always-on or always-off at boot time, with no way to pause, stop, or restart it independently from the main desktop session.

Part 2

Part 2 will explore replacing this static LightDM-managed setup with something more dynamic - ideally a way to start and stop the seat1 gaming session on demand without affecting seat0, and without requiring a display manager restart.