Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae4e8b9294 | ||
|
|
b4fa0edf28 | ||
|
|
e09b145f3a | ||
|
|
004094ef78 | ||
|
|
0c4e7645d0 | ||
|
|
c777e8280d | ||
|
|
2ea64d0603 | ||
|
|
dcdf2c1df3 | ||
|
|
c1841ec169 | ||
|
|
e1c77f049f | ||
|
|
fa624380b1 | ||
|
|
d8900ef80f | ||
|
|
46e63e9faa | ||
|
|
af82ffec78 | ||
|
|
b7e0ff6fd1 |
No files matched your search
@@ -8,7 +8,7 @@
|
||||
# Overridable so a packager can pass the version it is actually building
|
||||
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
||||
# straight from a checkout, and is what a release tag has to carry.
|
||||
VERSION ?= 1.1.0
|
||||
VERSION ?= 1.5.0
|
||||
|
||||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
@@ -23,13 +23,17 @@ MANDIR ?= $(DATADIR)/man
|
||||
# distribution that still keeps /lib separate from /usr/lib says so here - and
|
||||
# only guessed at when there is no systemd installed to ask.
|
||||
#
|
||||
# A build with a prefix of its own keeps the units under that prefix instead.
|
||||
# systemd searches $(PREFIX)/lib/systemd/user as well, and a file outside the
|
||||
# prefix it was asked for is not this build's to place.
|
||||
# A build with a prefix of its own keeps the units under that prefix instead,
|
||||
# and under share rather than lib. systemd searches both for a system prefix,
|
||||
# but only share for a home one: $XDG_DATA_HOME/systemd/user is a search path
|
||||
# and ~/.local/lib/systemd/user is not, so an install into ~/.local that put
|
||||
# the units in lib would leave the watcher impossible to enable. That prefix is
|
||||
# the one an atomic distribution leaves a user - there is no writing to /usr on
|
||||
# Bazzite or Silverblue without layering a package and rebooting.
|
||||
ifeq ($(PREFIX),/usr)
|
||||
USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user)
|
||||
else
|
||||
USERUNITDIR ?= $(PREFIX)/lib/systemd/user
|
||||
USERUNITDIR ?= $(DATADIR)/systemd/user
|
||||
endif
|
||||
|
||||
LINGUAS := de
|
||||
|
||||
@@ -1,21 +1,82 @@
|
||||
# middleclick-autoscroll
|
||||
|
||||
Middle-click autoscroll for Linux. Hold the middle mouse button, move the
|
||||
mouse, the page scrolls — like on Windows. Works with browsers, Electron apps,
|
||||
Flatpaks, snaps, Steam, and anything else that runs on Chromium under the hood.
|
||||
middleclick-autoscroll (what a great name, wow) is a CLI for linux that enables autoscroll in every application that supports it.
|
||||
Works with browsers, electron apps like discord and spotify, steam, and anything else that runs on chromium.
|
||||
|
||||
No app list to maintain. The program looks at what's actually installed, figures
|
||||
out what's Chromium-based, and handles it.
|
||||
This tool looks at which of your apps run chromium under the hood and applies the necessary steps to get autoscrolling working (often just a feature flag). One install, one command and autoscroll _✨just works✨_ (like on windows).
|
||||
|
||||
## Installing
|
||||
## How to use
|
||||
|
||||
**Arch, CachyOS, EndeavourOS, Manjaro**
|
||||
Just run `middleclick-autoscroll`. This will open the configuration TUI that looks like this:
|
||||
|
||||
```bash
|
||||
paru -S middleclick-autoscroll
|
||||
```
|
||||
Middle-Click Autoscroll
|
||||
|
||||
Autoscroll ON
|
||||
|
||||
Applications covered 11 of 13
|
||||
Not identified 1 - see the applications list
|
||||
Steam ON
|
||||
New applications ON
|
||||
Middle-click paste off
|
||||
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
|
||||
|
||||
>
|
||||
```
|
||||
|
||||
**Debian, Ubuntu, Linux Mint, Pop!_OS**
|
||||
Normally you just need to press `[1]` and the magic is done.
|
||||
Pressing `[3]` lets you see every app that was found and toggle each one individually.
|
||||
|
||||
```
|
||||
Applications
|
||||
|
||||
▸ Steam on (Steam)
|
||||
Chromium on (launcher)
|
||||
Discord on (flag file)
|
||||
Obsidian on (launcher)
|
||||
Spotify on (launcher)
|
||||
Slack off
|
||||
Cursor cannot tell
|
||||
|
||||
Up/Down select - Space turns one on or off - q goes back
|
||||
```
|
||||
|
||||
You can also press `[4]` for more settings.
|
||||
|
||||
## How to install
|
||||
|
||||
**Arch (for the cachyos enjoyers)**
|
||||
|
||||
```bash
|
||||
yay -S middleclick-autoscroll
|
||||
```
|
||||
|
||||
**Fedora (for the mentally stable)**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Bazzite (for the reboot enjoyers)**
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
|
||||
https://felitendo.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
|
||||
sudo rpm-ostree install middleclick-autoscroll
|
||||
systemctl reboot
|
||||
```
|
||||
|
||||
**Debian (for the elderly)**
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
@@ -26,15 +87,7 @@ echo "deb [signed-by=/etc/apt/keyrings/middleclick-autoscroll.gpg] https://felit
|
||||
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 (for the both of you)**
|
||||
|
||||
```bash
|
||||
sudo rpm --import https://felitendo.github.io/middleclick-autoscroll/KEY.gpg
|
||||
@@ -43,40 +96,27 @@ sudo zypper addrepo --gpgcheck --refresh \
|
||||
sudo zypper install middleclick-autoscroll
|
||||
```
|
||||
|
||||
Then run `middleclick-autoscroll enable`. That's it — nothing else to configure.
|
||||
|
||||
Updates come through your package manager like anything else. Without a package,
|
||||
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.
|
||||
|
||||
## How it works
|
||||
|
||||
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
|
||||
flag turns it on:
|
||||
it's off on Linux by default because middle click normally pastes the clipboard (??).
|
||||
This flag turns it on:
|
||||
|
||||
```
|
||||
--enable-blink-features=MiddleClickAutoscroll
|
||||
```
|
||||
|
||||
Doing that for one app is a five-minute job. Doing it for *all* of them — across
|
||||
flag files, desktop entries, Flatpaks, snaps, autostart entries, Steam — so it
|
||||
survives upgrades, is not. That's what this does.
|
||||
But doing that for every app is kinda bothersome and it also might break with updates.
|
||||
That's why I created this small tool to automate that.
|
||||
|
||||
New apps are picked up within a second by a systemd path unit that watches the
|
||||
relevant directories. Without systemd, `middleclick-autoscroll apply` does the
|
||||
Browsers get the same thing but this time without "blink":
|
||||
|
||||
--enable-features=MiddleClickAutoscroll
|
||||
|
||||
But both do the same ¯\_(ツ)_/¯
|
||||
|
||||
New apps are picked up by a systemd path unit that watches the
|
||||
relevant directories. If you hate systemd; `middleclick-autoscroll apply` does the
|
||||
same thing manually.
|
||||
|
||||
## Steam
|
||||
|
||||
Steam's web UI supports autoscroll but has no way to pass extra arguments to its
|
||||
helper. The program patches the helper launch script and adds `-noverifyfiles`
|
||||
to Steam's launcher so the patch sticks. That means Steam won't auto-repair
|
||||
damaged files on its own — you can turn this off separately in the settings.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | |
|
||||
@@ -89,8 +129,6 @@ damaged files on its own — you can turn this off separately in the settings.
|
||||
| `… status` | What's covered |
|
||||
| `… list` | All apps and how they're handled |
|
||||
|
||||
See `man middleclick-autoscroll` for more.
|
||||
|
||||
## Building from source
|
||||
|
||||
```bash
|
||||
|
||||
@@ -18,7 +18,18 @@ on the command line.
|
||||
|
||||
*middleclick-autoscroll* finds every Chromium-based application on the system
|
||||
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
|
||||
assumed from where the launcher came from.
|
||||
|
||||
@@ -71,10 +82,10 @@ Two ways, chosen per application.
|
||||
Where the launcher reads extra arguments from
|
||||
_$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
|
||||
applies to a launch from a terminal as much as one from the menu. An
|
||||
*--enable-blink-features* line that is already in the file is extended
|
||||
rather than duplicated - Chromium keeps only the last occurrence of that
|
||||
option, so a second one would switch the first one off.
|
||||
applies to a launch from a terminal as much as one from the menu. A
|
||||
feature list that is already in the file is extended rather than
|
||||
duplicated - Chromium keeps only the last occurrence of such an option, so
|
||||
a second one would switch the first one off.
|
||||
|
||||
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
|
||||
@@ -102,6 +113,13 @@ Programs that start themselves at login write their own entry into
|
||||
_~/.config/autostart_ pointing straight at their binary, bypassing the menu
|
||||
entry entirely. Those are patched in place as well.
|
||||
|
||||
Shortcuts on the desktop itself are patched in place too. Nothing in the XDG
|
||||
search path looks at that folder, so a shortcut that lives only there would
|
||||
otherwise be invisible - and Steam puts one there for every game somebody asks
|
||||
for a shortcut to. The folder's name is translated, and the name in use is read
|
||||
from _~/.config/user-dirs.dirs_ rather than guessed; the watcher is told about
|
||||
it in a drop-in written when autoscroll is turned on.
|
||||
|
||||
# STEAM
|
||||
|
||||
Steam's interface is CEF and supports the feature, but Steam builds the command
|
||||
@@ -116,26 +134,35 @@ _~/.steam/debian-installation_ for Debian's, and the private tree of the
|
||||
sandbox for the Flatpak and the snap. All of them are looked at.
|
||||
|
||||
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.
|
||||
size and timestamp rather than by content - and restores whatever differs. So
|
||||
the patch is written to look untouched: the bytes the argument costs are taken
|
||||
back out of the script's own comments and the timestamp is put back afterwards,
|
||||
leaving a file exactly as long and exactly as old as Steam left it. A client
|
||||
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
|
||||
launcher like Heroic or Lutris, from a terminal.
|
||||
|
||||
The shortcuts Steam writes for single games carry the switch too. A game is not
|
||||
an application this program has anything to offer and none of them is listed
|
||||
under *Applications*, but starting one with Steam closed is a Steam start like
|
||||
any other, and without the switch it costs the interface its autoscroll for the
|
||||
rest of the session.
|
||||
That is what has to work, because it is the only thing that does. A patch that
|
||||
changes the length is never written, whatever else could be done to cover for
|
||||
it: Steam checks its files at the shutdown it runs itself as well as at a start
|
||||
it was given arguments for, and that one carries no arguments of anybody's. One
|
||||
wrong length costs the whole client package downloaded, extracted and installed
|
||||
again, and a client that quits at the end of it instead of coming up.
|
||||
|
||||
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.
|
||||
So where the comments cannot pay for the argument, or a client update has
|
||||
changed how the helper is started, nothing is written and the status screen
|
||||
says Steam is not patched. Nothing goes on Steam's launcher entry, its
|
||||
autostart entry or the shortcuts it writes for single games either, and Steam
|
||||
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
|
||||
patches it again, and the copy kept for undoing is replaced with the new
|
||||
version at the same time, so undoing puts back the script Steam last shipped
|
||||
rather than the one from before the update.
|
||||
|
||||
# SPOTIFY
|
||||
|
||||
@@ -145,6 +172,35 @@ and has a configuration file with a slot for extra arguments; that slot is
|
||||
where the flag goes. Installed as a plain package, a Flatpak or a snap, it is
|
||||
an ordinary desktop entry and needs nothing special.
|
||||
|
||||
# MIDDLE-CLICK PASTE
|
||||
|
||||
Inside a Chromium application the argument settles this on its own: with
|
||||
autoscroll on, Blink stops pasting the primary selection on middle click,
|
||||
because the button is doing something else now. Everywhere else on the desktop
|
||||
middle click goes on pasting, so the desktop's own switch for it is turned off
|
||||
too.
|
||||
|
||||
KDE has such a switch, and only for a Wayland session, where the paste is a
|
||||
protocol the compositor either offers or does not:
|
||||
|
||||
~/.config/kwinrc, [Wayland], EnablePrimarySelection=false
|
||||
|
||||
It is written with *kwriteconfig6*, the same tool System Settings uses, because
|
||||
kwinrc has a cascade behind it and entries a distribution can mark immutable;
|
||||
an edit that reaches past all that writes something KDE goes on to ignore. KWin
|
||||
reads the key once, while it starts, so the change is true from the next login
|
||||
on rather than straight away.
|
||||
|
||||
What the key said before is recorded, and *disable* puts that back. A key that
|
||||
already said *false* before any of this is left alone and not recorded either,
|
||||
so a paste the user switched off themselves is never switched back on.
|
||||
|
||||
On X11 there is nothing to switch. The primary selection is part of X itself
|
||||
and every toolkit reaches for it on its own; no one place can say no. The
|
||||
status screen leaves the row out where there is nothing to report.
|
||||
|
||||
Turn it off under *Settings* to keep the desktop's middle-click paste.
|
||||
|
||||
# WHAT CANNOT BE DETECTED
|
||||
|
||||
An AppImage keeps its payload in a compressed filesystem, so there is no way to
|
||||
@@ -163,6 +219,11 @@ _~/.local/state/middleclick-autoscroll/ledger_
|
||||
_~/.local/state/middleclick-autoscroll/backup/_
|
||||
Copies of the files that are edited in place rather than shadowed.
|
||||
|
||||
_~/.config/kwinrc_
|
||||
KDE's own configuration. One key in it, *EnablePrimarySelection*, is
|
||||
written when the desktop's middle-click paste is turned off, and put
|
||||
back by *disable*.
|
||||
|
||||
_~/.cache/middleclick-autoscroll/detect_
|
||||
Which programs were found to be Chromium, keyed by size and modification
|
||||
time. Safe to delete.
|
||||
@@ -180,7 +241,7 @@ up at the next *apply* rather than on their own.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*systemctl*(1), *flatpak*(1), *snap*(8)
|
||||
*systemctl*(1), *flatpak*(1), *snap*(8), *kwriteconfig6*(1)
|
||||
|
||||
# AUTHORS
|
||||
|
||||
|
||||
@@ -43,9 +43,6 @@ 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"
|
||||
@@ -66,6 +63,9 @@ msgstr "%d – siehe Anwendungsliste"
|
||||
msgid "Steam"
|
||||
msgstr "Steam"
|
||||
|
||||
msgid "Middle-click paste"
|
||||
msgstr "Mittelklick-Einfügen"
|
||||
|
||||
msgid "New applications"
|
||||
msgstr "Neue Anwendungen"
|
||||
|
||||
@@ -115,6 +115,9 @@ msgstr "Programme, die sich beim Anmelden selbst starten"
|
||||
msgid "Spotify"
|
||||
msgstr "Spotify"
|
||||
|
||||
msgid "Turn off KDE's middle-click paste"
|
||||
msgstr "KDEs Mittelklick-Einfügen abschalten"
|
||||
|
||||
msgid "Apply to newly installed applications"
|
||||
msgstr "Auf neu installierte Anwendungen anwenden"
|
||||
|
||||
@@ -155,9 +158,6 @@ msgstr "Autoscroll ist für %d Anwendungen an."
|
||||
msgid "Applications that are already running pick this up when they are restarted."
|
||||
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."
|
||||
msgstr "Die Überwachung neu installierter Anwendungen konnte nicht gestartet werden."
|
||||
|
||||
@@ -179,11 +179,14 @@ msgstr "Auf %d Anwendungen angewendet."
|
||||
msgid "Already applied to %d applications."
|
||||
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 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 ä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."
|
||||
msgstr "Steam läuft und hat seine eigene Datei wiederhergestellt; die Änderung wird nachgeholt, sobald Steam geschlossen ist."
|
||||
msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
|
||||
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."
|
||||
|
||||
msgid "Middle click stops pasting at the next login."
|
||||
msgstr "Mittelklick fügt ab der nächsten Anmeldung nichts mehr ein."
|
||||
|
||||
|
||||
#. Help
|
||||
|
||||
@@ -43,9 +43,6 @@ msgstr ""
|
||||
msgid "not patched yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "waiting until Steam is closed"
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Status block
|
||||
msgid "Autoscroll"
|
||||
@@ -66,6 +63,9 @@ msgstr ""
|
||||
msgid "Steam"
|
||||
msgstr ""
|
||||
|
||||
msgid "Middle-click paste"
|
||||
msgstr ""
|
||||
|
||||
msgid "New applications"
|
||||
msgstr ""
|
||||
|
||||
@@ -115,6 +115,9 @@ msgstr ""
|
||||
msgid "Spotify"
|
||||
msgstr ""
|
||||
|
||||
msgid "Turn off KDE's middle-click paste"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply to newly installed applications"
|
||||
msgstr ""
|
||||
|
||||
@@ -155,9 +158,6 @@ msgstr ""
|
||||
msgid "Applications that are already running pick this up when they are restarted."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -179,10 +179,13 @@ msgstr ""
|
||||
msgid "Already applied to %d applications."
|
||||
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 ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Middle click stops pasting at the next login."
|
||||
msgstr ""
|
||||
|
||||
|
||||
|
||||
@@ -23,12 +23,18 @@ PathModified=/run/current-system/sw/share/applications
|
||||
PathModified=%h/.nix-profile/share/applications
|
||||
PathModified=%h/.guix-profile/share/applications
|
||||
|
||||
# The desktop folder is watched too - Steam writes a shortcut there for every
|
||||
# game somebody asks for one, and starting a game is a Steam start - but its
|
||||
# name is translated and cannot be written down here. `enable` adds it in a
|
||||
# drop-in, from the name this system uses.
|
||||
|
||||
# Steam replaces the script that starts its interface on every client update,
|
||||
# which takes the patch with it. Where that installation lives depends on how
|
||||
# Steam was installed.
|
||||
PathChanged=%h/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
PathChanged=%h/.steam/debian-installation/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
PathChanged=%h/snap/steam/common/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
|
||||
Unit=middleclick-autoscroll.service
|
||||
|
||||
+76
-18
@@ -66,6 +66,15 @@ mca_apply() {
|
||||
MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0
|
||||
|
||||
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
|
||||
|
||||
for i in "${!MCA_IDS[@]}"; do
|
||||
@@ -87,13 +96,16 @@ mca_apply() {
|
||||
|
||||
case "$route" in
|
||||
flags)
|
||||
mca_flags_apply "$prog" || mca_desktop_apply "$id" "$file"
|
||||
mca_flags_apply "$prog" "$kind" \
|
||||
|| mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")"
|
||||
;;
|
||||
desktop)
|
||||
mca_desktop_apply "$id" "$file"
|
||||
mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")"
|
||||
;;
|
||||
steam)
|
||||
mca_steam_desktop_apply "$id" "$file"
|
||||
# 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
|
||||
mca_steam_apply
|
||||
steam_done=1
|
||||
@@ -108,25 +120,25 @@ mca_apply() {
|
||||
mca_steam_apply
|
||||
fi
|
||||
|
||||
# The shortcuts Steam writes for single games. They are not applications
|
||||
# and are not offered as ones, but starting a game with Steam closed is a
|
||||
# Steam start like any other: without the switch the client finds the
|
||||
# patched helper script, puts its own back, and the interface loses
|
||||
# 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]}"
|
||||
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
|
||||
# Autostart entries. A Chromium application that starts itself at login
|
||||
# points straight at its binary and never reads the entry in the menu, so
|
||||
# Discord at login used to behave differently from Discord from the menu.
|
||||
if [[ $CFG_AUTOSTART == yes ]]; then
|
||||
mca_autostart_apply
|
||||
fi
|
||||
|
||||
# Shortcuts on the desktop itself, which nothing above has seen: the XDG
|
||||
# search path does not go there. Each entry is gated on its own, so there is
|
||||
# nothing to check out here.
|
||||
mca_shortcuts_apply
|
||||
|
||||
[[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply
|
||||
|
||||
# Not about any one application: middle click goes on pasting everywhere
|
||||
# else on the desktop, and KDE is the one desktop that can be told not to.
|
||||
# See kde.sh.
|
||||
[[ $CFG_PASTE == yes ]] && mca_kde_paste_apply
|
||||
|
||||
mca_prune_orphans
|
||||
|
||||
if (( MCA_CHANGES )) && mca_have update-desktop-database; then
|
||||
@@ -134,6 +146,7 @@ mca_apply() {
|
||||
fi
|
||||
|
||||
mca_state_write last_apply "$(date +%s)"
|
||||
mca_state_write flag_scheme "$MCA_FLAG_SCHEME"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -161,6 +174,12 @@ mca_revert() {
|
||||
MCA_UNIT_PATH="middleclick-autoscroll.path"
|
||||
MCA_UNIT_SERVICE="middleclick-autoscroll.service"
|
||||
|
||||
# The desktop folder is watched like every other directory an entry can turn up
|
||||
# in, but its name is translated and the unit that ships with the package
|
||||
# cannot know it. So that one path is written here, into a drop-in, from the
|
||||
# name this system actually uses.
|
||||
MCA_UNIT_DROPIN="${MCA_XDG_CONFIG}/systemd/user/${MCA_UNIT_PATH}.d"
|
||||
|
||||
mca_watch_available() {
|
||||
mca_have systemctl && [[ -n ${XDG_RUNTIME_DIR:-} || -S "/run/user/$(id -u)/bus" ]]
|
||||
}
|
||||
@@ -170,17 +189,56 @@ mca_watch_enabled() {
|
||||
systemctl --user is-enabled --quiet "$MCA_UNIT_PATH" 2>/dev/null
|
||||
}
|
||||
|
||||
# _mca_watch_dropin_write
|
||||
# Returns 0 when the drop-in changed and systemd has to be told, 1 when it was
|
||||
# already right - the same contract as mca_write_if_changed, and for the same
|
||||
# reason: the watcher runs after every desktop entry that appears anywhere on
|
||||
# the system, and a daemon-reload each time would be absurd.
|
||||
_mca_watch_dropin_write() {
|
||||
local dir file="$MCA_UNIT_DROPIN/desktop.conf" content
|
||||
|
||||
dir="$(mca_desktop_folder)"
|
||||
[[ -d $dir ]] || { _mca_watch_dropin_remove; return $?; }
|
||||
|
||||
# A per cent sign starts a specifier in a unit file and has to be doubled
|
||||
# to mean itself. Rare in a folder name, fatal when it happens.
|
||||
content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n'
|
||||
|
||||
mca_write_if_changed "$file" "$content"
|
||||
}
|
||||
|
||||
_mca_watch_dropin_remove() {
|
||||
[[ -e "$MCA_UNIT_DROPIN/desktop.conf" ]] || return 1
|
||||
rm -f -- "$MCA_UNIT_DROPIN/desktop.conf"
|
||||
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
mca_watch_enable() {
|
||||
mca_watch_available || return 1
|
||||
_mca_watch_dropin_write
|
||||
systemctl --user daemon-reload > /dev/null 2>&1 || true
|
||||
systemctl --user enable --now "$MCA_UNIT_PATH" > /dev/null 2>&1 || return 1
|
||||
systemctl --user enable "$MCA_UNIT_SERVICE" > /dev/null 2>&1 || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# mca_watch_refresh
|
||||
# Keeps a running watcher in step with a desktop folder that has been renamed,
|
||||
# which is what a change of session language does to it.
|
||||
mca_watch_refresh() {
|
||||
mca_watch_available || return 0
|
||||
_mca_watch_dropin_write || return 0
|
||||
systemctl --user daemon-reload > /dev/null 2>&1 || true
|
||||
systemctl --user restart "$MCA_UNIT_PATH" > /dev/null 2>&1 || true
|
||||
return 0
|
||||
}
|
||||
|
||||
mca_watch_disable() {
|
||||
mca_watch_available || return 0
|
||||
systemctl --user disable --now "$MCA_UNIT_PATH" > /dev/null 2>&1 || true
|
||||
systemctl --user disable "$MCA_UNIT_SERVICE" > /dev/null 2>&1 || true
|
||||
_mca_watch_dropin_remove && systemctl --user daemon-reload > /dev/null 2>&1
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
+84
-4
@@ -13,11 +13,46 @@ MCA_PRETTY="Middle-Click 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
|
||||
# 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"
|
||||
|
||||
# The Blink feature name on its own, for merging into an --enable-blink-features
|
||||
# list that an app (or the user) already carries.
|
||||
# Helium ships the feature under its own name and does not answer to the
|
||||
# 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_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_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}"
|
||||
@@ -44,6 +79,29 @@ MCA_BACKUPDIR="${MCA_STATEDIR}/backup"
|
||||
# else. See mca_ledger_add.
|
||||
MCA_LEDGER="${MCA_STATEDIR}/ledger"
|
||||
|
||||
# The folder the desktop itself shows. Nothing in the XDG search path looks at
|
||||
# it, so an entry that lives only there is invisible to a scan - and a game
|
||||
# shortcut dragged onto the desktop is exactly that.
|
||||
#
|
||||
# Its name is translated: Schreibtisch, Bureau, Escritorio. The name in use is
|
||||
# in the file xdg-user-dirs writes, so it is read rather than guessed, and
|
||||
# ~/Desktop is only the fallback for a system that has no such file.
|
||||
mca_desktop_folder() {
|
||||
local file="$MCA_XDG_CONFIG/user-dirs.dirs" line dir=''
|
||||
|
||||
if [[ -r $file ]]; then
|
||||
while IFS= read -r line; do
|
||||
[[ $line == XDG_DESKTOP_DIR=* ]] || continue
|
||||
dir="${line#XDG_DESKTOP_DIR=}"
|
||||
dir="${dir%\"}"; dir="${dir#\"}"
|
||||
done < "$file"
|
||||
dir="${dir/#\$HOME/$HOME}"
|
||||
fi
|
||||
|
||||
[[ $dir == /* ]] || dir="$HOME/Desktop"
|
||||
printf '%s\n' "${dir%/}"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Translations
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -220,9 +278,12 @@ mca_state_write() {
|
||||
# shadow <generated desktop entry> <source entry it was generated from>
|
||||
# inplace <edited file> <basename of its backup copy>
|
||||
# flags <flag file> created | appended | merged
|
||||
# kwin <kwinrc> what EnablePrimarySelection said before
|
||||
#
|
||||
# `inplace` and `flags` differ in how they are undone: a backup is restored
|
||||
# wholesale, a flag file only loses the one line that was added to it.
|
||||
# The kinds differ in how they are undone: a backup is restored wholesale, a
|
||||
# flag file only loses the one line that was added to it, and kwinrc gets one
|
||||
# key written back - it is a file KDE writes to itself, and putting a whole
|
||||
# copy of it back would take everything else settled since with it.
|
||||
|
||||
mca_ledger_add() {
|
||||
local kind="$1" path="$2" detail="${3:-}"
|
||||
@@ -235,6 +296,25 @@ mca_ledger_add() {
|
||||
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_detail <path>
|
||||
# The third column of that file's line, empty when the ledger has none. What it
|
||||
# means is the kind's business; for `kwin` it is the value to put back.
|
||||
mca_ledger_detail() {
|
||||
[[ -f $MCA_LEDGER ]] || return 1
|
||||
awk -F'\t' -v p="$1" '$2 == p { d = $3 } END { if (!length(d)) exit 1; print d }' \
|
||||
"$MCA_LEDGER"
|
||||
}
|
||||
|
||||
mca_ledger_forget() {
|
||||
local path="$1" tmp
|
||||
[[ -f $MCA_LEDGER ]] || return 0
|
||||
|
||||
@@ -144,6 +144,7 @@ mca_config_load() {
|
||||
CFG_STEAM=no; mca_config_bool PatchSteam yes && CFG_STEAM=yes
|
||||
CFG_SPOTIFY=no; mca_config_bool PatchSpotify yes && CFG_SPOTIFY=yes
|
||||
CFG_WATCH=no; mca_config_bool WatchNewApps yes && CFG_WATCH=yes
|
||||
CFG_PASTE=no; mca_config_bool DisablePaste yes && CFG_PASTE=yes
|
||||
|
||||
CFG_EXTRA_FLAGS="$(mca_config_get ExtraFlags '')"
|
||||
|
||||
@@ -154,10 +155,18 @@ mca_config_load() {
|
||||
CFG_SKIP="$(mca_config_get Skip '')"
|
||||
}
|
||||
|
||||
# mca_flags [kind]
|
||||
# The full argument string that gets injected. Kept in one place so the flag
|
||||
# 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() {
|
||||
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"
|
||||
printf '\n'
|
||||
}
|
||||
+69
-32
@@ -99,17 +99,6 @@ mca_desktop_dirs() {
|
||||
)
|
||||
}
|
||||
|
||||
# mca_desktop_get <file> <key>
|
||||
# A single value from the [Desktop Entry] group. Desktop Action groups repeat
|
||||
# the same keys, and reading past the first group would pick up the wrong one.
|
||||
mca_desktop_get() {
|
||||
local file="$1" key="$2"
|
||||
awk -v key="$key" '
|
||||
/^[[:space:]]*\[/ { inentry = ($0 ~ /^[[:space:]]*\[Desktop Entry\][[:space:]]*$/); next }
|
||||
inentry && index($0, key "=") == 1 { print substr($0, length(key) + 2); exit }
|
||||
' "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
# _mca_desktop_read <file>
|
||||
# Every key the scan needs, in one pass and without a single fork. There are a
|
||||
# couple of hundred desktop entries on an ordinary system, and doing this with
|
||||
@@ -231,10 +220,63 @@ mca_exec_is_steam_link() {
|
||||
local line="$1" prog="$2"
|
||||
|
||||
[[ $line == *steam://* ]] || return 1
|
||||
case "${prog##*/}" in
|
||||
steam|steam-runtime) return 0 ;;
|
||||
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
|
||||
[[ $prog == flatpak:com.valvesoftware.Steam || $prog == snap:steam ]]
|
||||
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>
|
||||
# Whether running this program starts the Steam client. Every packaging is in
|
||||
# here and every name Valve and the distributions give the launcher, because
|
||||
# the answer decides whether an entry gets Steam's own switch - and an entry
|
||||
# that starts Steam without it undoes the web helper patch on the way up.
|
||||
#
|
||||
# The program is what a scan leaves behind: an absolute path for a native
|
||||
# install, flatpak:<id> or snap:<name> for the other two.
|
||||
mca_prog_is_steam() {
|
||||
local prog="$1"
|
||||
|
||||
_mca_prog_is_steam_name "$prog" && return 0
|
||||
[[ $prog == flatpak:com.valvesoftware.Steam || $prog == snap:steam ]] && return 0
|
||||
_mca_prog_is_steam_wrapper "$prog"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -573,14 +615,6 @@ MCA_PROGS=() # resolved program, or a Flatpak app id or a snap name
|
||||
MCA_KINDS=() # app | browser | steam | unknown | no
|
||||
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
|
||||
|
||||
# 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
|
||||
# has to remember to invalidate this.
|
||||
@@ -598,7 +632,6 @@ mca_scan() {
|
||||
|
||||
MCA_IDS=(); MCA_FILES=(); MCA_NAMES=(); MCA_PROGS=(); MCA_KINDS=()
|
||||
MCA_PACKAGING=()
|
||||
MCA_STEAM_LINKS=(); MCA_STEAM_LINK_FILES=()
|
||||
|
||||
# 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
|
||||
@@ -638,11 +671,12 @@ mca_scan() {
|
||||
prog="snap:$MCA_PROG"
|
||||
fi
|
||||
|
||||
if mca_exec_is_steam_link "$exec_line" "$prog"; then
|
||||
MCA_STEAM_LINKS+=("$id")
|
||||
MCA_STEAM_LINK_FILES+=("$file")
|
||||
continue
|
||||
fi
|
||||
# The shortcuts Steam writes for single games are not
|
||||
# applications of their own - a game is whatever engine it was
|
||||
# built with, and none of those reads a Chromium argument - and
|
||||
# starting the client through one needs nothing on its command
|
||||
# line either.
|
||||
mca_exec_is_steam_link "$exec_line" "$prog" && continue
|
||||
|
||||
c_ids+=("$id"); c_files+=("$file"); c_names+=("$name")
|
||||
c_progs+=("$prog")
|
||||
@@ -664,10 +698,13 @@ mca_scan() {
|
||||
packaging=native
|
||||
|
||||
if [[ $prog == flatpak:* ]]; then
|
||||
prog="${prog#flatpak:}"
|
||||
packaging=flatpak
|
||||
mca_flatpak_is_chromium "$prog" \
|
||||
&& { (( c_browser[i] )) && kind=browser || kind=app; }
|
||||
if mca_prog_is_steam "$prog"; then
|
||||
kind=steam
|
||||
elif mca_flatpak_is_chromium "${prog#flatpak:}"; then
|
||||
(( c_browser[i] )) && kind=browser || kind=app
|
||||
fi
|
||||
prog="${prog#flatpak:}"
|
||||
elif [[ $prog == snap:* ]]; then
|
||||
prog="${prog#snap:}"
|
||||
packaging=snap
|
||||
@@ -676,7 +713,7 @@ mca_scan() {
|
||||
elif mca_snap_is_chromium "$prog"; then
|
||||
(( c_browser[i] )) && kind=browser || kind=app
|
||||
fi
|
||||
elif [[ ${prog##*/} == steam || ${prog##*/} == steam-runtime ]]; then
|
||||
elif mca_prog_is_steam "$prog"; then
|
||||
# Steam is Chromium inside, but nothing about it can be changed
|
||||
# from a command line argument; it has its own module.
|
||||
kind=steam
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# shellcheck shell=bash
|
||||
#
|
||||
# KDE's middle-click paste.
|
||||
#
|
||||
# Inside a Chromium application the flag settles this on its own: with
|
||||
# autoscroll on, Blink stops pasting the primary selection on middle click,
|
||||
# because the button is doing something else now. Everywhere else on the
|
||||
# desktop middle click goes on pasting, which is the behaviour this whole
|
||||
# program exists to get away from - so the desktop's own switch for it is
|
||||
# turned off too.
|
||||
#
|
||||
# KWin has such a switch, and only for a Wayland session, where the paste is a
|
||||
# protocol the compositor either offers or does not:
|
||||
#
|
||||
# ~/.config/kwinrc, [Wayland], EnablePrimarySelection=false
|
||||
#
|
||||
# On X11 there is nothing to switch. The primary selection is part of X itself
|
||||
# and every toolkit reaches for it on its own; no one place can say no.
|
||||
#
|
||||
# KWin reads the key once, while it starts, so what is written here is true
|
||||
# from the next login on rather than straight away.
|
||||
|
||||
MCA_KWINRC="${MCA_XDG_CONFIG}/kwinrc"
|
||||
MCA_KWIN_GROUP="Wayland"
|
||||
MCA_KWIN_KEY="EnablePrimarySelection"
|
||||
|
||||
# The kconfig command line tools, whichever generation this session has. They
|
||||
# are what System Settings writes kwinrc with, and worth the fork: the file has
|
||||
# a cascade behind it - /etc/xdg, the kdedefaults directory - and entries a
|
||||
# distribution can mark immutable, and an edit that reaches past all that with
|
||||
# sed writes something KDE goes on to ignore.
|
||||
MCA_KDE_TOOL=''
|
||||
MCA_KDE_TOOL_LOOKED=0
|
||||
|
||||
_mca_kde_tool() {
|
||||
local gen
|
||||
|
||||
if (( ! MCA_KDE_TOOL_LOOKED )); then
|
||||
MCA_KDE_TOOL_LOOKED=1
|
||||
for gen in 6 5; do
|
||||
if mca_have "kreadconfig$gen" && mca_have "kwriteconfig$gen"; then
|
||||
MCA_KDE_TOOL="$gen"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
[[ -n $MCA_KDE_TOOL ]] || return 1
|
||||
printf '%s\n' "$MCA_KDE_TOOL"
|
||||
}
|
||||
|
||||
# mca_kde_session
|
||||
# Whether this is a KWin Wayland session.
|
||||
mca_kde_session() {
|
||||
local desktop="${XDG_CURRENT_DESKTOP:-}"
|
||||
|
||||
# The watcher is a systemd service, and one started before the session
|
||||
# imported its environment sees none of these variables. A running
|
||||
# kwin_wayland answers both halves of the question at once, and is what
|
||||
# such a run ends up going by.
|
||||
if [[ -z $desktop && -z ${KDE_FULL_SESSION:-} ]]; then
|
||||
mca_have pgrep || return 1
|
||||
pgrep -u "$(id -u)" -x kwin_wayland > /dev/null 2>&1
|
||||
return
|
||||
fi
|
||||
|
||||
# XDG_CURRENT_DESKTOP is the one every desktop sets, and the one to go by
|
||||
# wherever it is set; KDE_FULL_SESSION is what answers when it is not.
|
||||
[[ -z $desktop || ${desktop^^} == *KDE* ]] || return 1
|
||||
|
||||
[[ ${XDG_SESSION_TYPE:-} == wayland || -n ${WAYLAND_DISPLAY:-} ]]
|
||||
}
|
||||
|
||||
mca_kde_available() {
|
||||
mca_kde_session && _mca_kde_tool > /dev/null
|
||||
}
|
||||
|
||||
# mca_kde_read [file]
|
||||
# What the key says now, lowercased, empty when it is not set at all.
|
||||
mca_kde_read() {
|
||||
local file="${1:-$MCA_KWINRC}" gen val
|
||||
gen="$(_mca_kde_tool)" || return 1
|
||||
|
||||
# kreadconfig creates the file it is pointed at, and the status screen -
|
||||
# which asks on every redraw - has no business writing anything. A file
|
||||
# that is not there has no value in it to read anyway.
|
||||
[[ -f $file ]] || return 0
|
||||
|
||||
val="$("kreadconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
|
||||
--key "$MCA_KWIN_KEY" 2>/dev/null)"
|
||||
printf '%s\n' "${val,,}"
|
||||
}
|
||||
|
||||
# mca_kde_write [file] <value>
|
||||
# An empty value takes the key back out rather than writing nothing into it -
|
||||
# a key that is missing and a key that says nothing are not the same thing to
|
||||
# KDE, and only the first one is what an untouched kwinrc looks like.
|
||||
mca_kde_write() {
|
||||
local file="$1" value="$2" gen
|
||||
gen="$(_mca_kde_tool)" || return 1
|
||||
|
||||
if [[ -z $value ]]; then
|
||||
"kwriteconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
|
||||
--key "$MCA_KWIN_KEY" --delete 2>/dev/null
|
||||
else
|
||||
"kwriteconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
|
||||
--key "$MCA_KWIN_KEY" "$value" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# mca_kde_paste_apply
|
||||
# Turns the paste off, and remembers what the key said before so that undoing
|
||||
# puts that back rather than a guess.
|
||||
#
|
||||
# A key that already says false without the ledger knowing about it was set by
|
||||
# the user or by the distribution. That one is left alone and nothing is
|
||||
# recorded, so `disable` cannot switch a paste back on that this program never
|
||||
# switched off.
|
||||
mca_kde_paste_apply() {
|
||||
local current previous changed=0
|
||||
|
||||
mca_kde_available || return 1
|
||||
current="$(mca_kde_read)" || return 1
|
||||
|
||||
if mca_ledger_has "$MCA_KWINRC"; then
|
||||
# Ours already. What goes back is the value recorded the first time
|
||||
# round, not what reading the file now would say - that is false, and
|
||||
# false is what is being undone.
|
||||
previous="$(mca_ledger_detail "$MCA_KWINRC")" || previous=unset
|
||||
elif [[ $current == false ]]; then
|
||||
return 0
|
||||
else
|
||||
previous="${current:-unset}"
|
||||
fi
|
||||
|
||||
if [[ $current != false ]]; then
|
||||
mca_kde_write "$MCA_KWINRC" false || return 1
|
||||
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
changed=1
|
||||
fi
|
||||
|
||||
mca_ledger_add kwin "$MCA_KWINRC" "$previous"
|
||||
|
||||
(( changed )) && mca_note "$(mca_msg "Middle click stops pasting at the next login.")"
|
||||
return 0
|
||||
}
|
||||
|
||||
# mca_kde_paste_revert <file> <previous value>
|
||||
mca_kde_paste_revert() {
|
||||
local file="$1" previous="$2"
|
||||
|
||||
_mca_kde_tool > /dev/null || return 1
|
||||
[[ -f $file ]] || return 1
|
||||
|
||||
# Only what is still recognisably ours. A key somebody has since put a
|
||||
# value of their own into is theirs now.
|
||||
[[ "$(mca_kde_read "$file")" == false ]] || return 1
|
||||
|
||||
[[ $previous == unset ]] && previous=''
|
||||
mca_kde_write "$file" "$previous" || return 1
|
||||
|
||||
# Nothing was in the file but the key, which means there was no file before
|
||||
# this program wrote one. An empty kwinrc says nothing that its absence
|
||||
# does not say better.
|
||||
[[ -s $file ]] || rm -f -- "$file"
|
||||
return 0
|
||||
}
|
||||
+13
-3
@@ -178,9 +178,6 @@ 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}"
|
||||
@@ -197,6 +194,18 @@ mca_ui_status() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Not an ON/OFF like the rows around it: what is being reported is the state
|
||||
# of the paste, not of a switch, and off is the state this program is after.
|
||||
if mca_kde_available; then
|
||||
if [[ "$(mca_kde_read)" == false ]]; then
|
||||
_mca_row "$(mca_msg "Middle-click paste")" \
|
||||
"${MCA_C_GREEN}$(mca_msg "off")${MCA_C_RESET}"
|
||||
else
|
||||
_mca_row "$(mca_msg "Middle-click paste")" \
|
||||
"${MCA_C_DIM}$(mca_msg "on")${MCA_C_RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
_mca_row "$(mca_msg "Last applied")" "$(mca_time_ago "$last")"
|
||||
|
||||
if [[ $CFG_ENABLED == yes ]]; then
|
||||
@@ -219,6 +228,7 @@ MCA_SETTINGS=(
|
||||
"PatchAutostart|bool|yes|Programs that start themselves at login"
|
||||
"PatchSteam|bool|yes|Steam"
|
||||
"PatchSpotify|bool|yes|Spotify"
|
||||
"DisablePaste|bool|yes|Turn off KDE's middle-click paste"
|
||||
"WatchNewApps|bool|yes|Apply to newly installed applications"
|
||||
"ExtraFlags|text||Additional Chromium arguments"
|
||||
)
|
||||
|
||||
+203
-69
@@ -29,6 +29,40 @@
|
||||
MCA_MARK_BEGIN='# >>> 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
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -70,28 +104,32 @@ _mca_exec_after_program() {
|
||||
# Prints nothing and returns 1 when there was nothing to change, so callers can
|
||||
# tell a real edit from a no-op without diffing.
|
||||
#
|
||||
# An --enable-blink-features that is already there is extended rather than
|
||||
# repeated: Chromium keeps the last occurrence of the option and drops the
|
||||
# rest, so a second one would silently disable whatever the first turned on.
|
||||
# A feature list that is already on the line is extended rather than repeated.
|
||||
# See _mca_list_merge.
|
||||
mca_exec_inject() {
|
||||
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
|
||||
|
||||
[[ -n $flagstr ]] || flagstr="$(mca_flags)"
|
||||
read -r -a flags <<< "$flagstr"
|
||||
|
||||
for flag in "${flags[@]}"; do
|
||||
if [[ $flag == --enable-blink-features=* ]]; then
|
||||
value="${flag#--enable-blink-features=}"
|
||||
if [[ $line =~ --enable-blink-features=([A-Za-z0-9,_-]*) ]]; then
|
||||
existing="${BASH_REMATCH[1]}"
|
||||
[[ ",$existing," == *",$value,"* ]] && continue
|
||||
line="${line/--enable-blink-features=$existing/--enable-blink-features=${existing:+$existing,}$value}"
|
||||
case "$flag" in
|
||||
--enable-blink-features=*|--enable-features=*)
|
||||
opt="${flag%%=*}"
|
||||
value="${flag#*=}"
|
||||
re="(^|[[:space:]])$opt=([^[:space:]]*)"
|
||||
if [[ $line =~ $re ]]; then
|
||||
existing="${BASH_REMATCH[2]}"
|
||||
merged="$(_mca_list_merge "$existing" "$value")"
|
||||
[[ $merged == "$existing" ]] && continue
|
||||
line="${line/$opt=$existing/$opt=$merged}"
|
||||
changed=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ " $line " == *" $flag "* ]] && continue
|
||||
|
||||
@@ -117,12 +155,12 @@ mca_exec_inject() {
|
||||
# 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
|
||||
# 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.
|
||||
mca_flags_apply() {
|
||||
local prog="$1" name target='' existing_target=''
|
||||
local prog="$1" kind="${2:-app}" name target='' existing_target=''
|
||||
local -a candidates=()
|
||||
|
||||
mapfile -t candidates < <(mca_flags_candidates "$prog" | awk '!seen[$0]++')
|
||||
@@ -133,7 +171,7 @@ mca_flags_apply() {
|
||||
for name in "${candidates[@]}"; do
|
||||
if [[ -f "$MCA_XDG_CONFIG/$name" ]]; then
|
||||
existing_target="$name"
|
||||
_mca_flags_write "$MCA_XDG_CONFIG/$name"
|
||||
_mca_flags_write "$MCA_XDG_CONFIG/$name" "$kind"
|
||||
fi
|
||||
done
|
||||
[[ -n $existing_target ]] && return 0
|
||||
@@ -143,19 +181,21 @@ mca_flags_apply() {
|
||||
# creating that one also covers every other application using the same
|
||||
# shared Electron.
|
||||
target="${candidates[-1]}"
|
||||
_mca_flags_write "$MCA_XDG_CONFIG/$target"
|
||||
_mca_flags_write "$MCA_XDG_CONFIG/$target" "$kind"
|
||||
return 0
|
||||
}
|
||||
|
||||
# _mca_flags_write <file>
|
||||
# Adds the flags as a marked block, or merges into an --enable-blink-features
|
||||
# line that is already in the file.
|
||||
# _mca_flags_write <file> [kind]
|
||||
# Adds the flags as a marked block, or merges into a feature list that is
|
||||
# already in the file.
|
||||
_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 merged_opts=()
|
||||
local created=0
|
||||
|
||||
read -r -a flags <<< "$(mca_flags)"
|
||||
read -r -a flags <<< "$(mca_flags "$kind")"
|
||||
|
||||
[[ -f $file ]] || created=1
|
||||
if (( ! created )); then
|
||||
@@ -166,24 +206,31 @@ _mca_flags_write() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Merge into a blink-features line the user (or a distribution) put there.
|
||||
if [[ $content == *--enable-blink-features=* ]]; then
|
||||
# Merge into a feature list the user (or a distribution) put there, rather
|
||||
# than write a second line of the same option further down.
|
||||
if [[ $content == *--enable-* ]]; then
|
||||
local out=''
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then
|
||||
existing="${BASH_REMATCH[1]}"
|
||||
if [[ ",$existing," != *",$MCA_FEATURE,"* ]]; then
|
||||
line="--enable-blink-features=${existing:+$existing,}$MCA_FEATURE"
|
||||
fi
|
||||
if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then
|
||||
opt="${BASH_REMATCH[1]}"
|
||||
existing="${BASH_REMATCH[2]}"
|
||||
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
|
||||
out+="$line"$'\n'
|
||||
done <<< "$content"
|
||||
content="${out%$'\n'}"
|
||||
fi
|
||||
|
||||
for flag in "${flags[@]}"; do
|
||||
(( merged )) && [[ $flag == --enable-blink-features=* ]] && continue
|
||||
[[ -n ${merged_opts["${flag%%=*}"]:-} ]] && continue
|
||||
block+="$flag"$'\n'
|
||||
done
|
||||
|
||||
@@ -218,7 +265,7 @@ _mca_flags_drop_block() {
|
||||
|
||||
# mca_flags_revert <file> <detail>
|
||||
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
|
||||
|
||||
@@ -238,12 +285,13 @@ mca_flags_revert() {
|
||||
|
||||
if [[ $detail == merged* ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then
|
||||
existing="${BASH_REMATCH[1]}"
|
||||
new="${existing//$MCA_FEATURE/}"
|
||||
new="${new//,,/,}"; new="${new#,}"; new="${new%,}"
|
||||
if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then
|
||||
opt="${BASH_REMATCH[1]}"
|
||||
existing="${BASH_REMATCH[2]}"
|
||||
# Either name, whichever version of this program put it there.
|
||||
new="$(_mca_list_strip "$existing" "$MCA_BROWSER_FEATURES")"
|
||||
[[ -z $new ]] && continue
|
||||
line="--enable-blink-features=$new"
|
||||
line="$opt=$new"
|
||||
fi
|
||||
out+="$line"$'\n'
|
||||
done <<< "$content"
|
||||
@@ -349,51 +397,120 @@ mca_desktop_apply() {
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Autostart entries
|
||||
# Entries outside the search path
|
||||
# ---------------------------------------------------------------------------
|
||||
# Applications that start themselves at login write their own entry into
|
||||
# ~/.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.
|
||||
# Two directories hold desktop entries that no part of the XDG search path
|
||||
# looks at, so nothing the scan does can reach them:
|
||||
#
|
||||
# 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.
|
||||
# ~/.config/autostart, where applications that start themselves at login
|
||||
# write their own entry, pointing straight at their binary. Those bypass the
|
||||
# entry in the menu completely, which is why Discord launched at login used
|
||||
# to behave differently from Discord launched from the menu.
|
||||
#
|
||||
# The desktop folder, where a shortcut somebody dragged out of the menu -
|
||||
# or asked Steam for - lives and nowhere else.
|
||||
#
|
||||
# Neither can be shadowed from anywhere, so both are edited where they stand,
|
||||
# with the original kept.
|
||||
|
||||
# _mca_entry_patch_inplace <file> <gate: autostart|apps>
|
||||
# One desktop entry that lives outside the XDG search path, edited where it is
|
||||
# because there is nowhere to shadow it from.
|
||||
#
|
||||
# What decides whether an application here is in scope differs by where the
|
||||
# entry came from, which is what the gate says: an autostart entry follows the
|
||||
# autostart setting, a shortcut follows the same rules as the application it is
|
||||
# a shortcut to.
|
||||
_mca_entry_patch_inplace() {
|
||||
local file="$1" gate="$2"
|
||||
local id prog packaging=native content backup kind copy
|
||||
|
||||
# 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"
|
||||
[[ -n $DE_EXEC ]] || return 0
|
||||
[[ $DE_HIDDEN == true ]] && return 0
|
||||
|
||||
mca_exec_program "$DE_EXEC" || return 0
|
||||
prog="$MCA_PROG"
|
||||
|
||||
# A Flatpak or a snap entry names the wrapper rather than the application.
|
||||
# The scan resolves those into an id; outside the scan the same has to
|
||||
# happen here, or every Flatpak looks like the flatpak program itself.
|
||||
if [[ ${prog##*/} == flatpak ]]; then
|
||||
mca_exec_flatpak_id "$DE_EXEC" || return 0
|
||||
prog="flatpak:$MCA_PROG"
|
||||
packaging=flatpak
|
||||
elif mca_snap_name "$prog"; then
|
||||
prog="snap:$MCA_PROG"
|
||||
packaging=snap
|
||||
fi
|
||||
|
||||
# An entry that starts Steam is left where it is. Steam takes nothing
|
||||
# from a command line that would help, and what it does take belongs to
|
||||
# the web helper script in its own installation.
|
||||
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
|
||||
id="${file##*/}"; id="${id%.desktop}"
|
||||
mca_kind_wanted "$kind" "$id" "$packaging" || return 0
|
||||
fi
|
||||
|
||||
content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \
|
||||
"$(mca_flags "$kind")")"
|
||||
|
||||
[[ -n $content ]] || return 0
|
||||
[[ "$content" == "$(< "$file")" ]] && return 0
|
||||
|
||||
backup="$(mca_backup "$file")" || return 0
|
||||
if mca_write_if_changed "$file" "$content"$'\n'; then
|
||||
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
fi
|
||||
mca_ledger_add inplace "$file" "$backup"
|
||||
}
|
||||
|
||||
mca_autostart_apply() {
|
||||
local dir="$MCA_XDG_CONFIG/autostart" file exec_line prog content backup
|
||||
local dir="$MCA_XDG_CONFIG/autostart" file
|
||||
|
||||
[[ -d $dir ]] || return 0
|
||||
|
||||
for file in "$dir"/*.desktop; do
|
||||
[[ -f $file ]] || continue
|
||||
grep -q "^$MCA_MARK_INPLACE=" "$file" 2>/dev/null && continue
|
||||
_mca_entry_patch_inplace "$file" autostart
|
||||
done
|
||||
}
|
||||
|
||||
exec_line="$(mca_desktop_get "$file" Exec)"
|
||||
[[ -n $exec_line ]] || continue
|
||||
mca_shortcuts_apply() {
|
||||
local dir file
|
||||
dir="$(mca_desktop_folder)"
|
||||
|
||||
mca_exec_program "$exec_line" || continue
|
||||
prog="$MCA_PROG"
|
||||
[[ -d $dir ]] || return 0
|
||||
|
||||
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
|
||||
|
||||
backup="$(mca_backup "$file")" || continue
|
||||
if mca_write_if_changed "$file" "$content"$'\n'; then
|
||||
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
fi
|
||||
mca_ledger_add inplace "$file" "$backup"
|
||||
for file in "$dir"/*.desktop; do
|
||||
[[ -f $file ]] || continue
|
||||
_mca_entry_patch_inplace "$file" apps
|
||||
done
|
||||
}
|
||||
|
||||
@@ -446,13 +563,15 @@ mca_spotify_apply() {
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# mca_prune_orphans
|
||||
# Removes generated entries whose application is gone.
|
||||
# Forgets changes there is no longer anything to undo.
|
||||
#
|
||||
# Uninstalling something deletes its entry from /usr/share/applications, but the
|
||||
# copy shadowing it is in the user's home and pacman knows nothing about it. It
|
||||
# would sit in the menu forever, offering to start a program that is no longer
|
||||
# installed - and the watcher would not notice, because a plain apply only ever
|
||||
# looks at what is there now.
|
||||
# looks at what is there now. A file that was edited where it stood and has
|
||||
# since been deleted is the same problem from the other side: its record would
|
||||
# have `disable` put the file back.
|
||||
mca_prune_orphans() {
|
||||
local kind path source line
|
||||
local -a lines=()
|
||||
@@ -462,6 +581,17 @@ mca_prune_orphans() {
|
||||
|
||||
for line in "${lines[@]}"; do
|
||||
IFS=$'\t' read -r kind path source <<< "$line"
|
||||
|
||||
# An entry that was edited where it stood and has since been deleted -
|
||||
# a game shortcut dragged to the wastebasket, an autostart entry the
|
||||
# application removed. There is nothing left to put back, and keeping
|
||||
# the record would have `disable` recreate a file the user threw away.
|
||||
if [[ $kind == inplace && ! -e $path ]]; then
|
||||
[[ -n $source ]] && rm -f -- "$MCA_BACKUPDIR/$source"
|
||||
mca_ledger_forget "$path"
|
||||
continue
|
||||
fi
|
||||
|
||||
[[ $kind == shadow && -n $source ]] || continue
|
||||
[[ -e $source ]] && continue
|
||||
|
||||
@@ -516,6 +646,10 @@ mca_revert_all() {
|
||||
mca_steam_revert "$path" "$detail" \
|
||||
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
;;
|
||||
kwin)
|
||||
mca_kde_paste_revert "$path" "$detail" \
|
||||
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
;;
|
||||
esac
|
||||
done < <(tac "$MCA_LEDGER" 2>/dev/null)
|
||||
|
||||
|
||||
+144
-81
@@ -10,24 +10,27 @@
|
||||
# ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
|
||||
#
|
||||
# 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.
|
||||
# size and timestamp rather than by content - and restores whatever differs.
|
||||
# So the patch is written to look untouched: the bytes the flag costs are taken
|
||||
# back out of the script's comments and the timestamp is put back afterwards,
|
||||
# which leaves a file that is byte-for-byte the size Steam recorded and as old
|
||||
# as Steam left it. A client that verifies its files finds nothing to repair
|
||||
# and the flag survives however Steam was started - from the menu, from a game
|
||||
# shortcut, from a launcher like Heroic, from a terminal.
|
||||
#
|
||||
# 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.
|
||||
# A patch that changes the length is not an option, and no switch covers for
|
||||
# one. Steam checks its files at a start it was given arguments for, but also
|
||||
# at the shutdown it runs itself - and that one has no -noverifyfiles on its
|
||||
# command line, whatever the session was started with. What a single wrong
|
||||
# length costs is the whole client package downloaded, extracted and installed
|
||||
# again, and a client that quits at the end of it instead of coming up. So the
|
||||
# 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
|
||||
# reinstalls itself.
|
||||
#
|
||||
# The file comes back on every client update, and the watcher re-applies the
|
||||
# patch when that happens.
|
||||
|
||||
MCA_STEAM_LAUNCH_FLAG='-noverifyfiles'
|
||||
|
||||
# Every place a Steam installation is known to live, resolved and de-duplicated
|
||||
# because ~/.steam/steam is normally a symlink into ~/.local/share.
|
||||
#
|
||||
@@ -79,24 +82,6 @@ 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" \
|
||||
"$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() {
|
||||
grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null
|
||||
@@ -111,26 +96,6 @@ mca_steam_patched() {
|
||||
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>
|
||||
# The line number of the command that starts the web helper: the last
|
||||
# uncommented line that both names steamwebhelper and forwards "$@". Both the
|
||||
@@ -145,12 +110,116 @@ _mca_steam_exec_line() {
|
||||
' "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# _mca_steam_build <script> <flags>
|
||||
# The patched script on stdout: the flags appended to the line that starts the
|
||||
# web helper, and the same number of bytes taken back out of the script's
|
||||
# comments, so that the file Steam finds is the length Steam wrote down.
|
||||
# Everything else - setting up the container runtime, deciding whether the
|
||||
# sandbox can be used - is left alone.
|
||||
#
|
||||
# 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
|
||||
# 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.
|
||||
_mca_steam_build() {
|
||||
local script="$1" flags="$2" lineno need
|
||||
|
||||
lineno="$(_mca_steam_exec_line "$script")"
|
||||
[[ -n $lineno ]] || return 1
|
||||
|
||||
need="$(printf '%s' " $flags" | wc -c)"
|
||||
need="${need//[^0-9]/}"
|
||||
|
||||
# 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.
|
||||
LC_ALL=C awk -v n="$lineno" -v f=" $flags" -v need="$need" '
|
||||
{ lines[NR] = (NR == n ? $0 f : $0) }
|
||||
END {
|
||||
for (i = NR; i >= 2 && need > 0; i--) {
|
||||
if (i == n) continue
|
||||
p = index(lines[i], "#")
|
||||
if (p == 0) continue
|
||||
# Only a line that is nothing but a comment; a trailing one
|
||||
# sits behind a command that has to stay intact.
|
||||
if (substr(lines[i], 1, p - 1) ~ /[^ \t]/) continue
|
||||
can = length(lines[i]) - p
|
||||
if (can <= 0) continue
|
||||
if (can > need) can = need
|
||||
lines[i] = substr(lines[i], 1, length(lines[i]) - can)
|
||||
need -= can
|
||||
}
|
||||
if (need > 0) exit 1
|
||||
for (i = 1; i <= NR; i++) print lines[i]
|
||||
}
|
||||
' "$script"
|
||||
}
|
||||
|
||||
# _mca_steam_same_size <script> <content>
|
||||
# Whether writing that content leaves the file at the size it has now. Measured
|
||||
# rather than worked out: a script without a final newline gains one on the way
|
||||
# through awk, and one byte is all it takes for Steam to notice.
|
||||
_mca_steam_same_size() {
|
||||
local script="$1" content="$2"
|
||||
(( $(wc -c < "$script") == $(printf '%s\n' "$content" | wc -c) ))
|
||||
}
|
||||
|
||||
# _mca_steam_refresh_backup <script>
|
||||
# A client update brings a new version of the script, and the copy kept from
|
||||
# before it is no longer what putting it back means. Only ever done while the
|
||||
# script is unpatched, so the copy that replaces it is Steam's own.
|
||||
_mca_steam_refresh_backup() {
|
||||
local script="$1" name
|
||||
name="$(mca_backup_name "$script")"
|
||||
|
||||
[[ -e "$MCA_BACKUPDIR/$name" ]] || return 0
|
||||
cmp -s -- "$script" "$MCA_BACKUPDIR/$name" && return 0
|
||||
cp -p -- "$script" "$MCA_BACKUPDIR/$name" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# _mca_steam_settle <script> <flags>
|
||||
# The script is already patched. Whether that is a patch Steam can live with is
|
||||
# a question of length: an earlier version of this program appended the flag
|
||||
# where the comments could not pay for it, which leaves the script longer than
|
||||
# the file Steam wrote down - and a client that checks its files reinstalls
|
||||
# itself over that, then quits instead of starting.
|
||||
#
|
||||
# Rebuilt from the untouched original kept when the script was first patched,
|
||||
# never from the patched file, the flag either fits this time or the original
|
||||
# goes back and Steam is left with a client it recognises.
|
||||
_mca_steam_settle() {
|
||||
local script="$1" flags="$2" name copy out
|
||||
|
||||
name="$(mca_backup_name "$script")"
|
||||
copy="$MCA_BACKUPDIR/$name"
|
||||
|
||||
# Nothing to compare against, so nothing can be said about the length.
|
||||
[[ -f $copy ]] || return 0
|
||||
|
||||
# Already the length Steam expects: the patch is a good one.
|
||||
(( $(wc -c < "$script") == $(wc -c < "$copy") )) && return 0
|
||||
|
||||
if out="$(_mca_steam_build "$copy" "$flags")" && _mca_steam_same_size "$copy" "$out"; then
|
||||
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
|
||||
|
||||
mca_steam_revert "$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
|
||||
}
|
||||
|
||||
# mca_steam_apply
|
||||
# Appends the flags to the line that starts the web helper. Everything else in
|
||||
# the script - setting up the container runtime, deciding whether the sandbox
|
||||
# can be used - is left alone.
|
||||
# Puts the flags into every Steam installation that has the script, and only
|
||||
# where that can be done without changing how long the file is. Returns 1 when
|
||||
# there is no Steam to patch at all.
|
||||
mca_steam_apply() {
|
||||
local root script backup flags lineno found=0 out
|
||||
local root script backup flags found=0 out
|
||||
|
||||
flags="$(mca_flags)"
|
||||
|
||||
@@ -158,34 +227,37 @@ mca_steam_apply() {
|
||||
script="$(mca_steam_script "$root")" || continue
|
||||
found=1
|
||||
|
||||
mca_steam_script_patched "$script" && continue
|
||||
|
||||
# Not patched now, but patched 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.
|
||||
if 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.")"
|
||||
if mca_steam_script_patched "$script"; then
|
||||
_mca_steam_settle "$script" "$flags"
|
||||
continue
|
||||
fi
|
||||
|
||||
lineno="$(_mca_steam_exec_line "$script")"
|
||||
if [[ -z $lineno ]]; then
|
||||
# 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.")"
|
||||
_mca_steam_refresh_backup "$script"
|
||||
|
||||
# Either the comments can pay for the flag or nothing is written.
|
||||
# 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"; }
|
||||
then
|
||||
mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")"
|
||||
continue
|
||||
fi
|
||||
|
||||
out="$(awk -v n="$lineno" -v f=" $flags" 'NR == n { $0 = $0 f } { print }' "$script")" || continue
|
||||
|
||||
backup="$(mca_backup "$script")" || continue
|
||||
if mca_write_if_changed "$script" "$out"$'\n'; then
|
||||
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
fi
|
||||
chmod +x -- "$script" 2>/dev/null || true
|
||||
|
||||
# Steam looks at when the file was last written as well as at how big
|
||||
# it is, and the copy taken before the edit still carries the original
|
||||
# timestamp. Putting it back costs nothing and removes the other half
|
||||
# of what the client would notice.
|
||||
touch -r "$MCA_BACKUPDIR/$backup" -- "$script" 2>/dev/null || true
|
||||
|
||||
mca_ledger_add steam "$script" "$backup"
|
||||
done < <(mca_steam_roots)
|
||||
|
||||
@@ -213,12 +285,3 @@ mca_steam_revert() {
|
||||
chmod +x -- "$script" 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# mca_steam_desktop_apply <id> <source entry>
|
||||
# Steam restores its own files at every start unless it is told not to verify
|
||||
# them, so the launcher entry carries that switch. It goes directly after the
|
||||
# program because Steam reads its options before the steam:// argument that the
|
||||
# right-click actions pass.
|
||||
mca_steam_desktop_apply() {
|
||||
mca_desktop_apply "$1" "$2" "$MCA_STEAM_LAUNCH_FLAG" after-program
|
||||
}
|
||||
@@ -17,7 +17,7 @@ set -uo pipefail
|
||||
|
||||
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
|
||||
|
||||
for _mod in common config detect patch steam apply menu; do
|
||||
for _mod in common config detect patch steam kde apply menu; do
|
||||
# shellcheck source=/dev/null
|
||||
if ! source "$MCA_LIBDIR/$_mod.sh"; then
|
||||
printf 'middleclick-autoscroll: cannot load %s/%s.sh\n' "$MCA_LIBDIR" "$_mod" >&2
|
||||
@@ -56,10 +56,6 @@ mca_do_enable() {
|
||||
|
||||
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.")"
|
||||
}
|
||||
|
||||
@@ -119,8 +115,12 @@ mca_do_apply() {
|
||||
mca_ok "$(mca_msg "Already applied to %d applications." "$MCA_N_ON")"
|
||||
fi
|
||||
|
||||
if [[ $CFG_WATCH == yes ]] && mca_watch_available && ! mca_watch_enabled; then
|
||||
if [[ $CFG_WATCH == yes ]] && mca_watch_available; then
|
||||
if mca_watch_enabled; then
|
||||
mca_watch_refresh
|
||||
else
|
||||
mca_watch_enable || true
|
||||
fi
|
||||
elif [[ $CFG_WATCH != yes ]] && mca_watch_enabled; then
|
||||
mca_watch_disable
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user