Skip to content

Add AYN button toggle for a bottom-screen performance HUD - #90

Open
brycesub wants to merge 22 commits into
virtudude:mainfrom
brycesub:bottom-screen-perf
Open

Add AYN button toggle for a bottom-screen performance HUD#90
brycesub wants to merge 22 commits into
virtudude:mainfrom
brycesub:bottom-screen-perf

Conversation

@brycesub

Copy link
Copy Markdown
Contributor

Summary

  • add an AYN-button listener that swaps MangoApp between the nested top-screen overlay and a full-stat bottom-screen HUD
  • pin the desktop MangoApp to the Thor's DSI-1 panel, force fullscreen behavior, and prevent it from stealing focus
  • merge packaged KWin rules into the active per-user rulebook before nested gaming starts
  • bind both HUD services to nested gaming and include VRAM in the bottom-screen statistics

Depends on #80, which provides the nested-gaming session this HUD integrates with.

Validation

  • all tests/*.sh
  • ShellCheck on the HUD, nested-gaming, bootstrap, and test scripts
  • just check
  • device-side systemd-analyze --user verify for both HUD units
  • live AYN Thor validation: bottom window measured 1240x1080 at the DSI-1 offset, remained fullscreen/above/skip-taskbar, and did not become the active window

@brycesub brycesub mentioned this pull request Jul 13, 2026
@brycesub
brycesub force-pushed the bottom-screen-perf branch from c5cebae to 8de7ea2 Compare July 15, 2026 02:03
brycesub and others added 22 commits July 24, 2026 17:57
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XMbeBSJJG1pNfdqkUcG1gn
Two clean-install bugs, one root cause each:

Steam applies client updates by exiting and relying on its wrapper to
relaunch it (sometimes twice in a row, as during first-run onboarding).
The embedded session gets this from SDDM Relogin restarting the whole
gamescope session; nested mode had no equivalent, so the update left a
Steam-less gamescope on screen and a dead gaming mode. Gaming mode now
runs as armada-nested-gaming.service (user unit) with Restart=always:
nested-gaming launches gamescope with the -R readiness pipe (the same
handshake gamescope-session-plus uses), runs Steam as its own child with
the environment gamescope would hand it (DISPLAY, GAMESCOPE_WAYLAND_DISPLAY,
host WAYLAND_DISPLAY unset), and exits when Steam does so systemd brings
the pair back up. StartLimit caps crash loops and PartOf=
graphical-session.target stops the unit at logout instead of letting it
thrash without a compositor.

The desktop "Return to Game Mode" icon went steamos-session-select →
os-session-select → sudo session-control switch-gamemode: a full plasma
logout + SDDM relogin (~20s of desktop) even on nested devices, so
impatient taps killed the Steam that was already starting - mid
update-install during onboarding. os-session-select now starts/stops the
unit directly when the desktop compositor is alive, like steamos-manager
does; both switch directions are instant and idempotent, which also makes
the icon's double-activation per tap harmless. steamos-manager and
desktop-bootstrap converge on the same unit, replacing the transient
systemd-run instances and the pgrep guard they needed.

Validated on AYN Thor: Steam exit auto-relaunches in ~2s into a fresh
gamescope; icon and D-Bus switches instant both ways including
double-fired taps; explicit stop is never restarted.

Part of virtudude#28 phase 2

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011roK8EGErjD8eEjjZNNW9w
A gamescope-wl the unit does not own (stale instance from an older
deployment, manual launch) survived SwitchToDesktopMode's unit stop and
left Steam waiting on "Switching to desktop..." indefinitely. Both switch
paths now pkill gamescope-wl after stopping the unit; for unit-owned
processes this duplicates the SIGTERM systemd already sent, so it is
harmless in the normal case.

Part of virtudude#28 phase 2

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011roK8EGErjD8eEjjZNNW9w
KWin asks the kscreen OSD service to show its layout picker whenever
two outputs appear in a combination it has not stored yet (kwin
workspace.cpp, updateOutputConfiguration). On dual-screen devices that
is the very first boot, so the picker pops up over Steam onboarding.
The kded kscreen module is not involved on Wayland; KWin has no config
knob for this.

Mask the user service instead: KWin's D-Bus call is fire-and-forget,
so the only trace is a one-line activation failure from dbus-broker.
Display Settings still work; only the picker OSD (and the output
identify overlay) go away.

Validated on the AYN Thor: with kwinoutputconfig.json absent, the
journal shows the activation request refused and no OSD appears.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ASwEJacP64yJTjo66FRdu9
Gaming mode covers the top screen, so the desktop panel and icons
belong on the bottom one, DS-style. Make the secondary output the
priority-1 display and scale it to 150% to match the primary. Rotate
it per the new ARMADA_SECONDARY_ORIENTATION (the Thor's bottom panel
is portrait-native like the top), and center it below the primary in
post-scale logical coordinates.

Validated on the AYN Thor: DSI-1 lands at priority 1, scale 1.5,
rotation right, position 226,720 (centered under the 1280x720 top
screen).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ASwEJacP64yJTjo66FRdu9
Conflicts= carries no ordering, so a toggle could briefly run both
MangoApps against gamescope's single-consumer stats queue. An After=
edge between the HUD units makes systemd finish the old HUD's stop
before the new one starts, in both directions.

Also tighten the listener-guard test literal and fold the review's
focus-steal and slider-tracking checks into the validation checklist.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018YYLGYK7FVvW3jP7WVrNjg
@brycesub
brycesub force-pushed the bottom-screen-perf branch from 8de7ea2 to 30ddbcb Compare July 24, 2026 23:06
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.

1 participant