Compare commits

...
8 Commits
Author SHA1 Message Date
Felitendo c777e8280d chore: 1.4.0
release / Debian package (push) Failing after 14s
release / RPM package (push) Failing after 23s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
Steam's helper script is only patched when the patch keeps the file at the
length Steam wrote down. Where the comments cannot pay for the argument,
nothing is written at all: a file of the wrong length costs the whole client
package downloaded and installed again, and a client that quits at the end of
it instead of coming up.

With no such patch left to prop up, -noverifyfiles is gone from everything that
carried it - Steam's launcher entry, its autostart entry, the shortcut on the
desktop and the ones Steam writes for single games. Steam repairs its own
installation again, and nothing of this program's is on its command line. A
patch an earlier version appended is rebuilt to fit at the next apply, or taken
back if it cannot be.

A script somebody puts in front of the client to add a switch of its own is
recognised as a way of starting Steam, rather than as an application to hand
the flag to.

A desktop entry edited in place that the ledger has lost is written again from
the copy kept beside it. Without that, an entry from an older version kept an
older answer for good - a browser patched that way stayed on the flag that
shows the "unsupported command-line flag" bar while every other browser moved
off it in 1.3.0.
2026-09-06 14:00:43 +02:00
Felitendo 2ea64d0603 fix: write the current answer into an entry the ledger forgot
A desktop entry that was edited in place is left alone once it carries the
marker, and the only thing that ever writes it again is the undo a change of
flags goes through - which reads the ledger. So an entry the ledger has lost is
one nothing looks at any more: it keeps whatever an older version put on its
command line, and an update that moves browsers to a different flag moves every
browser but that one.

The copy taken before the edit is named after the path, not recorded in the
ledger, and is still there. A marked entry with no ledger line is now put back
from that copy and written again from scratch, which is the same thing the undo
would have done, and it lands in the ledger on the way out.

Only entries edited in place need this. A generated entry is built from the
source entry every time and is right by construction, and a flag file has its
block rewritten whenever the contents differ.
2026-09-06 13:58:11 +02:00
Felitendo dcdf2c1df3 fix: never leave Steam a file it would reinstall itself over
The patch that keeps the script's length was the good path and appending was
the fallback, with -noverifyfiles on everything that starts Steam to cover for
it. That cover does not exist.

Steam checks its files at the shutdown it runs itself, not only at a start
somebody handed arguments to, and that run carries no -noverifyfiles whatever
the session was started with. One wrong length there costs the whole client
package downloaded, extracted and installed again - and the client quits at the
end of it instead of coming up, which is the "Steam just closed itself" that
this was seen as:

    BVerifyInstalledFiles: steamwebhelper.sh is 130 bytes, expected 84
    Update wird heruntergeladen ... Paket wird extrahiert ...
    Aktualisierung abgeschlossen, Steam wird geladen ... Shutdown

So the flag now either fits in the space the comments give back or it is not
written at all, and the status screen says Steam is not patched. No autoscroll
in the interface is a far smaller thing than a client that reinstalls itself.

With no growing patch left to prop up, -noverifyfiles has nothing to do and is
gone with everything that carried it: Steam's launcher entry, its autostart
entry, the shortcuts on the desktop and the ones it writes for single games.
Steam repairs its own installation again. An installation from an older version
is taken back once through the flag scheme, and a patch that version appended
is rebuilt to fit at the next apply - or taken back, if the comments cannot pay
for it.
2026-09-05 01:07:15 +02:00
Felitendo c1841ec169 fix: recognise a wrapper script in front of Steam as Steam
A script somebody puts in front of the client to add a switch of its own is
still a way of starting Steam, but nothing recognised it as one: the check is
by name, and the script is not called steam.

Such a script tends to name the flag it exists to add, and that flag is one of
the markers the hint scan reads as "this is Chromium". The entry was therefore
handled as an application and had the flag appended to every Exec line of it,
where Steam ignores it, instead of being handed to the Steam module - so the
interface it was pointing at never got autoscroll, and the launcher entry never
got Steam's own switch either.

The handover a launcher script makes is now followed one step, and a script
that hands over to Steam counts as Steam. One step is all there is: the
client's own launcher is recognised by name already.
2026-09-05 00:51:42 +02:00
Felitendo e1c77f049f chore: 1.3.0
release / Debian package (push) Failing after 13s
release / RPM package (push) Failing after 26s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
A browser no longer shows the "unsupported command-line flag" bar. It is given
--enable-features=MiddleClickAutoscroll, which asks for the same feature
without being on the list of flags Chromium warns about; everything that embeds
an older Chromium keeps the flag that works there, and shows no bar either way.

Helium gets its own name for the feature alongside it, so autoscroll works
there for the first time.

An installation from 1.2.0 or earlier is taken back and written again on the
next apply.
2026-09-05 00:44:52 +02:00
Felitendo fa624380b1 fix: keep the unsupported-flag warning off browsers
--enable-blink-features is on the list of flags Chromium warns about, so every
browser the flag was applied to put a yellow "unsupported command-line flag"
bar above each page.

Browsers are given --enable-features=MiddleClickAutoscroll instead. It asks for
the same thing - Blink generates a feature of the same name for each of its
runtime flags - and is not on that list. That spelling only works from Chromium
124 onwards, so everything else keeps the flag that works everywhere: an
application embedding an older Chromium, Steam's CEF among them, has no such
bar to show anyway.

Helium knows the feature under a name of its own and ignores the Chromium one,
so browsers are asked for HeliumMiddleClickAutoscroll as well; autoscroll never
worked there before. A name a browser does not know is ignored, which is what
makes one list safe for all of them.

