The real bug behind every "60 Hz stuck in Gaming Mode" report:
- DeckShift installs `gamescope` from Arch's `extra` repo (upstream
Valve binary). That binary does NOT have `--custom-refresh-rates`.
The flag is a ChimeraOS-fork (`gamescope-plus`) addition that
never landed upstream, and the fork is not packaged in AUR for
64-bit Arch.
- The AUR `gamescope-session-git` script (OpenGamingCollective,
ex-ChimeraOS) was written assuming gamescope-plus. It feature-
detects via `gamescope_has_option "--custom-refresh-rates"` and
silently drops the value when missing.
- Net effect: CUSTOM_REFRESH_RATES from the env conf reaches the
session script but never reaches the gamescope binary. Every
refresh-rate selection in the DeckShift TUI since the project
began has been a paper fix; gamescope just launched at the
EDID-preferred mode (usually 60 Hz).
- v0.1.8's comma-list "fix" was correct on paper but the value
never reached the binary, so it didn't actually do anything on
Omarchy. The Framework Desktop user wasn't reporting a hardware-
specific bug — they were the first user to notice a bug that
affected everyone, because their 170 Hz monitor made the regression
obvious. On a 165 Hz Acer Nitro you'd just never notice 60 vs 165
in Steam BPM.
Fix: ./deckshift.sh now patches the installed gamescope-session-plus
in place, adding an elif fallback that uses `--nested-refresh` (a flag
present in every gamescope version) with the highest value from the
CUSTOM_REFRESH_RATES list as the launch rate. Marked with a
DECKSHIFT-NESTED-REFRESH-FALLBACK sentinel comment for idempotency,
re-applied on every install so AUR upgrades that clobber the file
don't silently regress refresh-rate handling.
Implementation: new patch_gamescope_session_plus() function in
deckshift.sh, called from setup_session_switching() after the AUR
install completes. Uses Python regex with a function-based replacement
(re.sub with a string-form replacement was the first attempt and got
caught by Python's backslash processing turning `\n` in the `tr`
argument into a real newline — function callbacks bypass that, so the
literal `\n` reaches the shell as intended).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported on a Framework Desktop (AMD AI MAX 380) + Gigabyte M27Q +
LG DualUp setup: with both monitors attached, gamescope would either
land on the wrong screen or refuse to start, and writing
OUTPUT_CONNECTOR=DP-X alone wasn't enough to fix it. The workaround
shipped by the user was to manually patch /usr/share/gamescope-session-plus
to disable the other monitor before launching gamescope.
DeckShift now handles this natively, without touching the
gamescope-session-plus script (which is ChimeraOS's, not ours):
- New env var OUTPUT_CONNECTOR_TO_DISABLE (single connector or
comma list) written to ~/.config/environment.d/gamescope-session-plus.conf
alongside the other display keys.
- switch-to-gaming reads it and runs `hyprctl keyword monitor X,disable`
for each listed connector BEFORE the SDDM restart, while Hyprland is
still alive (hyprctl needs a live IPC socket). The disable is
runtime-only — Hyprland's static config isn't touched — so when the
user returns from Gaming Mode the new Hyprland reads its config fresh
and the monitor comes back automatically. No re-enable step needed.
- Settings TUI exposes this as a "Hide monitor" main-menu item. The
picker lists every connected monitor EXCEPT the gaming one (so a
user can't accidentally pick the same connector they just set as
OUTPUT_CONNECTOR) and includes a "(clear)" entry.
Also fixes a latent bug in v0.1.10's config-path shortening:
${CONF/#$HOME/~} was supposed to render the conf path with ~ but
bash applies tilde-expansion to the replacement side, re-expanding ~
to $HOME and making the substitution a no-op. Escaped as \~ now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Banner, state panel, menu header, and menu items now share a single centred
panel column rather than each block centring itself independently. The TUI
feels visibly aligned in a Walker floating window of any width — no more
drifting elements off to the left while the menu floats to the right.
- Adaptive panel width: min(COLS − 6, 60), floored at 40.
- Terminal-width detection: `stty size </dev/tty` first (kernel-reported,
always reflects the live window) with `tput cols`/80 fallback. Fixes
off-centre rendering in freshly-spawned floating terminals whose terminfo
hasn't caught up to the compositor's actual size yet.
- Config-file path renders as `~/...` instead of `/home/<user>/...` so it
fits the panel.
- Unset resolution shows `<auto>` instead of `?x?` (matches the other unset
placeholders).
Internally: `pad_block` replaces `center_block`'s widest-line-centres-block
behaviour with a single shared left margin; `cmenu` left-aligns the menu
at the panel edge so the `> ` cursor sits in the same column as the state
panel's section headers above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-v0.1.8 users have a scalar CUSTOM_REFRESH_RATES=<rate> in their
gamescope-session-plus.conf that triggers the 60 Hz launch bug. v0.1.8
only fixed the TUI write path, so existing users had to re-open the TUI
and re-pick the rate to migrate. Installer now detects the legacy
scalar format and rewrites to the comma list (60,<rate>) on re-run,
then imports the new value into the running systemd user environment.
Idempotent — already-migrated configs are left alone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported by clutchmuffin: TUI saved e.g. CUSTOM_REFRESH_RATES=165 but
Gaming Mode always launched at 60 Hz. Two stacked bugs:
1. Settings TUI wrote ~/.config/environment.d/gamescope-session-plus.conf
but never reloaded systemd's user env, so the running user manager
(and therefore gamescope-session-plus@.service) still had the old
values until next login. Now flush_pending calls
`systemctl --user import-environment` / `unset-environment` for the
keys it just touched.
2. CUSTOM_REFRESH_RATES was written as a single scalar. Gamescope's
--custom-refresh-rates is a list of *switchable* rates, not a
launch-rate selector — and with no safe 60 Hz fallback in the list,
some DRM/NVIDIA paths drop to the EDID-preferred 60 Hz on first
launch. TUI now writes a comma list (e.g. 60,165). show_state and
confirm_risky_save de-list to the highest member for display /
validation.
Adds a troubleshooting README entry documenting the Steam BPM
client-side rate persistence as the canonical first-launch workaround.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Steam was failing to bootstrap on AMD systems during install. The homegrown
`setsid gtk-launch steam` path silently no-op'd when `gtk-launch` wasn't
available (gtk3 isn't a hard dep of `steam` or any AMD-specific package, so
leaner AMD setups never had it; NVIDIA systems usually pulled it in via
nvidia-settings).
Now delegates to `omarchy-install-gaming-steam`, which is the canonical
install + bootstrap path on Omarchy and is exercised on AMD/Intel/NVIDIA
by the upstream Omarchy tests. It's idempotent — `omarchy-pkg-add steam`
no-ops if Steam is already present.
Cleanup pass on dep lists now that the bootstrap is delegated:
- core_deps: drop `steam` (installed by omarchy-install-gaming-steam) and
`mesa-utils` (glxinfo/glxgears never called by the script).
- gpu_deps: drop `lib32-nvidia-utils`, `lib32-nvidia-580xx-utils`,
`lib32-vulkan-radeon`, `lib32-vulkan-intel` — all now installed by
`omarchy-install-gaming-gpu-lib32` (called by omarchy-install-gaming-steam).
- optional_deps: drop `xf86-video-amdgpu` — X11 DDX driver, useless under
Hyprland (Wayland-only).
- setup_requirements: drop `steam` from required_packages (same reason
as core_deps).
64-bit GPU drivers (`vulkan-radeon`, `vulkan-intel`, `nvidia-utils`, etc.)
kept because `omarchy-install-gaming-gpu-lib32` only handles the lib32 side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeckShift targets Omarchy only — the fallback added in 74b1a5b was
unnecessary defensive coding. Helper now just calls
omarchy-restart-walker directly. Also updated the file header to
remove the stale "distro-portability is the next direction" note.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After v0.1.4 fixed the portal race, a user reported that the clipboard
also broke after returning from Gaming Mode. Symptom: paste does
nothing, Walker's clipboard history is empty.
Same root cause as the portal stack: Walker's elephant backend holds
the wl-clipboard listener, which is bound to the dead Hyprland's
Wayland socket after the SDDM restart. Fixed by appending a Walker
restart to deckshift-portal-recovery, using the existing
omarchy-restart-walker helper when present and falling back to direct
systemctl --user restart of elephant.service +
app-walker@autostart.service when not (keeps the helper portable for
the planned non-Omarchy targets).
Reported and validated by the same user as the v0.1.4 fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous portal-recovery helper restarted xdg-desktop-portal-hyprland,
xdg-desktop-portal, pipewire, pipewire-pulse, and wireplumber in a single
systemctl --user restart call. That introduced a race where the portals
could come back up before wireplumber had finished rebuilding the node
graph, so the screencast portal would bind to nothing and "Share desktop"
would silently fail intermittently.
Reported by a user on the issue tracker: the cure is to (1) push the
live session env into D-Bus + systemd --user so D-Bus-activated portals
target the new Wayland socket, (2) stop portals first, (3) kill any
gamescope-era zombies (SIGTERM then SIGKILL for stragglers — SIGKILL
alone leaves stale D-Bus name registrations), (4) restart the pipewire
stack and wait for wireplumber, (5) bring portals back up last.
The exec-once marker guard and the initial 2s sleep are preserved so
this stays a no-op on normal logins and so the env-import sees a
populated WAYLAND_DISPLAY.
Bumps DECKSHIFT_VERSION from the stale 0.1.1 string in the file (last
shipped tag is 0.1.3) to 0.1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chromium/Firefox "Share desktop" / "Share window" silently broke after
the SDDM-restart cycle: xdg-desktop-portal-hyprland stays bound to the
killed Hyprland instance, so the new compositor's screencasts get no
frames. Tab sharing kept working because Chromium captures tabs
internally, bypassing the portal.
switch-to-desktop now drops /tmp/.deckshift-just-returned before the
SDDM restart. A new helper (/usr/local/bin/deckshift-portal-recovery),
exec-once'd from ~/.config/hypr/autostart.conf, checks for that marker
on every Hyprland start and bounces the portal + pipewire user services
when present. No marker = no-op.
README troubleshooting updated with both this and the suspend-access
fix from the previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
switch-to-gaming masks sleep/suspend/hibernate targets with --runtime
(symlinks in /run/systemd/system/). switch-to-desktop's plain `unmask`
plus a missing daemon-reload left logind's CanSuspend cache stale, so
`systemctl suspend` returned polkit "Access denied" back on desktop.
Add the --runtime unmask and a daemon-reload, plus matching NOPASSWD
sudoers entries so the desktop-side script can actually run them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: after exiting Gaming Mode (Super+Shift+R or Steam→Power→Switch
to Desktop) the laptop stayed pinned at CPU governor=performance and
power profile=performance until manually reset. Drained battery, ran hot.
Root causes:
- The wrapper's trap-based restore raced with `systemctl restart sddm`
in switch-to-desktop. SDDM teardown could SIGKILL the wrapper before
`restore_balanced_mode` finished its writes.
- restore_balanced_mode hardcoded "powersave" / "balanced" — wrong on
systems whose default is schedutil or power-saver.
- powerprofilesctl was called without sudo and without a polkit auth
rule, so it silently failed when the session had no auth agent.
Fix:
- On entry, gamescope-session-nm-wrapper writes the user's actual
pre-Gaming-Mode CPU governor + power profile to ~/.cache/deckshift/
saved-state.
- On exit, switch-to-desktop reads that file FIRST (synchronous, before
any pkill / sddm-restart) and applies the restore. Wrapper's trap
still runs as a backup.
- Restore reads from the saved-state file rather than guessing.
- powerprofilesctl is now invoked via `sudo -n` and added to the
NOPASSWD allowlist (%video ALL=(ALL) NOPASSWD: powerprofilesctl set *)
so the call always succeeds without a polkit prompt.
Verified on Acer Nitro (AMD APU + NVIDIA hybrid):
Before: stuck at performance/performance after exit.
After: back to powersave/balanced on Switch-to-Desktop, both via the
synchronous restore in switch-to-desktop and the wrapper trap.
Settings TUI (bin/deckshift-settings):
- Don't crash when the saved OUTPUT_CONNECTOR is no longer plugged in
(stale connector → empty mode list → grep + pipefail killed the script)
- Add [hybrid-nvidia] GPU mode for NVIDIA dGPU + AMD/Intel iGPU laptops:
sets __NV_PRIME_RENDER_OFFLOAD / __VK_LAYER_NV_optimus / __GLX_VENDOR_LIBRARY_NAME
so games inside gamescope render on NVIDIA while gamescope itself runs on
the iGPU (necessary on hybrid laptops where eDP is wired to the iGPU)
- Add [hybrid-amd] GPU mode for AMD dGPU + AMD/Intel iGPU laptops: sub-menu
to pick the dGPU, then sets DRI_PRIME + MESA_VK_DEVICE_SELECT
- Single GPU-mode line in the status panel covering all five states
(auto / NVIDIA direct / AMD direct / hybrid-nvidia / hybrid-amd)
- Switch GPU selection to clear-then-set so mode-switching never leaves
stale flags behind
Installer (deckshift.sh):
- Drop monitor / resolution / refresh selection from the installer entirely;
display keys are owned by the TUI now. Avoids stale OUTPUT_CONNECTOR
values when the configured display is later unplugged
- Switch gamescope-session-plus.conf writer from heredoc-overwrite to
per-key set/unset (sed-based, mirrors flush_pending in the TUI) so the
installer is idempotent and re-runs preserve user-set display values
- Strip stale opposite-GPU keys when re-running on a changed GPU
(e.g. NVIDIA → AMD swap clears VULKAN_ADAPTER / GBM_BACKEND)
- Fix switch-to-desktop: replace racy `stop sddm` + disowned `start sddm`
with a single atomic `systemctl restart sddm`. The disowned start was
getting killed by user-session teardown before SDDM came back up,
leaving the user on a black screen after Super+Shift+R
Two issues found during first-run-machine test:
- Steam was installed but never auto-launched, so the first-run client
update never happened — Gaming Mode failed to enter Big Picture
cleanly. Now follows omarchy-install-gaming-steam's pattern: setsid
gtk-launch steam &, disown, runs in parallel with the rest of the
install.
- Walker didn't pick up the deckshift-settings.desktop entry until the
next manual elephant restart. setup_settings_tui now calls
omarchy-restart-walker after update-desktop-database, which restarts
elephant.service + the walker autostart unit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Steam Deck-style gaming mode for Linux + Hyprland. Lineage:
Super-Shift-S-Omarchy-Deck-Mode → Omarchy Deck → DeckShift (renamed for
distro-portability — distro-agnostic features on the roadmap).
Currently targets Omarchy (Arch + Hyprland + SDDM + iwd). Includes:
- Press Super+Shift+S to enter Gaming Mode (Gamescope + Steam Big
Picture), Super+Shift+R to return to desktop
- NVIDIA GSP-aware driver branch selection (legacy nvidia-580xx-utils
for Maxwell/Pascal/Volta cards)
- AMD support (vulkan-radeon, libvdpau)
- Intel support (vulkan-intel, intel-media-driver) with generation-aware
performance warning before continuing
- Idempotent package installs via Omarchy's omarchy-pkg-add
- Optional Xbox Bluetooth controller support (xpadneo-dkms)
- Settings TUI launched from Walker (deckshift-settings) for adjusting
monitor / GPU / resolution / refresh rate after install:
* Buffered changes — explicit Save and exit / Cancel
* hyprctl-based mode detection with monitor-capability filtering
* Save-time warning if values exceed monitor capability
* Esc returns to main menu without crashing
- Multilib check removed (Omarchy ships with multilib enabled)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>