v0.1.12 — Patch gamescope-session-plus so refresh-rate selection actually reaches gamescope

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>
This commit is contained in:
28allday 2026-05-18 19:38:33 +01:00
parent 52c883b780
commit a3cb6f28e4
2 changed files with 101 additions and 1 deletions

View file

@ -10,6 +10,12 @@ Lineage: forked from [Super-Shift-S-Omarchy-Deck-Mode](https://git.no-signal.uk/
## What's New ## What's New
### v0.1.12 — Refresh-rate selection actually reaches gamescope now
- **The real bug:** Omarchy installs `gamescope` from Arch's `extra` repo (upstream Valve binary), but the AUR `gamescope-session-git` script (OpenGamingCollective / ex-ChimeraOS fork) was written assuming the ChimeraOS-fork `gamescope-plus` binary that ships `--custom-refresh-rates`. The fork isn't packaged for 64-bit Arch — we can't install it cleanly. The session script feature-detects via `gamescope_has_option "--custom-refresh-rates"`, finds it absent, and **silently drops the `CUSTOM_REFRESH_RATES` value before it reaches gamescope**. Net effect: every refresh-rate selection in the DeckShift TUI since the project began has been a no-op. Gaming Mode has been launching at the EDID-preferred mode (usually 60 Hz) regardless of what the user picked. v0.1.8's "60 Hz fix" was correct on paper but never actually reached the binary on Omarchy.
- **The fix:** `./deckshift.sh` now patches `/usr/share/gamescope-session-plus/gamescope-session-plus` in place, adding an `elif` branch that falls back to `--nested-refresh` (a flag present in every gamescope version) with the highest value from the `CUSTOM_REFRESH_RATES` list as the launch rate. The patch is marked with a `DECKSHIFT-NESTED-REFRESH-FALLBACK` sentinel comment for idempotency, and is re-applied on every install so AUR upgrades that clobber the file don't silently regress refresh-rate handling.
- **What you should do after upgrading:** re-run `./deckshift.sh` once. Future-you, if you ever see Gaming Mode stuck at 60 Hz after a `pacman -Syu` that touched `gamescope-session-git`, just re-run the installer — the patch reapplies cleanly.
### v0.1.11 — Multi-monitor handling: disable an auxiliary monitor before Gaming Mode ### v0.1.11 — Multi-monitor handling: disable an auxiliary monitor before Gaming Mode
- New env var `OUTPUT_CONNECTOR_TO_DISABLE` (single connector or comma list). When set, `switch-to-gaming` runs `hyprctl keyword monitor <conn>,disable` for each listed connector *before* SDDM restart, while Hyprland is still alive. The disable is runtime-only — when the user returns from Gaming Mode, the new Hyprland reads its static config fresh and the monitor comes back automatically. - New env var `OUTPUT_CONNECTOR_TO_DISABLE` (single connector or comma list). When set, `switch-to-gaming` runs `hyprctl keyword monitor <conn>,disable` for each listed connector *before* SDDM restart, while Hyprland is still alive. The disable is runtime-only — when the user returns from Gaming Mode, the new Hyprland reads its static config fresh and the monitor comes back automatically.

View file

@ -34,7 +34,7 @@ set -Euo pipefail
# -u: Treat unset variables as errors (catches typos in variable names) # -u: Treat unset variables as errors (catches typos in variable names)
# -o pipefail: A pipeline fails if ANY command in it fails, not just the last one # -o pipefail: A pipeline fails if ANY command in it fails, not just the last one
DECKSHIFT_VERSION="0.1.11" DECKSHIFT_VERSION="0.1.12"
# Resolve the directory this script lives in so we can find sibling files like # Resolve the directory this script lives in so we can find sibling files like
# bin/deckshift-settings and applications/deckshift-settings.desktop when # bin/deckshift-settings and applications/deckshift-settings.desktop when
@ -1302,6 +1302,95 @@ setup_settings_tui() {
# It also installs ChimeraOS's gamescope-session packages from AUR, which # It also installs ChimeraOS's gamescope-session packages from AUR, which
# provide the base session framework that the Steam Deck uses. # provide the base session framework that the Steam Deck uses.
# ============================================================================== # ==============================================================================
# Patch the installed gamescope-session-plus script to add a --nested-refresh
# fallback for CUSTOM_REFRESH_RATES.
#
# Why this exists:
# - 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.
# - The OpenGamingCollective (ex-ChimeraOS) `gamescope-session-plus` script
# we install from AUR was written assuming gamescope-plus. It feature-
# detects via `gamescope_has_option "--custom-refresh-rates"` and silently
# drops the value when the flag is missing. Result: CUSTOM_REFRESH_RATES
# reaches the script but never reaches gamescope, and Gaming Mode launches
# at the EDID-preferred rate (usually 60 Hz) regardless of TUI selection.
# - This patch adds an `elif` branch that falls back to `--nested-refresh`
# (the older flag that exists in every gamescope version) with the highest
# rate from the comma list as the launch rate.
#
# Idempotent: the patched line carries a `DECKSHIFT-NESTED-REFRESH-FALLBACK`
# marker so re-runs detect "already patched" and skip. Re-applied on every
# ./deckshift.sh run so pacman/AUR upgrades that clobber the script don't
# silently regress refresh-rate handling.
patch_gamescope_session_plus() {
local gsp="/usr/share/gamescope-session-plus/gamescope-session-plus"
if [[ ! -f "$gsp" ]]; then
warn "$gsp not found — skipping refresh-rate fallback patch"
return 0
fi
if grep -q "DECKSHIFT-NESTED-REFRESH-FALLBACK" "$gsp" 2>/dev/null; then
info "gamescope-session-plus already has DeckShift refresh-rate fallback"
return 0
fi
info "Patching gamescope-session-plus to add --nested-refresh fallback..."
local tmp
tmp=$(mktemp)
if ! python3 - "$gsp" "$tmp" <<'PY'
import re, sys
src, dst = sys.argv[1], sys.argv[2]
with open(src) as f:
content = f.read()
# Match the 4-line block exactly as it ships in gamescope-session-git r339.
# Group 1 = leading 3 lines (kept), group 2 = closing `fi` (kept). The new
# elif+body lines are inserted between them.
pattern = re.compile(
r'(\tCUSTOM_REFRESH_RATES_OPTION=""\n'
r'\tif \[ -n "\$CUSTOM_REFRESH_RATES" \] && gamescope_has_option "--custom-refresh-rates"; then\n'
r'\t\tCUSTOM_REFRESH_RATES_OPTION="--custom-refresh-rates \$CUSTOM_REFRESH_RATES"\n'
r')(\tfi\n)'
)
# Use a function as the replacement so re.sub doesn't process backslash escapes
# in our string — we need literal `\n` in the `tr "," "\n"` argument to reach
# the shell, and a string-form replacement would turn it into a real newline.
def _patch(m):
return m.group(1) + (
'\telif [ -n "$CUSTOM_REFRESH_RATES" ] && gamescope_has_option "--nested-refresh"; then # DECKSHIFT-NESTED-REFRESH-FALLBACK\n'
'\t\t_deckshift_rate=$(echo "$CUSTOM_REFRESH_RATES" | tr "," "\\n" | sort -nr | head -1)\n'
'\t\tCUSTOM_REFRESH_RATES_OPTION="--nested-refresh $_deckshift_rate"\n'
) + m.group(2)
new = pattern.sub(_patch, content, count=1)
if new == content:
sys.stderr.write("could not locate CUSTOM_REFRESH_RATES_OPTION block in expected shape\n")
sys.exit(1)
with open(dst, "w") as f:
f.write(new)
PY
then
warn "Could not patch $gsp — upstream may have changed shape"
warn "Refresh-rate selection in the TUI will continue to be a no-op until this is resolved"
rm -f "$tmp"
return 1
fi
if ! grep -q "DECKSHIFT-NESTED-REFRESH-FALLBACK" "$tmp"; then
warn "Patch produced output but marker is missing — aborting install"
rm -f "$tmp"
return 1
fi
sudo install -m 0755 "$tmp" "$gsp"
rm -f "$tmp"
info "Patched $gsp — CUSTOM_REFRESH_RATES now reaches gamescope via --nested-refresh"
}
setup_session_switching() { setup_session_switching() {
echo "" echo ""
echo "================================================================" echo "================================================================"
@ -1526,6 +1615,11 @@ setup_session_switching() {
info "ChimeraOS gamescope-session packages already installed (correct -git versions)" info "ChimeraOS gamescope-session packages already installed (correct -git versions)"
fi fi
# Patch the installed gamescope-session-plus to add --nested-refresh
# fallback. Runs on every install so AUR upgrades that overwrite the file
# get re-patched the next time the user re-runs ./deckshift.sh.
patch_gamescope_session_plus
# NetworkManager Integration # NetworkManager Integration
# #
# Omarchy uses iwd (Intel Wireless Daemon) for WiFi, but Steam requires # Omarchy uses iwd (Intel Wireless Daemon) for WiFi, but Steam requires