An installation set up by an earlier version is taken back and written again
once, because a file that is already marked as patched would otherwise be left
alone with the old flag in it.
2026-09-05 00:44:47 +02:00
Felitendo d8900ef80f docs: show the TUI in the readme 2026-09-04 13:02:13 +02:00
Felitendo 46e63e9faa docs: rewrite the readme in plain words 2026-09-04 12:59:50 +02:00
13 changed files with 433 additions and 343 deletions

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
# Overridable so a packager can pass the version it is actually building # Overridable so a packager can pass the version it is actually building
# (`make VERSION=$pkgver`). The literal below is the fallback for builds # (`make VERSION=$pkgver`). The literal below is the fallback for builds
# straight from a checkout, and is what a release tag has to carry. # straight from a checkout, and is what a release tag has to carry.
VERSION ?= 1.2.0 VERSION ?= 1.4.0
PREFIX ?= /usr PREFIX ?= /usr
DESTDIR ?= DESTDIR ?=
+89 -41
View File
@@ -1,21 +1,27 @@
# middleclick-autoscroll # middleclick-autoscroll
Middle-click autoscroll for Linux. Hold the middle mouse button, move the Middle-click autoscroll is a CLI for Linux that enables autoscroll in every application that supports it.
mouse, the page scrolls — like on Windows. Works with browsers, Electron apps, Works with browsers, Electron apps like Discord and Spotify, Steam, and anything else that runs on Chromium under the hood.
Flatpaks, snaps, Steam, and anything else that runs on Chromium under the hood.
No app list to maintain. The program looks at what's actually installed, figures There is no app list. My tool just looks at what uses chromium under the hood and applies the necessary steps to get it working.
out what's Chromium-based, and handles it.
## Installing ## How to install
**Arch, CachyOS, EndeavourOS, Manjaro** **Arch**
```bash ```bash
paru -S middleclick-autoscroll yay -S middleclick-autoscroll
``` ```
**Debian, Ubuntu, Linux Mint, Pop!_OS** **Fedora**
```bash
sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
https://felitendo.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
sudo dnf install middleclick-autoscroll
```
**Debian**
```bash ```bash
sudo install -d -m 0755 /etc/apt/keyrings sudo install -d -m 0755 /etc/apt/keyrings
@@ -26,14 +32,6 @@ echo "deb [signed-by=/etc/apt/keyrings/middleclick-autoscroll.gpg] https://felit
sudo apt update && sudo apt install middleclick-autoscroll sudo apt update && sudo apt install middleclick-autoscroll
``` ```
**Fedora, RHEL, CentOS Stream**
```bash
sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
https://felitendo.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
sudo dnf install middleclick-autoscroll
```
**openSUSE** **openSUSE**
```bash ```bash
@@ -43,30 +41,80 @@ sudo zypper addrepo --gpgcheck --refresh \
sudo zypper install middleclick-autoscroll sudo zypper install middleclick-autoscroll
``` ```
Then run `middleclick-autoscroll enable`. That's it — nothing else to configure. ## How to use
Updates come through your package manager like anything else. Without a package, Just run `middleclick-autoscroll`. This will open the configuration TUI that looks like this:
build [from source](#building-from-source) or grab a `.deb`/`.rpm` from the
[releases page](https://github.com/Felitendo/middleclick-autoscroll/releases).
Run `middleclick-autoscroll disable` before removing the package — it undoes ```
everything. Middle-Click Autoscroll
Autoscroll ON
Applications covered 11 of 13
Not identified 1 - see the applications list
Steam ON
New applications ON
Last applied 2 minutes ago
Applications pick this up the next time they are started.
[1] Turn autoscroll on or off
[2] Re-apply everything
[3] Applications
[4] Settings
[q] Quit
>
```
`[1]` is all you need for the normal case. `[3]` lists every app that was found
and what is being done with it, so you can leave a single one out or switch on
an AppImage that couldn't be identified:
```
Applications
▸ Steam on (Steam)
Chromium on (launcher)
Discord on (flag file)
Obsidian on (launcher)
Spotify on (launcher)
Slack off
Cursor cannot tell
Anything not identified is left alone until it is turned on here.
Up/Down select - Space turns one on or off - q goes back
```
`[4]` has the same switches per category - browsers, Electron and CEF apps,
Flatpaks, snaps, autostart entries, Steam, Spotify - plus a field for extra
Chromium arguments.
## How it works ## How it works
Blink (the engine in Chromium, Electron, and CEF) already has autoscroll, but Blink (the engine in Chromium, Electron, and CEF) already has autoscroll, but
it's off on Linux because middle click does primary-selection paste there. One it's off on Linux because middle click does primary-selection paste there. This
flag turns it on: flag turns it on:
```
--enable-blink-features=MiddleClickAutoscroll --enable-blink-features=MiddleClickAutoscroll
```
Doing that for one app is a five-minute job. Doing it for *all* of them — across But doing that for every app is kinda bothersome and it also might break with updates.
flag files, desktop entries, Flatpaks, snaps, autostart entries, Steam — so it That's why I created this small tool to automate that.
survives upgrades, is not. That's what this does.
New apps are picked up within a second by a systemd path unit that watches the Browsers get the same thing asked for differently:
--enable-features=MiddleClickAutoscroll
Both mean the same feature - Blink generates a matching feature name for each of
its runtime flags - but the first one is on Chromium's list of flags worth
warning about, so a browser started with it shows a yellow "unsupported
command-line flag" bar over every page. The second one is not on that list, so
there's no bar. It only works from Chromium 124 on, which is why apps that
embed something older (Steam's CEF, older Electron) keep the first one - they
have no such bar to begin with. Helium, which carries the feature under its own
name, is asked for `HeliumMiddleClickAutoscroll` alongside it.
New apps are picked up by a systemd path unit that watches the
relevant directories. Without systemd, `middleclick-autoscroll apply` does the relevant directories. Without systemd, `middleclick-autoscroll apply` does the
same thing manually. same thing manually.
@@ -74,16 +122,18 @@ same thing manually.
Steam's web UI supports autoscroll but has no way to pass extra arguments to its Steam's web UI supports autoscroll but has no way to pass extra arguments to its
helper, so the program patches the script that starts it. Steam checks its own helper, so the program patches the script that starts it. Steam checks its own
files at every start and repairs whatever looks changed, so the patch is written files against its manifest - by size and timestamp - and reinstalls the whole
to look unchanged: the bytes the argument costs come back out of the script's client package over anything that differs, so the patch is written to look
comments and the timestamp is restored, leaving a file exactly as long and as old unchanged: the bytes the argument costs are taken back out of the script's
as Steam left it. It survives however you start Steam — the menu, a desktop comments and the timestamp is put back.
shortcut, a game launcher, a terminal.
As a fallback, for the case where that isn't possible, `-noverifyfiles` goes on If that can't be done - a script with no comments left to pay for the argument,
everything that starts Steam: its launcher, its autostart entry, and the or one this version doesn't recognise - nothing is written and Steam is left
shortcuts it writes for single games. That one means Steam won't auto-repair alone. A file of the wrong length costs the client package downloaded and
damaged files on its own — you can turn it off separately in the settings. installed again, and a Steam that quits at the end of it instead of coming up;
no autoscroll in Steam is the smaller problem. Nothing is added to Steam's
launcher entry or to the shortcuts it writes for games, and Steam keeps
repairing its own installation.
## Commands ## Commands
@@ -97,8 +147,6 @@ damaged files on its own — you can turn it off separately in the settings.
| `… status` | What's covered | | `… status` | What's covered |
| `… list` | All apps and how they're handled | | `… list` | All apps and how they're handled |
See `man middleclick-autoscroll` for more.
## Building from source ## Building from source
```bash ```bash
+31 -21
View File
@@ -18,7 +18,18 @@ on the command line.
*middleclick-autoscroll* finds every Chromium-based application on the system *middleclick-autoscroll* finds every Chromium-based application on the system
and puts that argument somewhere the application will actually read it, then and puts that argument somewhere the application will actually read it, then
keeps doing so for anything installed later. It works on any distribution: keeps doing so for anything installed later.
A browser is given *--enable-features=MiddleClickAutoscroll* instead. It asks
for the same feature - Blink generates a feature of that name for each of its
runtime flags - but it is not on the list of flags Chromium warns about, so the
browser does not put a bar reading "You are using an unsupported command-line
flag" above every page. That spelling only works from Chromium 124 onwards,
which is why everything else keeps the first one: an application that embeds an
older Chromium, such as Steam's CEF, has no such bar to show anyway. Helium
knows the feature under a name of its own and is asked for
*HeliumMiddleClickAutoscroll* as well; a name a browser does not know is
ignored. It works on any distribution:
which of the routes below an application takes is read off its launcher, not which of the routes below an application takes is read off its launcher, not
assumed from where the launcher came from. assumed from where the launcher came from.
@@ -71,10 +82,10 @@ Two ways, chosen per application.
Where the launcher reads extra arguments from Where the launcher reads extra arguments from
_$XDG_CONFIG_HOME/<name>-flags.conf_. This is the preferred route: it is _$XDG_CONFIG_HOME/<name>-flags.conf_. This is the preferred route: it is
the supported way to pass arguments, it survives package upgrades, and it the supported way to pass arguments, it survives package upgrades, and it
applies to a launch from a terminal as much as one from the menu. An applies to a launch from a terminal as much as one from the menu. A
*--enable-blink-features* line that is already in the file is extended feature list that is already in the file is extended rather than
rather than duplicated - Chromium keeps only the last occurrence of that duplicated - Chromium keeps only the last occurrence of such an option, so
option, so a second one would switch the first one off. a second one would switch the first one off.
Arch's Electron and Chromium packages all wrap their binaries this way, and Arch's Electron and Chromium packages all wrap their binaries this way, and
so do a number of individual vendors' launchers elsewhere. Whether a given so do a number of individual vendors' launchers elsewhere. Whether a given
@@ -131,23 +142,22 @@ that checks its files finds nothing to repair, and the argument survives
however Steam was started - from the menu, from a game shortcut, from a however Steam was started - from the menu, from a game shortcut, from a
launcher like Heroic or Lutris, from a terminal. launcher like Heroic or Lutris, from a terminal.
That is what has to work, because there is no way to make every possible way of That is what has to work, because it is the only thing that does. A patch that
starting Steam carry an argument. *-noverifyfiles* is the second line rather changes the length is never written, whatever else could be done to cover for
than the first: it covers the case where the script has no comments left to pay it: Steam checks its files at the shutdown it runs itself as well as at a start
for the argument and the patch has to grow the file. It goes on Steam's it was given arguments for, and that one carries no arguments of anybody's. One
launcher entry, on its entry in _~/.config/autostart_, which Steam writes as wrong length costs the whole client package downloaded, extracted and installed
soon as it is set to run at login, and on the shortcuts Steam writes for single again, and a client that quits at the end of it instead of coming up.
games, on the desktop and in the menu alike. A game is not an application this
program has anything to offer and none of them is listed under *Applications*,
but starting one is a Steam start like any other. The trade-off of the switch
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.
A patch that does change the size is still held back while the client is So where the comments cannot pay for the argument, or a client update has
running: Steam puts its own copy back, the two would only undo each other, and changed how the helper is started, nothing is written and the status screen
the helper is started once, at the start, so patching again would not help that says Steam is not patched. Nothing goes on Steam's launcher entry, its
session anyway. It goes in at the next apply with Steam closed. A patch that autostart entry or the shortcuts it writes for single games either, and Steam
keeps the size has nothing to wait for and goes in either way. goes on repairing its own installation.
An installation patched by an earlier version, which appended where the
comments were short, is written again to fit at the next apply, or taken back
if it cannot be made to.
A client update brings a new version of the script. The watcher notices and A client update brings a new version of the script. The watcher notices and
patches it again, and the copy kept for undoing is replaced with the new patches it again, and the copy kept for undoing is replaced with the new
+4 -10
View File
@@ -43,9 +43,6 @@ msgstr "nicht erkennbar"
msgid "not patched yet" msgid "not patched yet"
msgstr "noch nicht gepatcht" msgstr "noch nicht gepatcht"
msgid "waiting until Steam is closed"
msgstr "wartet, bis Steam geschlossen ist"
#. Status block #. Status block
msgid "Autoscroll" msgid "Autoscroll"
@@ -155,9 +152,6 @@ msgstr "Autoscroll ist für %d Anwendungen an."
msgid "Applications that are already running pick this up when they are restarted." msgid "Applications that are already running pick this up when they are restarted."
msgstr "Bereits laufende Anwendungen übernehmen das nach einem Neustart." msgstr "Bereits laufende Anwendungen übernehmen das nach einem Neustart."
msgid "Steam's launcher now skips its file check, which is what keeps the change from being undone at every start."
msgstr "Steams Startereintrag überspringt jetzt die Dateiprüfung – nur so bleibt die Änderung bei jedem Start erhalten."
msgid "Could not start the watcher for newly installed applications." msgid "Could not start the watcher for newly installed applications."
msgstr "Die Überwachung neu installierter Anwendungen konnte nicht gestartet werden." msgstr "Die Überwachung neu installierter Anwendungen konnte nicht gestartet werden."
@@ -179,11 +173,11 @@ msgstr "Auf %d Anwendungen angewendet."
msgid "Already applied to %d applications." msgid "Already applied to %d applications."
msgstr "Bereits auf %d Anwendungen angewendet." msgstr "Bereits auf %d Anwendungen angewendet."
msgid "Steam starts its interface in a way this version does not recognise; leaving it alone." msgid "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone."
msgstr "Steam startet seine Oberfläche auf eine Art, die diese Version nicht kennt; Steam bleibt unangetastet." msgstr "Steam startet seine Oberfläche auf eine Art, die diese Version nicht ändern kann, ohne dass Steam es merkt; Steam bleibt unangetastet."
msgid "Steam is running and has put its own file back; the change waits until Steam is closed." msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
msgstr "Steam läuft und hat seine eigene Datei wiederhergestellt; die Änderung wird nachgeholt, sobald Steam geschlossen ist." msgstr "Steams Oberfläche lässt sich das Flag nicht geben, ohne eine Datei zu hinterlassen, die Steam neu installieren würde; die Änderung wurde zurückgenommen."
#. Help #. Help
+2 -8
View File
@@ -43,9 +43,6 @@ msgstr ""
msgid "not patched yet" msgid "not patched yet"
msgstr "" msgstr ""
msgid "waiting until Steam is closed"
msgstr ""
#. Status block #. Status block
msgid "Autoscroll" msgid "Autoscroll"
@@ -155,9 +152,6 @@ msgstr ""
msgid "Applications that are already running pick this up when they are restarted." msgid "Applications that are already running pick this up when they are restarted."
msgstr "" msgstr ""
msgid "Steam's launcher now skips its file check, which is what keeps the change from being undone at every start."
msgstr ""
msgid "Could not start the watcher for newly installed applications." msgid "Could not start the watcher for newly installed applications."
msgstr "" msgstr ""
@@ -179,10 +173,10 @@ msgstr ""
msgid "Already applied to %d applications." msgid "Already applied to %d applications."
msgstr "" msgstr ""
msgid "Steam starts its interface in a way this version does not recognise; leaving it alone." msgid "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone."
msgstr "" msgstr ""
msgid "Steam is running and has put its own file back; the change waits until Steam is closed." msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
msgstr "" msgstr ""
+23 -24
View File
@@ -66,6 +66,15 @@ mca_apply() {
MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0 MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0
mca_config_load mca_config_load
# An installation that was set up by a version whose answer was a different
# flag still has that flag where it put it, and nothing below would touch it
# again: a file that is already marked as patched is left alone. Taking
# everything back once, here, is what carries such an installation over.
if [[ "$(mca_state_read flag_scheme 1)" != "$MCA_FLAG_SCHEME" ]]; then
mca_revert_all
fi
mca_scan mca_scan
for i in "${!MCA_IDS[@]}"; do for i in "${!MCA_IDS[@]}"; do
@@ -87,13 +96,16 @@ mca_apply() {
case "$route" in case "$route" in
flags) flags)
mca_flags_apply "$prog" || mca_desktop_apply "$id" "$file" mca_flags_apply "$prog" "$kind" \
|| mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")"
;; ;;
desktop) desktop)
mca_desktop_apply "$id" "$file" mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")"
;; ;;
steam) steam)
mca_steam_desktop_apply "$id" "$file" "$packaging" # Nothing is done to the entry itself: what Steam needs is
# in its own installation, and the same patch serves every
# way of starting the client.
if (( ! steam_done )); then if (( ! steam_done )); then
mca_steam_apply mca_steam_apply
steam_done=1 steam_done=1
@@ -108,30 +120,16 @@ mca_apply() {
mca_steam_apply mca_steam_apply
fi fi
# The shortcuts Steam writes for single games. They are not applications # Autostart entries. A Chromium application that starts itself at login
# and are not offered as ones, but starting a game with Steam closed is a # points straight at its binary and never reads the entry in the menu, so
# Steam start like any other: without the switch the client finds the # Discord at login used to behave differently from Discord from the menu.
# patched helper script, puts its own back, and the interface loses if [[ $CFG_AUTOSTART == yes ]]; then
# autoscroll for the rest of the session.
if [[ $CFG_STEAM == yes ]]; then
for i in "${!MCA_STEAM_LINKS[@]}"; do
mca_steam_desktop_apply "${MCA_STEAM_LINKS[i]}" \
"${MCA_STEAM_LINK_FILES[i]}" "${MCA_STEAM_LINK_PACK[i]}"
done
fi
# 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 mca_autostart_apply
fi fi
# Shortcuts on the desktop itself. Nothing above has seen them - the XDG # Shortcuts on the desktop itself, which nothing above has seen: the XDG
# search path does not go there - and a game started from one starts Steam # search path does not go there. Each entry is gated on its own, so there is
# without its switch, which is the difference between autoscroll working # nothing to check out here.
# and autoscroll working most of the time. Each entry is gated on its own,
# so there is nothing to check out here.
mca_shortcuts_apply mca_shortcuts_apply
[[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply [[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply
@@ -143,6 +141,7 @@ mca_apply() {
fi fi
mca_state_write last_apply "$(date +%s)" mca_state_write last_apply "$(date +%s)"
mca_state_write flag_scheme "$MCA_FLAG_SCHEME"
return 0 return 0
} }
+47 -2
View File
@@ -13,11 +13,46 @@ MCA_PRETTY="Middle-Click Autoscroll"
# The whole point of the package. Blink implements Windows-style autoscroll # The whole point of the package. Blink implements Windows-style autoscroll
# behind a runtime flag that Chromium does not turn on for Linux, because # behind a runtime flag that Chromium does not turn on for Linux, because
# middle click is taken by primary-selection paste there. # middle click is taken by primary-selection paste there.
#
# There are two ways to ask for that flag, and which one is right depends on
# what is being started:
#
# --enable-blink-features=MiddleClickAutoscroll is the one that works
# everywhere, and the only one that works in the Chromium versions embedded
# applications ship - Steam's CEF, an Electron a few years old, anything
# before Chromium 124. A browser started with it puts a yellow bar above the
# page saying an unsupported command line flag is in use, because Chromium
# keeps a list of flags worth warning about and this is on it.
#
# --enable-features=MiddleClickAutoscroll is not on that list, so a browser
# started with it says nothing. Blink generates a feature of the same name
# for every one of its runtime flags, which is what makes the two the same
# request - but only since Chromium 124. Before that the name means nothing
# and is ignored without a word.
#
# So a browser, which is the only thing that shows the bar and the one thing
# that keeps itself up to date, is given the quiet one; everything else keeps
# the one that always works.
MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll" MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll"
# The Blink feature name on its own, for merging into an --enable-blink-features # Helium ships the feature under its own name and does not answer to the
# list that an app (or the user) already carries. # Chromium one, so browsers are asked for both. A name a browser does not know
# is ignored, which is what makes one list safe to hand to all of them.
MCA_BROWSER_FLAG="--enable-features=MiddleClickAutoscroll,HeliumMiddleClickAutoscroll"
# The feature names on their own, for merging into a list that an application
# (or the user) already carries, and for taking them back out again.
MCA_FEATURE="MiddleClickAutoscroll" MCA_FEATURE="MiddleClickAutoscroll"
MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll"
# Bumped when the flags above change, and when what gets written where does.
# An installation that was set up by a version with a different answer is taken
# back and done again once, because a file that is already patched is otherwise
# left alone. See mca_apply.
#
# 3 is what takes -noverifyfiles back out of the Steam entries an older version
# put it in.
MCA_FLAG_SCHEME=3
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}" MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}" MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}"
@@ -258,6 +293,16 @@ mca_ledger_add() {
printf '%s\t%s\t%s\n' "$kind" "$path" "$detail" >> "$MCA_LEDGER" printf '%s\t%s\t%s\n' "$kind" "$path" "$detail" >> "$MCA_LEDGER"
} }
# mca_ledger_has <path>
# Whether the ledger still knows about that file. Worth asking because the copy
# kept beside it is named after the path and outlives a ledger that was lost or
# truncated: a file nothing has a record of any more is one nothing would ever
# look at again.
mca_ledger_has() {
[[ -f $MCA_LEDGER ]] || return 1
awk -F'\t' -v p="$1" '$2 == p { found = 1 } END { exit !found }' "$MCA_LEDGER"
}
mca_ledger_forget() { mca_ledger_forget() {
local path="$1" tmp local path="$1" tmp
[[ -f $MCA_LEDGER ]] || return 0 [[ -f $MCA_LEDGER ]] || return 0
+9 -1
View File
@@ -154,10 +154,18 @@ mca_config_load() {
CFG_SKIP="$(mca_config_get Skip '')" CFG_SKIP="$(mca_config_get Skip '')"
} }
# mca_flags [kind]
# The full argument string that gets injected. Kept in one place so the flag # The full argument string that gets injected. Kept in one place so the flag
# file writer and the desktop entry writer cannot drift apart. # file writer and the desktop entry writer cannot drift apart.
#
# A browser gets the flag that leaves it without a warning bar; everything else
# gets the one that works in every Chromium there is. See MCA_FLAG.
mca_flags() { mca_flags() {
printf '%s' "$MCA_FLAG" if [[ ${1:-app} == browser ]]; then
printf '%s' "$MCA_BROWSER_FLAG"
else
printf '%s' "$MCA_FLAG"
fi
[[ -n ${CFG_EXTRA_FLAGS:-} ]] && printf ' %s' "$CFG_EXTRA_FLAGS" [[ -n ${CFG_EXTRA_FLAGS:-} ]] && printf ' %s' "$CFG_EXTRA_FLAGS"
printf '\n' printf '\n'
} }
+49 -27
View File
@@ -223,6 +223,46 @@ mca_exec_is_steam_link() {
mca_prog_is_steam "$prog" mca_prog_is_steam "$prog"
} }
# _mca_prog_is_steam_name <program>
# The launcher under one of the names Valve and the distributions give it.
_mca_prog_is_steam_name() {
case "${1##*/}" in
steam|steam-runtime|steam-native|steam-jupiter) return 0 ;;
esac
return 1
}
# _mca_prog_is_steam_wrapper <program>
# Whether a script in front of the client is a way of starting Steam. People
# put one there to add a switch of their own, and an entry pointing at it is a
# Steam start like any other - but the script is not named after Steam, so
# nothing above recognises it.
#
# Getting this wrong is worse than it sounds: such a script tends to mention
# the flag it is there to add, which is one of the markers that say "Chromium"
# to the hint scan. The entry then ends up being handled as an application and
# is given the flag on its command line, where Steam ignores it, instead of
# being handed to the Steam module that knows how to reach the web helper.
#
# Only the handover is followed, and only one step of it: the client's own
# launcher is already recognised by name, so a wrapper in front of it is the
# whole of what is left.
_mca_prog_is_steam_wrapper() {
local prog="$1" head='' target
[[ $prog == /* && -f $prog && -r $prog ]] || return 1
# Two characters, read in the shell: the scan asks this about every program
# on the system, and most of them are binaries whose first line is the whole
# file.
read -r -N 2 head < "$prog" 2>/dev/null || return 1
[[ $head == '#!' ]] || return 1
target="$(_mca_script_target "$prog")" || return 1
[[ -n $target ]] || return 1
_mca_prog_is_steam_name "$target"
}
# mca_prog_is_steam <program> # mca_prog_is_steam <program>
# Whether running this program starts the Steam client. Every packaging is in # Whether running this program starts the Steam client. Every packaging is in
# here and every name Valve and the distributions give the launcher, because # here and every name Valve and the distributions give the launcher, because
@@ -234,10 +274,9 @@ mca_exec_is_steam_link() {
mca_prog_is_steam() { mca_prog_is_steam() {
local prog="$1" local prog="$1"
case "${prog##*/}" in _mca_prog_is_steam_name "$prog" && return 0
steam|steam-runtime|steam-native|steam-jupiter) return 0 ;; [[ $prog == flatpak:com.valvesoftware.Steam || $prog == snap:steam ]] && return 0
esac _mca_prog_is_steam_wrapper "$prog"
[[ $prog == flatpak:com.valvesoftware.Steam || $prog == snap:steam ]]
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -576,16 +615,6 @@ MCA_PROGS=() # resolved program, or a Flatpak app id or a snap name
MCA_KINDS=() # app | browser | steam | unknown | no MCA_KINDS=() # app | browser | steam | unknown | no
MCA_PACKAGING=() # native | flatpak | snap MCA_PACKAGING=() # native | flatpak | snap
# The shortcuts Steam writes for single games. Not applications of their own - a
# game is whatever engine it was built with, and none of those reads a Chromium
# argument - so they are kept apart from the list rather than listed as
# something that got switched on. They do start Steam, which is why they are
# kept at all: the Steam module gives them Steam's own switch.
MCA_STEAM_LINKS=() # desktop file id
MCA_STEAM_LINK_FILES=() # the entry that is in effect for it
MCA_STEAM_LINK_PACK=() # native | flatpak | snap, which decides where the
# switch goes on the command line
# A scan reads every desktop entry on the system, so the menu does it once and # A scan reads every desktop entry on the system, so the menu does it once and
# then redraws from what it found. Applying rescans on its own, so nothing else # then redraws from what it found. Applying rescans on its own, so nothing else
# has to remember to invalidate this. # has to remember to invalidate this.
@@ -603,7 +632,6 @@ mca_scan() {
MCA_IDS=(); MCA_FILES=(); MCA_NAMES=(); MCA_PROGS=(); MCA_KINDS=() MCA_IDS=(); MCA_FILES=(); MCA_NAMES=(); MCA_PROGS=(); MCA_KINDS=()
MCA_PACKAGING=() MCA_PACKAGING=()
MCA_STEAM_LINKS=(); MCA_STEAM_LINK_FILES=(); MCA_STEAM_LINK_PACK=()
# Pass one: read the entries and work out what each of them starts. No # Pass one: read the entries and work out what each of them starts. No
# detection yet - that needs a stat per program, and those are collected so # detection yet - that needs a stat per program, and those are collected so
@@ -643,18 +671,12 @@ mca_scan() {
prog="snap:$MCA_PROG" prog="snap:$MCA_PROG"
fi fi
if mca_exec_is_steam_link "$exec_line" "$prog"; then # The shortcuts Steam writes for single games are not
MCA_STEAM_LINKS+=("$id") # applications of their own - a game is whatever engine it was
MCA_STEAM_LINK_FILES+=("$file") # built with, and none of those reads a Chromium argument - and
if [[ $prog == flatpak:* ]]; then # starting the client through one needs nothing on its command
MCA_STEAM_LINK_PACK+=(flatpak) # line either.
elif [[ $prog == snap:* ]]; then mca_exec_is_steam_link "$exec_line" "$prog" && continue
MCA_STEAM_LINK_PACK+=(snap)
else
MCA_STEAM_LINK_PACK+=(native)
fi
continue
fi
c_ids+=("$id"); c_files+=("$file"); c_names+=("$name") c_ids+=("$id"); c_files+=("$file"); c_names+=("$name")
c_progs+=("$prog") c_progs+=("$prog")
-3
View File
@@ -178,9 +178,6 @@ mca_ui_status() {
if mca_steam_installed; then if mca_steam_installed; then
if mca_steam_patched; then if mca_steam_patched; then
_mca_row "$(mca_msg "Steam")" "$(_mca_onoff yes)" _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 elif [[ $CFG_STEAM == yes && $CFG_ENABLED == yes ]]; then
_mca_row "$(mca_msg "Steam")" \ _mca_row "$(mca_msg "Steam")" \
"${MCA_C_YELLOW}$(mca_msg "not patched yet")${MCA_C_RESET}" "${MCA_C_YELLOW}$(mca_msg "not patched yet")${MCA_C_RESET}"
+123 -72
View File
@@ -29,6 +29,40 @@
MCA_MARK_BEGIN='# >>> middleclick-autoscroll' MCA_MARK_BEGIN='# >>> middleclick-autoscroll'
MCA_MARK_END='# <<< middleclick-autoscroll' MCA_MARK_END='# <<< middleclick-autoscroll'
# ---------------------------------------------------------------------------
# Feature lists
# ---------------------------------------------------------------------------
# Both --enable-features and --enable-blink-features take a comma separated
# list, and Chromium keeps the last one it is given and drops the rest. A
# second one of the same kind would therefore silently switch off whatever the
# first one turned on, so an option that is already there is extended instead.
# _mca_list_merge <list> <wanted>
# The two lists as one, in the order they came and without repeating anything.
_mca_list_merge() {
local out="$1" item
for item in ${2//,/ }; do
[[ ",$out," == *",$item,"* ]] && continue
out="${out:+$out,}$item"
done
printf '%s' "$out"
}
# _mca_list_strip <list> <unwanted>
# The list without any of the named entries.
_mca_list_strip() {
local out='' item
for item in ${1//,/ }; do
[[ ",$2," == *",$item,"* ]] && continue
out="${out:+$out,}$item"
done
printf '%s' "$out"
}
# The two options, as a regular expression branch, for the places that have to
# recognise either of them.
MCA_FEATURE_OPTS='--enable-blink-features|--enable-features'
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Command lines # Command lines
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -70,28 +104,32 @@ _mca_exec_after_program() {
# Prints nothing and returns 1 when there was nothing to change, so callers can # Prints nothing and returns 1 when there was nothing to change, so callers can
# tell a real edit from a no-op without diffing. # tell a real edit from a no-op without diffing.
# #
# An --enable-blink-features that is already there is extended rather than # A feature list that is already on the line is extended rather than repeated.
# repeated: Chromium keeps the last occurrence of the option and drops the # See _mca_list_merge.
# rest, so a second one would silently disable whatever the first turned on.
mca_exec_inject() { mca_exec_inject() {
local line="$1" flagstr="${2:-}" where="${3:-before-fields}" local line="$1" flagstr="${2:-}" where="${3:-before-fields}"
local flag value existing at changed=0 local flag opt value existing merged re at changed=0
local -a flags local -a flags
[[ -n $flagstr ]] || flagstr="$(mca_flags)" [[ -n $flagstr ]] || flagstr="$(mca_flags)"
read -r -a flags <<< "$flagstr" read -r -a flags <<< "$flagstr"
for flag in "${flags[@]}"; do for flag in "${flags[@]}"; do
if [[ $flag == --enable-blink-features=* ]]; then case "$flag" in
value="${flag#--enable-blink-features=}" --enable-blink-features=*|--enable-features=*)
if [[ $line =~ --enable-blink-features=([A-Za-z0-9,_-]*) ]]; then opt="${flag%%=*}"
existing="${BASH_REMATCH[1]}" value="${flag#*=}"
[[ ",$existing," == *",$value,"* ]] && continue re="(^|[[:space:]])$opt=([^[:space:]]*)"
line="${line/--enable-blink-features=$existing/--enable-blink-features=${existing:+$existing,}$value}" if [[ $line =~ $re ]]; then
changed=1 existing="${BASH_REMATCH[2]}"
continue merged="$(_mca_list_merge "$existing" "$value")"
fi [[ $merged == "$existing" ]] && continue
fi line="${line/$opt=$existing/$opt=$merged}"
changed=1
continue
fi
;;
esac
[[ " $line " == *" $flag "* ]] && continue [[ " $line " == *" $flag "* ]] && continue
@@ -117,12 +155,12 @@ mca_exec_inject() {
# Flag files # Flag files
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# mca_flags_apply <program> # mca_flags_apply <program> [kind]
# Writes the flags into the file the program's launcher reads. Returns 0 when # Writes the flags into the file the program's launcher reads. Returns 0 when
# the program has such a file (whether or not anything needed changing), 1 when # the program has such a file (whether or not anything needed changing), 1 when
# it has none and the desktop entry route has to be used instead. # it has none and the desktop entry route has to be used instead.
mca_flags_apply() { mca_flags_apply() {
local prog="$1" name target='' existing_target='' local prog="$1" kind="${2:-app}" name target='' existing_target=''
local -a candidates=() local -a candidates=()
mapfile -t candidates < <(mca_flags_candidates "$prog" | awk '!seen[$0]++') mapfile -t candidates < <(mca_flags_candidates "$prog" | awk '!seen[$0]++')
@@ -133,7 +171,7 @@ mca_flags_apply() {
for name in "${candidates[@]}"; do for name in "${candidates[@]}"; do
if [[ -f "$MCA_XDG_CONFIG/$name" ]]; then if [[ -f "$MCA_XDG_CONFIG/$name" ]]; then
existing_target="$name" existing_target="$name"
_mca_flags_write "$MCA_XDG_CONFIG/$name" _mca_flags_write "$MCA_XDG_CONFIG/$name" "$kind"
fi fi
done done
[[ -n $existing_target ]] && return 0 [[ -n $existing_target ]] && return 0
@@ -143,19 +181,21 @@ mca_flags_apply() {
# creating that one also covers every other application using the same # creating that one also covers every other application using the same
# shared Electron. # shared Electron.
target="${candidates[-1]}" target="${candidates[-1]}"
_mca_flags_write "$MCA_XDG_CONFIG/$target" _mca_flags_write "$MCA_XDG_CONFIG/$target" "$kind"
return 0 return 0
} }
# _mca_flags_write <file> # _mca_flags_write <file> [kind]
# Adds the flags as a marked block, or merges into an --enable-blink-features # Adds the flags as a marked block, or merges into a feature list that is
# line that is already in the file. # already in the file.
_mca_flags_write() { _mca_flags_write() {
local file="$1" content='' line flag value existing merged=0 block='' local file="$1" kind="${2:-app}"
local content='' line flag opt want existing merged=0 block=''
local -a flags local -a flags
local -A merged_opts=()
local created=0 local created=0
read -r -a flags <<< "$(mca_flags)" read -r -a flags <<< "$(mca_flags "$kind")"
[[ -f $file ]] || created=1 [[ -f $file ]] || created=1
if (( ! created )); then if (( ! created )); then
@@ -166,16 +206,23 @@ _mca_flags_write() {
fi fi
fi fi
# Merge into a blink-features line the user (or a distribution) put there. # Merge into a feature list the user (or a distribution) put there, rather
if [[ $content == *--enable-blink-features=* ]]; then # than write a second line of the same option further down.
if [[ $content == *--enable-* ]]; then
local out='' local out=''
while IFS= read -r line; do while IFS= read -r line; do
if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then
existing="${BASH_REMATCH[1]}" opt="${BASH_REMATCH[1]}"
if [[ ",$existing," != *",$MCA_FEATURE,"* ]]; then existing="${BASH_REMATCH[2]}"
line="--enable-blink-features=${existing:+$existing,}$MCA_FEATURE" want=''
for flag in "${flags[@]}"; do
[[ $flag == "$opt="* ]] && want="${flag#*=}"
done
if [[ -n $want ]]; then
line="$opt=$(_mca_list_merge "$existing" "$want")"
merged_opts["$opt"]=1
merged=1
fi fi
merged=1
fi fi
out+="$line"$'\n' out+="$line"$'\n'
done <<< "$content" done <<< "$content"
@@ -183,7 +230,7 @@ _mca_flags_write() {
fi fi
for flag in "${flags[@]}"; do for flag in "${flags[@]}"; do
(( merged )) && [[ $flag == --enable-blink-features=* ]] && continue [[ -n ${merged_opts["${flag%%=*}"]:-} ]] && continue
block+="$flag"$'\n' block+="$flag"$'\n'
done done
@@ -218,7 +265,7 @@ _mca_flags_drop_block() {
# mca_flags_revert <file> <detail> # mca_flags_revert <file> <detail>
mca_flags_revert() { mca_flags_revert() {
local file="$1" detail="$2" content line out='' existing new local file="$1" detail="$2" content line out='' opt existing new
[[ -f $file ]] || return 0 [[ -f $file ]] || return 0
@@ -238,12 +285,13 @@ mca_flags_revert() {
if [[ $detail == merged* ]]; then if [[ $detail == merged* ]]; then
while IFS= read -r line; do while IFS= read -r line; do
if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then
existing="${BASH_REMATCH[1]}" opt="${BASH_REMATCH[1]}"
new="${existing//$MCA_FEATURE/}" existing="${BASH_REMATCH[2]}"
new="${new//,,/,}"; new="${new#,}"; new="${new%,}" # Either name, whichever version of this program put it there.
new="$(_mca_list_strip "$existing" "$MCA_BROWSER_FEATURES")"
[[ -z $new ]] && continue [[ -z $new ]] && continue
line="--enable-blink-features=$new" line="$opt=$new"
fi fi
out+="$line"$'\n' out+="$line"$'\n'
done <<< "$content" done <<< "$content"
@@ -362,15 +410,6 @@ mca_desktop_apply() {
# The desktop folder, where a shortcut somebody dragged out of the menu - # The desktop folder, where a shortcut somebody dragged out of the menu -
# or asked Steam for - lives and nowhere else. # or asked Steam for - lives and nowhere else.
# #
# Steam is in both, even though it is not a Chromium process itself: its
# entries need -noverifyfiles exactly like the menu one. The autostart entry is
# the one 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. The desktop
# ones are the gap behind "autoscroll works, except sometimes": starting a game
# from the desktop is a Steam start like any other, and a Steam start without
# the switch costs the interface its autoscroll for the rest of the session.
#
# Neither can be shadowed from anywhere, so both are edited where they stand, # Neither can be shadowed from anywhere, so both are edited where they stand,
# with the original kept. # with the original kept.
@@ -378,16 +417,27 @@ mca_desktop_apply() {
# One desktop entry that lives outside the XDG search path, edited where it is # One desktop entry that lives outside the XDG search path, edited where it is
# because there is nowhere to shadow it from. # because there is nowhere to shadow it from.
# #
# Anything that starts Steam gets Steam's own switch; a Chromium application # What decides whether an application here is in scope differs by where the
# gets the flag. What decides whether a Chromium application here is in scope # entry came from, which is what the gate says: an autostart entry follows the
# differs by where the entry came from, which is what the gate says: an # autostart setting, a shortcut follows the same rules as the application it is
# autostart entry follows the autostart setting, a shortcut follows the same # a shortcut to.
# rules as the application it is a shortcut to.
_mca_entry_patch_inplace() { _mca_entry_patch_inplace() {
local file="$1" gate="$2" local file="$1" gate="$2"
local id prog packaging=native content backup kind local id prog packaging=native content backup kind copy
grep -q "^$MCA_MARK_INPLACE=" "$file" 2>/dev/null && return 0 # Already ours, and normally that is the end of it: what is in there is what
# this version put there. Not so for a file the ledger has no record of -
# nothing would ever look at that one again, neither the undo a change of
# flags goes through nor this, which leaves a marked file alone, and it would
# keep an answer from an older version for good. The copy taken before the
# edit is named after the path and is still there, so the file goes back to
# what it was and is written again below.
if grep -q "^$MCA_MARK_INPLACE=" "$file" 2>/dev/null; then
mca_ledger_has "$file" && return 0
copy="$MCA_BACKUPDIR/$(mca_backup_name "$file")"
[[ -f $copy ]] || return 0
cp -p -- "$copy" "$file" 2>/dev/null || return 0
fi
_mca_desktop_read "$file" _mca_desktop_read "$file"
[[ -n $DE_EXEC ]] || return 0 [[ -n $DE_EXEC ]] || return 0
@@ -408,28 +458,29 @@ _mca_entry_patch_inplace() {
packaging=snap packaging=snap
fi fi
if mca_prog_is_steam "$prog"; then # An entry that starts Steam is left where it is. Steam takes nothing
[[ $CFG_STEAM == yes ]] || return 0 # from a command line that would help, and what it does take belongs to
content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \ # the web helper script in its own installation.
"$MCA_STEAM_LAUNCH_FLAG" "$(mca_steam_flag_position "$packaging")")" mca_prog_is_steam "$prog" && return 0
case "$packaging" in
flatpak) mca_flatpak_is_chromium "${prog#flatpak:}" || return 0 ;;
snap) mca_snap_is_chromium "${prog#snap:}" || return 0 ;;
*) mca_is_chromium "$prog" || return 0 ;;
esac
mca_desktop_is_browser && kind=browser || kind=app
if [[ $gate == autostart ]]; then
[[ $CFG_AUTOSTART == yes ]] || return 0
else else
case "$packaging" in id="${file##*/}"; id="${id%.desktop}"
flatpak) mca_flatpak_is_chromium "${prog#flatpak:}" || return 0 ;; mca_kind_wanted "$kind" "$id" "$packaging" || return 0
snap) mca_snap_is_chromium "${prog#snap:}" || return 0 ;;
*) mca_is_chromium "$prog" || return 0 ;;
esac
if [[ $gate == autostart ]]; then
[[ $CFG_AUTOSTART == yes ]] || return 0
else
id="${file##*/}"; id="${id%.desktop}"
mca_desktop_is_browser && kind=browser || kind=app
mca_kind_wanted "$kind" "$id" "$packaging" || return 0
fi
content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")"
fi fi
content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \
"$(mca_flags "$kind")")"
[[ -n $content ]] || return 0 [[ -n $content ]] || return 0
[[ "$content" == "$(< "$file")" ]] && return 0 [[ "$content" == "$(< "$file")" ]] && return 0
+55 -129
View File
@@ -18,25 +18,19 @@
# and the flag survives however Steam was started - from the menu, from a game # and the flag survives however Steam was started - from the menu, from a game
# shortcut, from a launcher like Heroic, from a terminal. # shortcut, from a launcher like Heroic, from a terminal.
# #
# That is the part that has to work, because there is no way to make every # A patch that changes the length is not an option, and no switch covers for
# possible way of starting Steam carry an argument. -noverifyfiles on the # one. Steam checks its files at a start it was given arguments for, but also
# launcher entries is the second line rather than the first: it covers the case # at the shutdown it runs itself - and that one has no -noverifyfiles on its
# where the script has no comments left to pay for the flag and the patch has # command line, whatever the session was started with. What a single wrong
# to grow the file. The trade-off is real and belongs to the user, so it is a # length costs is the whole client package downloaded, extracted and installed
# switch of its own rather than part of the general application handling: with # again, and a client that quits at the end of it instead of coming up. So the
# verification off, Steam no longer repairs a damaged installation by itself. # flag either fits in the space the comments give back, or it is not written at
# # all: no autoscroll in the interface is a smaller thing than a client that
# A patch that does change the size still has to stay out of the way of a # reinstalls itself.
# client that is already running, 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. A patch
# that keeps the size is invisible to that check and goes in either way.
# #
# The file comes back on every client update, and the watcher re-applies the # The file comes back on every client update, and the watcher re-applies the
# patch when that happens. # patch when that happens.
MCA_STEAM_LAUNCH_FLAG='-noverifyfiles'
# Every place a Steam installation is known to live, resolved and de-duplicated # Every place a Steam installation is known to live, resolved and de-duplicated
# because ~/.steam/steam is normally a symlink into ~/.local/share. # because ~/.steam/steam is normally a symlink into ~/.local/share.
# #
@@ -88,24 +82,6 @@ mca_steam_installed() {
return 1 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" \
"$HOME/snap/steam/common/.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 <script> # mca_steam_script_patched <script>
mca_steam_script_patched() { mca_steam_script_patched() {
grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null
@@ -120,26 +96,6 @@ mca_steam_patched() {
return 1 return 1
} }
# mca_steam_deferred <script>
# Whether the patch is being held back rather than simply missing: the script
# was patched before, its own copy is back, and Steam is still running. See
# mca_steam_apply for why that is left alone.
mca_steam_deferred() {
[[ -e "$MCA_BACKUPDIR/$(mca_backup_name "$1")" ]] && mca_steam_running
}
# mca_steam_waiting
# The same question for the status screen, which has no script in hand.
mca_steam_waiting() {
local root script
while IFS= read -r root; do
script="$(mca_steam_script "$root")" || continue
mca_steam_script_patched "$script" && continue
mca_steam_deferred "$script" && return 0
done < <(mca_steam_roots)
return 1
}
# _mca_steam_exec_line <script> # _mca_steam_exec_line <script>
# The line number of the command that starts the web helper: the last # The line number of the command that starts the web helper: the last
# uncommented line that both names steamwebhelper and forwards "$@". Both the # uncommented line that both names steamwebhelper and forwards "$@". Both the
@@ -154,27 +110,25 @@ _mca_steam_exec_line() {
' "$1" 2>/dev/null ' "$1" 2>/dev/null
} }
# _mca_steam_build <script> <flags> <reclaim: 0|1> # _mca_steam_build <script> <flags>
# The patched script on stdout: the flags appended to the line that starts the # The patched script on stdout: the flags appended to the line that starts the
# web helper, and - when asked for - the same number of bytes taken back out of # web helper, and the same number of bytes taken back out of the script's
# the script's comments, so that the file Steam finds is the length Steam wrote # comments, so that the file Steam finds is the length Steam wrote down.
# down. Everything else - setting up the container runtime, deciding whether # Everything else - setting up the container runtime, deciding whether the
# the sandbox can be used - is left alone. # sandbox can be used - is left alone.
# #
# Comments are eaten from the bottom up, so the header that says what the # Comments are eaten from the bottom up, so the header that says what the
# script is for is the last thing to lose anything, and the '#' itself always # script is for is the last thing to lose anything, and the '#' itself always
# stays: a line that loses it stops being a comment and starts being a command. # stays: a line that loses it stops being a comment and starts being a command.
# Fails when the comments are too short to pay for the flags. # Fails when the comments are too short to pay for the flags.
_mca_steam_build() { _mca_steam_build() {
local script="$1" flags="$2" reclaim="$3" lineno need=0 local script="$1" flags="$2" lineno need
lineno="$(_mca_steam_exec_line "$script")" lineno="$(_mca_steam_exec_line "$script")"
[[ -n $lineno ]] || return 1 [[ -n $lineno ]] || return 1
if (( reclaim )); then need="$(printf '%s' " $flags" | wc -c)"
need="$(printf '%s' " $flags" | wc -c)" need="${need//[^0-9]/}"
need="${need//[^0-9]/}"
fi
# LC_ALL=C so awk counts bytes: a comment with an accent in it is shorter # LC_ALL=C so awk counts bytes: a comment with an accent in it is shorter
# in characters than it is on disk, and it is the disk that Steam measures. # in characters than it is on disk, and it is the disk that Steam measures.
@@ -222,44 +176,50 @@ _mca_steam_refresh_backup() {
cp -p -- "$script" "$MCA_BACKUPDIR/$name" 2>/dev/null || true cp -p -- "$script" "$MCA_BACKUPDIR/$name" 2>/dev/null || true
} }
# _mca_steam_reneutralise <script> <flags> # _mca_steam_settle <script> <flags>
# An earlier version of this program patched by appending and left the script # The script is already patched. Whether that is a patch Steam can live with is
# longer than Steam expects, which is a patch that only survives because # a question of length: an earlier version of this program appended the flag
# -noverifyfiles is there to stop Steam looking. If the same flags can be made # where the comments could not pay for it, which leaves the script longer than
# to fit, they are: the file goes back to the length and the timestamp Steam # the file Steam wrote down - and a client that checks its files reinstalls
# wrote down and stops depending on that switch. # itself over that, then quits instead of starting.
# #
# The new copy is built from the untouched original that was kept when the # Rebuilt from the untouched original kept when the script was first patched,
# script was first patched, never from the patched file - patching a patched # never from the patched file, the flag either fits this time or the original
# file is how a flag ends up in there twice. # goes back and Steam is left with a client it recognises.
_mca_steam_reneutralise() { _mca_steam_settle() {
local script="$1" flags="$2" name copy out local script="$1" flags="$2" name copy out
name="$(mca_backup_name "$script")" name="$(mca_backup_name "$script")"
copy="$MCA_BACKUPDIR/$name" copy="$MCA_BACKUPDIR/$name"
[[ -f $copy ]] || return 1
# Already the length Steam expects: there is nothing to gain. # Nothing to compare against, so nothing can be said about the length.
(( $(wc -c < "$script") == $(wc -c < "$copy") )) && return 1 [[ -f $copy ]] || return 0
out="$(_mca_steam_build "$copy" "$flags" 1)" || return 1 # Already the length Steam expects: the patch is a good one.
_mca_steam_same_size "$copy" "$out" || return 1 (( $(wc -c < "$script") == $(wc -c < "$copy") )) && return 0
if mca_write_if_changed "$script" "$out"$'\n'; then if out="$(_mca_steam_build "$copy" "$flags")" && _mca_steam_same_size "$copy" "$out"; then
MCA_CHANGES=$(( MCA_CHANGES + 1 )) if mca_write_if_changed "$script" "$out"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
chmod +x -- "$script" 2>/dev/null || true
touch -r "$copy" -- "$script" 2>/dev/null || true
mca_ledger_add steam "$script" "$name"
return 0
fi fi
chmod +x -- "$script" 2>/dev/null || true
touch -r "$copy" -- "$script" 2>/dev/null || true mca_steam_revert "$script" "$name"
mca_ledger_add steam "$script" "$name" mca_ledger_forget "$script"
mca_note "$(mca_msg "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back.")"
return 0 return 0
} }
# mca_steam_apply # mca_steam_apply
# Puts the flags into every Steam installation that has the script, preferring # Puts the flags into every Steam installation that has the script, and only
# the patch that keeps the file's size and falling back to the one that does # where that can be done without changing how long the file is. Returns 1 when
# not. Returns 1 when there is no Steam to patch at all. # there is no Steam to patch at all.
mca_steam_apply() { mca_steam_apply() {
local root script backup flags found=0 out neutral local root script backup flags found=0 out
flags="$(mca_flags)" flags="$(mca_flags)"
@@ -268,35 +228,21 @@ mca_steam_apply() {
found=1 found=1
if mca_steam_script_patched "$script"; then if mca_steam_script_patched "$script"; then
_mca_steam_reneutralise "$script" "$flags" || true _mca_steam_settle "$script" "$flags"
continue continue
fi fi
_mca_steam_refresh_backup "$script" _mca_steam_refresh_backup "$script"
neutral=1 # Either the comments can pay for the flag or nothing is written.
if ! { out="$(_mca_steam_build "$script" "$flags" 1)" \ # A longer file is one Steam reinstalls itself over; a client update
# that changed how the helper is started leaves no line to patch at
# all. Both end here, and this script is what starts Steam's entire
# interface, so leaving it alone is the only safe answer to either.
if ! { out="$(_mca_steam_build "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out"; } && _mca_steam_same_size "$script" "$out"; }
then then
neutral=0 mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")"
out="$(_mca_steam_build "$script" "$flags" 0)" || {
# A client update changed how the helper is started. Leaving
# the file alone is the only safe answer: this script is what
# starts Steam's entire interface.
mca_note "$(mca_msg "Steam starts its interface in a way this version does not recognise; leaving it alone.")"
continue
}
fi
# The patch has to grow the file, and it is not patched now but was
# before: Steam has just put its own copy back. Patching it again
# while the client watches is what turns one size mismatch into an
# endless update dialog, and it would not help this session anyway -
# the helper is started once, at the start. The patch waits for the
# next apply with Steam closed. A patch that keeps the size has
# nothing to wait for.
if (( ! neutral )) && mca_steam_deferred "$script"; then
mca_note "$(mca_msg "Steam is running and has put its own file back; the change waits until Steam is closed.")"
continue continue
fi fi
@@ -339,23 +285,3 @@ mca_steam_revert() {
chmod +x -- "$script" 2>/dev/null || true chmod +x -- "$script" 2>/dev/null || true
return 0 return 0
} }
# mca_steam_flag_position <packaging>
# Where Steam's own switch goes on a command line. Straight after the program
# for a native or snap entry, because Steam reads its options before the
# steam:// argument that the right-click actions pass. For a Flatpak the first
# token is flatpak itself, which would take the switch for one of its own and
# refuse to start, so there it goes at the end of the arguments - after the
# application id, where flatpak hands everything on to Steam.
mca_steam_flag_position() {
[[ ${1:-native} == flatpak ]] && { printf 'before-fields\n'; return; }
printf 'after-program\n'
}
# mca_steam_desktop_apply <id> <source entry> [packaging]
# Steam restores its own files at every start unless it is told not to verify
# them, so the launcher entry carries that switch.
mca_steam_desktop_apply() {
mca_desktop_apply "$1" "$2" "$MCA_STEAM_LAUNCH_FLAG" \
"$(mca_steam_flag_position "${3:-native}")"
}
-4
View File
@@ -56,10 +56,6 @@ mca_do_enable() {
mca_ok "$(mca_msg "Autoscroll is on for %d applications." "$MCA_N_ON")" mca_ok "$(mca_msg "Autoscroll is on for %d applications." "$MCA_N_ON")"
if [[ $CFG_STEAM == yes ]] && mca_steam_installed; then
mca_note "$(mca_msg "Steam's launcher now skips its file check, which is what keeps the change from being undone at every start.")"
fi
mca_note "$(mca_msg "Applications that are already running pick this up when they are restarted.")" mca_note "$(mca_msg "Applications that are already running pick this up when they are restarted.")"
} }