Skip to content

Nested gaming session for dual-screen devices (Thor phase 2)#80

Open
brycesub wants to merge 9 commits into
virtudude:mainfrom
brycesub:thor-nested-gaming
Open

Nested gaming session for dual-screen devices (Thor phase 2)#80
brycesub wants to merge 9 commits into
virtudude:mainfrom
brycesub:thor-nested-gaming

Conversation

@brycesub

@brycesub brycesub commented Jul 8, 2026

Copy link
Copy Markdown

Phase 2 of AYN Thor dual-screen support (#28): gaming mode with both screens live — Steam on the top screen, a full plasma desktop on the bottom — and near-instant mode switching.

Builds on #78 (its two commits are included here; this PR reduces to the two thor-nested-gaming commits once #78 merges). Also needs virtudude/armada-packages#2 (gamescope wl_touch patch), or the top screen loses touch in nested mode.

Architecture

With ARMADA_GAMING_SESSION=nested (set for Thor), the compositor stack is inverted: KWin stays the only DRM master driving both panels, and gaming mode runs as a nested gamescope fullscreen window on the primary screen (/usr/libexec/armada/nested-gaming).

  • Switching: steamos-manager's session-bus instance handles Steam's SwitchToDesktopMode/SwitchToGameMode by stopping/starting the nested gamescope — the desktop is already underneath, so switching is near-instant (no SDDM session swap, no logout). Single-screen devices and the system-bus instance keep the existing session-swap path.
  • Boot: armada-session-default still resets to "gamemode" each boot; on nested devices that session is now armada-plasma, and desktop-bootstrap launches the nested gamescope on fresh session start. Boot lands in gaming mode with the desktop live on the bottom screen.
  • The embedded gamescope session is no longer used on nested devices (it remains untouched for every other device).

Validated on hardware (AYN Thor)

  • Cold boot → Steam on top + desktop on bottom, automatically
  • Instant switch in both directions through Steam's UI, repeatedly, plus stability across multiple days of use
  • Game performance indistinguishable from the embedded session (user-tested)
  • Brightness (via the AYN Thor second screen support (userspace) #78 backlight steering), gamepad, and per-screen touch all work; the bottom touchscreen drives the desktop while gaming

Refresh switching

Steam's per-game refresh switching works through nested-refresh-bridge: it watches the GAMESCOPE_DYNAMIC_REFRESH atom Steam sets on the nested Xwayland root and applies the nearest supported panel mode via kscreen-doctor (restoring the max rate when dynamic refresh is off or the session ends).

Design doc: docs/superpowers/specs/ (phase 2) — happy to include it in the PR if wanted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn

brycesub and others added 2 commits July 7, 2026 14:13
With the bottom panel enabled in the kernel, the top screen becomes
DSI-2 and a second backlight (ae94000.dsi.0) appears that Steam picks
up for brightness control instead of the top panel's.

- ayn-thor.conf: primary connector is DSI-2; declare the primary
  backlight, secondary connector, and both touchscreen device names
- steamos-priv-write: accept backlight brightness writes and steer them
  to ARMADA_PRIMARY_BACKLIGHT, rescaling between brightness ranges
  (bottom is 0-255, top is 0-4096)
- desktop-bootstrap/setup-dual-screen: one-time desktop layout for
  dual-screen devices - primary on top at priority 1, secondary
  centered below, each touchscreen pinned to its own output (KWin
  otherwise maps both to the same screen)

Requires the armada-packages kernel with the Thor bottom panel enabled;
on the old kernel DSI-2 does not exist and gamescope falls back to the
only connected output, so ordering is safe.

Fixes virtudude#28 (phase 1: no bottom-screen UI in gaming mode yet)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
Gamescope only lights up the primary panel, but the secondary
touchscreen still feeds it input - fingers resting on the dark glass
move Steam's cursor. Inhibit the device (via /sys/class/input/*/inhibited,
made wheel-writable like the other session-managed sysfs knobs) when the
gaming session starts and re-enable it when the desktop session starts.

A future Armada Control toggle could expose this as a user choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
@brycesub brycesub mentioned this pull request Jul 8, 2026
@brycesub

brycesub commented Jul 8, 2026

Copy link
Copy Markdown
Author

Pushed the refresh-rate bridge, closing the known limitation in the PR description: nested-refresh-bridge watches GAMESCOPE_DYNAMIC_REFRESH on the nested Xwayland root (the same atom Steam sets for the embedded DRM backend) and applies the nearest supported panel mode through kscreen-doctor — 60→60Hz, 90→snaps to 120Hz, 0/dynamic-off→restores max. nested-gaming now exports STEAM_DISPLAY_REFRESH_LIMITS so Steam offers the refresh UI. Validated on the Thor with the exact atom writes Steam performs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn

@virtudude

Copy link
Copy Markdown
Owner

Will take me some time to review and test all this but appreciate the work! Very exciting for the Thor.

@brycesub

brycesub commented Jul 8, 2026

Copy link
Copy Markdown
Author

Found during end-to-end image testing: the persistent desktop session brings KDE powerdevil's idle suspend with it, and systemd suspend wedges on these devices (stuck suspend.target job that blanks both panels and blocks everything until a forced reboot — very likely also the intermittent black screens seen during earlier prototype testing). The embedded session sidesteps this with armada's fake-suspend, but the nested session currently has no equivalent.

Follow-up needed before this ships as the Thor default: powerdevil defaults that disable idle suspend on nested devices (or wire the desktop session into the fake-suspend flow). I'll push a commit for this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn

brycesub and others added 7 commits July 8, 2026 18:53
kscreen-doctor -o -j prints the human-readable listing after the JSON
document, so json.loads raised JSONDecodeError and the script died
before applying the layout or pinning the touchscreens. On first boot
that left both touchscreens mapped to the same output: taps on the top
screen landed on the bottom desktop, where a stray double-tap on the
Return to Game Mode icon logged the session out.

Request JSON alone and parse only the first document so trailing text
can never take the script down again. Verified on an AYN Thor: layout
applied, both touchscreens pinned to their own panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeRkgMBMwWNcnwMk4j5kVx
With ARMADA_GAMING_SESSION=nested (Thor), gaming mode runs as a nested
gamescope fullscreen on the primary screen inside the persistent desktop
session, with the plasma desktop live on the secondary screen. Mode
switches become near-instant: SwitchToDesktopMode just ends the nested
gamescope (the desktop is already underneath) and SwitchToGameMode
launches it via a transient user unit.

steamos-manager handles the switch in its session-bus instance only;
the embedded session-swap path is unchanged for single-screen devices
and for the system-bus instance.

Validated on AYN Thor: instant switch both directions through Steam's
UI, game performance indistinguishable from embedded gaming mode.

Part of virtudude#28 phase 2 (design doc in docs/superpowers/specs/)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
armada-session-default resets the SDDM session to "gamemode" each boot;
on ARMADA_GAMING_SESSION=nested devices that session is now the plasma
session, and desktop-bootstrap launches the nested gamescope on a fresh
session start. Boot lands in gaming mode with the desktop already live
on the secondary screen, and the embedded gamescope session is no longer
used on these devices.

Known limitation: nested gaming runs at the mode KWin selected for the
primary output (120Hz on Thor); Steam's per-game refresh switching is
not bridged yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
Steam communicates refresh switching by setting GAMESCOPE_DYNAMIC_REFRESH
on gamescope's Xwayland root; the embedded DRM backend applies it but
nested gamescope cannot modeset the host output. nested-refresh-bridge
watches the atom on the nested Xwayland (stdbuf -oL: xprop block-buffers
into pipes) and applies the nearest supported panel rate via
kscreen-doctor, restoring the max rate when dynamic refresh is disabled
or the gaming session ends. nested-gaming exports
STEAM_DISPLAY_REFRESH_LIMITS so Steam offers the refresh UI, and Steam's
in-between requests (e.g. 90 on a 60/120 panel) snap to the nearest
mode, ties to the higher.

Validated on hardware via the exact atom writes Steam performs:
60 -> panel 60Hz, 90 -> 120Hz, 0 -> 120Hz.

Closes the known limitation from the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
Pairs with the armada-packages gamescope patch that reads
GAMESCOPE_NESTED_REFRESH_RATES: without it Steam's refresh slider
collapses to the single current rate because the nested backend cannot
enumerate the host output's modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
Pass --prefer-output with the device's primary connector; the armada
gamescope patch makes the nested Wayland backend fullscreen onto that
output instead of whichever one the host compositor considered active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
PowerDevil's idle detection does not count gamepad input as activity;
with the persistent desktop session hosting nested gaming it suspends
the device mid-game (fake-suspend blanks both screens and everything
looks wedged until the power button or a reboot). This also explains an
intermittent all-black-screens report during prototype testing.

Ship a system default that disables automatic suspend on AC and
battery; suspend stays available through the power button
(powerbuttond -> fake-suspend), and users can re-enable idle suspend in
System Settings. Low-battery behavior keeps the upstream default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
@brycesub brycesub force-pushed the thor-nested-gaming branch from 09fafb4 to 920ef43 Compare July 8, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants