From f42d80598fe4f4e261f0e26c5c5c8669bf2052be Mon Sep 17 00:00:00 2001 From: Felitendo Date: Mon, 24 Aug 2026 10:01:27 +0200 Subject: [PATCH] fix: keep Steam out of an endless update loop Steam compares its installed files against its manifest at every start and restores whatever differs, which is why its launcher entry carries -noverifyfiles. The entry in ~/.config/autostart never got it: it points at /usr/bin/steam, a shell script, so it fell through the Chromium filter in mca_autostart_apply and was left alone. A Steam started at login therefore verified, found the web helper script 46 bytes larger than the manifest says, restored it, and the path unit patched it straight back - an update dialog that begins again every few seconds and never finishes. The same client started from the menu was fine, which is what made it look like a problem of Steam's own. The autostart entry now carries the switch as well. It follows the Steam setting rather than the autostart one, because leaving it out while Steam is patched is exactly what causes the loop. That covers the entry that exists; a terminal or a script still starts Steam without the switch. So the patch no longer fights back either: a script that was patched before, is not patched now, and belongs to a client that is still running has just been restored by Steam, and doing it again would only have the two of them undoing each other. It waits for the next apply with Steam closed - the helper is started once, at the start, so patching it now would not have helped that session anyway. Also: Steam does not checksum that script, it compares sizes - the log says "Verifying file sizes only" - while the comments and the documentation claimed otherwise. And the status screen now tells a patch that is waiting from one that is missing, instead of reporting both as not patched yet. --- README.md | 23 +++++++---- doc/middleclick-autoscroll.1.scd | 21 +++++++---- po/de.po | 6 +++ po/middleclick-autoscroll.pot | 6 +++ src/lib/apply.sh | 7 +++- src/lib/common.sh | 10 ++++- src/lib/menu.sh | 3 ++ src/lib/patch.sh | 17 ++++++++- src/lib/steam.sh | 65 +++++++++++++++++++++++++++++--- 9 files changed, 135 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8a58d41..ec1850c 100644 --- a/README.md +++ b/README.md @@ -118,17 +118,26 @@ Steam's own installation: ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh ``` -Steam checksums that script at every start and restores it when it differs, so -its launcher entry also gets `-noverifyfiles`. **The trade-off is real**: with -verification off, Steam no longer repairs a damaged installation by itself. That -is why Steam is a switch of its own rather than part of the general handling — -turn it off in the settings and Steam is left completely alone. +Steam compares the installed files against its manifest at every start — by +size, not by content — and restores whatever differs, so its launcher entry gets +`-noverifyfiles`. So does its entry in `~/.config/autostart`, which Steam writes +as soon as it is set to run at login: that one bypasses the menu entry entirely, +and without the switch a Steam started at login finds the patched script, +restores it, gets patched again, and never gets past its update dialog. + +**The trade-off is real**: with verification off, Steam no longer repairs a +damaged installation by itself. That is why Steam is a switch of its own rather +than part of the general handling — turn it off in the settings and Steam is +left completely alone. The script comes back on every client update. The watcher notices and puts the patch back. -Starting Steam from a terminal without `-noverifyfiles` undoes it for that one -session; the next start from the menu has it again. +Starting Steam some other way — from a terminal, from a script — leaves the +switch out and Steam puts its own copy back for that session. The patch returns +at the next apply with Steam closed; it is deliberately not repeated while the +client is running, because the two would only undo each other and the helper is +started once, at the start. ## Applications installed later diff --git a/doc/middleclick-autoscroll.1.scd b/doc/middleclick-autoscroll.1.scd index 9118368..961e371 100644 --- a/doc/middleclick-autoscroll.1.scd +++ b/doc/middleclick-autoscroll.1.scd @@ -98,14 +98,21 @@ helper, inside Steam's own installation: ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh -Steam checksums that script at every start and restores it when it differs, so -its launcher entry also gets *-noverifyfiles*. The trade-off is that Steam no -longer repairs a damaged installation on its own; that is why Steam is a switch -of its own in the settings. +Steam compares the installed files against its manifest at every start - by +size, not by content - and restores whatever differs, so its launcher entry +gets *-noverifyfiles*. So does its entry in _~/.config/autostart_, which Steam +writes as soon as it is set to run at login: that entry bypasses the menu one +entirely, and without the switch a Steam started at login spends the session in +an update dialog. The trade-off is that Steam no longer repairs a damaged +installation on its own; that is why Steam is a switch of its own in the +settings. -Starting Steam from a terminal without *-noverifyfiles* undoes the patch for -that session. The watcher notices and puts it back, so the next start has it -again. +Starting Steam some other way - from a terminal, from a script - leaves the +switch out, and Steam puts its own copy of the script back for that session. +The patch returns at the next apply with Steam closed. It is deliberately not +repeated while the client is running: the two would only undo each other, and +the helper is started once, at the start, so it would not help that session +anyway. # SPOTIFY diff --git a/po/de.po b/po/de.po index e10d616..c8edeb1 100644 --- a/po/de.po +++ b/po/de.po @@ -43,6 +43,9 @@ msgstr "nicht erkennbar" msgid "not patched yet" msgstr "noch nicht gepatcht" +msgid "waiting until Steam is closed" +msgstr "wartet, bis Steam geschlossen ist" + #. Status block msgid "Autoscroll" @@ -176,6 +179,9 @@ msgstr "Bereits auf %d Anwendungen angewendet." msgid "Steam starts its interface in a way this version does not recognise; leaving it alone." msgstr "Steam startet seine Oberfläche auf eine Art, die diese Version nicht kennt; Steam bleibt unangetastet." +msgid "Steam is running and has put its own file back; the change waits until Steam is closed." +msgstr "Steam läuft und hat seine eigene Datei wiederhergestellt; die Änderung wird nachgeholt, sobald Steam geschlossen ist." + #. Help msgid "Usage: middleclick-autoscroll [command]" diff --git a/po/middleclick-autoscroll.pot b/po/middleclick-autoscroll.pot index 0cab8d1..48ec7d2 100644 --- a/po/middleclick-autoscroll.pot +++ b/po/middleclick-autoscroll.pot @@ -43,6 +43,9 @@ msgstr "" msgid "not patched yet" msgstr "" +msgid "waiting until Steam is closed" +msgstr "" + #. Status block msgid "Autoscroll" @@ -176,6 +179,9 @@ msgstr "" msgid "Steam starts its interface in a way this version does not recognise; leaving it alone." msgstr "" +msgid "Steam is running and has put its own file back; the change waits until Steam is closed." +msgstr "" + #. Help msgid "Usage: middleclick-autoscroll [command]" diff --git a/src/lib/apply.sh b/src/lib/apply.sh index fa58873..25d0f3c 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -107,7 +107,12 @@ mca_apply() { mca_steam_apply fi - [[ $CFG_AUTOSTART == yes ]] && mca_autostart_apply + # Steam's autostart entry carries Steam's own switch and follows the Steam + # setting, not this one - leaving it out while Steam is patched is what puts + # the client in an update loop - so both are checked inside. + if [[ $CFG_AUTOSTART == yes || $CFG_STEAM == yes ]]; then + mca_autostart_apply + fi [[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply mca_prune_orphans diff --git a/src/lib/common.sh b/src/lib/common.sh index afcabfb..e833c8f 100644 --- a/src/lib/common.sh +++ b/src/lib/common.sh @@ -239,6 +239,14 @@ mca_ledger_forget() { } +# mca_backup_name +# The name a backup copy of that file is stored under. Derived from the path +# rather than remembered, so a caller can ask whether a file has been backed up +# before without reading the ledger. +mca_backup_name() { + printf '%s' "$1" | sed 's|/|%|g' +} + # mca_backup # Copies a file aside before it is edited in place, and prints the name the # copy was stored under. Existing backups are never overwritten: the first copy @@ -246,7 +254,7 @@ mca_ledger_forget() { # patched version. mca_backup() { local file="$1" name - name="$(printf '%s' "$file" | sed 's|/|%|g')" + name="$(mca_backup_name "$file")" mkdir -p "$MCA_BACKUPDIR" 2>/dev/null || return 1 if [[ ! -e "$MCA_BACKUPDIR/$name" ]]; then diff --git a/src/lib/menu.sh b/src/lib/menu.sh index d55da2a..888fbaf 100644 --- a/src/lib/menu.sh +++ b/src/lib/menu.sh @@ -178,6 +178,9 @@ mca_ui_status() { if mca_steam_installed; then if mca_steam_patched; then _mca_row "$(mca_msg "Steam")" "$(_mca_onoff yes)" + elif [[ $CFG_STEAM == yes && $CFG_ENABLED == yes ]] && mca_steam_waiting; then + _mca_row "$(mca_msg "Steam")" \ + "${MCA_C_DIM}$(mca_msg "waiting until Steam is closed")${MCA_C_RESET}" elif [[ $CFG_STEAM == yes && $CFG_ENABLED == yes ]]; then _mca_row "$(mca_msg "Steam")" \ "${MCA_C_YELLOW}$(mca_msg "not patched yet")${MCA_C_RESET}" diff --git a/src/lib/patch.sh b/src/lib/patch.sh index 1597698..d556c4b 100644 --- a/src/lib/patch.sh +++ b/src/lib/patch.sh @@ -345,6 +345,12 @@ mca_desktop_apply() { # ~/.config/autostart, pointing straight at their binary. Those bypass the # desktop entry in the menu completely, which is why Discord launched at login # used to behave differently from Discord launched from the menu. +# +# Steam is here too, even though it is not a Chromium process itself: its entry +# needs -noverifyfiles exactly like the menu one, and it is the entry most +# likely to exist, because Steam writes it as soon as "run at startup" is +# ticked. Without it, a Steam started at login restores the patched web helper +# script and the watcher patches it back, over and over. mca_autostart_apply() { local dir="$MCA_XDG_CONFIG/autostart" file exec_line prog content backup @@ -360,9 +366,16 @@ mca_autostart_apply() { mca_exec_program "$exec_line" || continue prog="$MCA_PROG" - mca_is_chromium "$prog" || continue - content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")" + if [[ ${prog##*/} == steam || ${prog##*/} == steam-runtime ]]; then + [[ $CFG_STEAM == yes ]] || continue + content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \ + "$MCA_STEAM_LAUNCH_FLAG" after-program)" + else + [[ $CFG_AUTOSTART == yes ]] || continue + mca_is_chromium "$prog" || continue + content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")" + fi [[ -n $content ]] || continue [[ "$content" == "$(< "$file")" ]] && continue diff --git a/src/lib/steam.sh b/src/lib/steam.sh index 09c31f7..e7bce3c 100644 --- a/src/lib/steam.sh +++ b/src/lib/steam.sh @@ -9,11 +9,19 @@ # # ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh # -# Steam checksums that script at every start and restores it when it differs, -# which is why the launcher gets -noverifyfiles as well. The trade-off is real -# and belongs to the user, so this is a switch of its own rather than part of -# the general application handling: with verification off, Steam no longer -# repairs a damaged installation by itself. +# Steam compares the installed files against its manifest at every start - by +# size, not by content - and restores whatever differs, which is why the +# launcher gets -noverifyfiles as well. The trade-off is real and belongs to +# the user, so this is a switch of its own rather than part of the general +# application handling: with verification off, Steam no longer repairs a +# damaged installation by itself. +# +# Every way of starting Steam has to carry that switch, or the two programs +# spend the session undoing each other: Steam restores the script, the watcher +# patches it again, Steam restores it again, and the client never gets past its +# update dialog. So the autostart entry gets it as well as the menu one (see +# mca_autostart_apply), and the patch below stays out of the way of a client +# that is already running. # # The file comes back on every client update, and the watcher re-applies the # patch when that happens. @@ -64,6 +72,23 @@ mca_steam_installed() { return 1 } +# mca_steam_running +# Steam records its own process id beside its installation while it runs. The +# file outlives a crash, so the id is checked rather than believed. +mca_steam_running() { + local f pid + for f in \ + "$HOME/.steam/steam.pid" \ + "$HOME/.var/app/com.valvesoftware.Steam/.steam/steam.pid" + do + [[ -r $f ]] || continue + pid="$(< "$f")" + [[ $pid =~ ^[0-9]+$ ]] || continue + kill -0 "$pid" 2>/dev/null && return 0 + done + return 1 +} + # mca_steam_script_patched