From a3cb6f28e4038f4e1a0fe974bf5851dd01433fdc Mon Sep 17 00:00:00 2001 From: 28allday Date: Mon, 18 May 2026 19:38:33 +0100 Subject: [PATCH] =?UTF-8?q?v0.1.12=20=E2=80=94=20Patch=20gamescope-session?= =?UTF-8?q?-plus=20so=20refresh-rate=20selection=20actually=20reaches=20ga?= =?UTF-8?q?mescope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 6 ++++ deckshift.sh | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f160ce4..55d9d64 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ Lineage: forked from [Super-Shift-S-Omarchy-Deck-Mode](https://git.no-signal.uk/ ## 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 - New env var `OUTPUT_CONNECTOR_TO_DISABLE` (single connector or comma list). When set, `switch-to-gaming` runs `hyprctl keyword monitor ,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. diff --git a/deckshift.sh b/deckshift.sh index 01daa09..9c57f59 100755 --- a/deckshift.sh +++ b/deckshift.sh @@ -34,7 +34,7 @@ set -Euo pipefail # -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 -DECKSHIFT_VERSION="0.1.11" +DECKSHIFT_VERSION="0.1.12" # Resolve the directory this script lives in so we can find sibling files like # 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 # 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() { echo "" echo "================================================================" @@ -1526,6 +1615,11 @@ setup_session_switching() { info "ChimeraOS gamescope-session packages already installed (correct -git versions)" 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 # # Omarchy uses iwd (Intel Wireless Daemon) for WiFi, but Steam requires