Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fbe8bdb39 | ||
|
|
a992efb0cb | ||
|
|
b97ccf0379 | ||
|
|
7a446364dc | ||
|
|
449a20ce9b | ||
|
|
ae4e8b9294 | ||
|
|
b4fa0edf28 | ||
|
|
e09b145f3a | ||
|
|
004094ef78 | ||
|
|
0c4e7645d0 | ||
|
|
c777e8280d | ||
|
|
2ea64d0603 | ||
|
|
dcdf2c1df3 | ||
|
|
c1841ec169 |
No files matched your search
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: Felitendo
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: felitendo
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
@@ -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.3.0
|
VERSION ?= 1.5.1
|
||||||
|
|
||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
@@ -23,13 +23,17 @@ MANDIR ?= $(DATADIR)/man
|
|||||||
# distribution that still keeps /lib separate from /usr/lib says so here - and
|
# distribution that still keeps /lib separate from /usr/lib says so here - and
|
||||||
# only guessed at when there is no systemd installed to ask.
|
# 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.
|
# 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
|
# and under share rather than lib. systemd searches both for a system prefix,
|
||||||
# prefix it was asked for is not this build's to place.
|
# 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)
|
ifeq ($(PREFIX),/usr)
|
||||||
USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user)
|
USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user)
|
||||||
else
|
else
|
||||||
USERUNITDIR ?= $(PREFIX)/lib/systemd/user
|
USERUNITDIR ?= $(DATADIR)/systemd/user
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LINGUAS := de
|
LINGUAS := de
|
||||||
|
|||||||
@@ -1,9 +1,55 @@
|
|||||||
# middleclick-autoscroll
|
# middleclick-autoscroll
|
||||||
|
|
||||||
Middle-click autoscroll is a CLI for Linux that enables autoscroll in every application that supports it.
|
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 under the hood.
|
Works with browsers, electron apps like discord and spotify, steam, and anything else that runs on chromium.
|
||||||
|
|
||||||
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.
|
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).
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Just run `middleclick-autoscroll`. This will open the configuration TUI that looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
## How to install
|
||||||
|
|
||||||
@@ -21,6 +67,15 @@ sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
|
|||||||
sudo dnf install middleclick-autoscroll
|
sudo dnf install middleclick-autoscroll
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Bazzite**
|
||||||
|
|
||||||
|
```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**
|
**Debian**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -41,94 +96,37 @@ sudo zypper addrepo --gpgcheck --refresh \
|
|||||||
sudo zypper install middleclick-autoscroll
|
sudo zypper install middleclick-autoscroll
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
Just run `middleclick-autoscroll`. This will open the configuration TUI that looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
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. This
|
it's off on Linux by default because middle click normally pastes the clipboard (??).
|
||||||
flag turns it on:
|
This flag turns it on:
|
||||||
|
|
||||||
--enable-blink-features=MiddleClickAutoscroll
|
--enable-blink-features=MiddleClickAutoscroll
|
||||||
|
|
||||||
But doing that for every app is kinda bothersome and it also might break with updates.
|
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.
|
That's why I created this small tool to automate that.
|
||||||
|
|
||||||
Browsers get the same thing asked for differently:
|
Browsers get the same thing but this time without "blink":
|
||||||
|
|
||||||
--enable-features=MiddleClickAutoscroll
|
--enable-features=MiddleClickAutoscroll
|
||||||
|
|
||||||
Both mean the same feature - Blink generates a matching feature name for each of
|
But both do the same ¯\_(ツ)_/¯
|
||||||
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
|
Except in Helium and Brave, which have their own names for it
|
||||||
command-line flag" bar over every page. The second one is not on that list, so
|
(`HeliumMiddleClickAutoscroll`, and Brave's is actually spelled
|
||||||
there's no bar. It only works from Chromium 124 on, which is why apps that
|
`MiddelButtonClickAutoscroll`), so browsers get those as well.
|
||||||
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
|
New apps are picked up by a systemd path unit that watches the
|
||||||
relevant directories. Without systemd, `middleclick-autoscroll apply` does the
|
relevant directories. If you hate systemd; `middleclick-autoscroll apply` does the
|
||||||
same thing manually.
|
same thing manually.
|
||||||
|
|
||||||
## Steam
|
AppImages get looked into as well. The payload is a squashfs image glued onto
|
||||||
|
the runtime, and squashfs keeps a small table with the name of every file in
|
||||||
Steam's web UI supports autoscroll but has no way to pass extra arguments to its
|
there - so that table gets read and unpacked (a few kb, nothing is extracted
|
||||||
helper, so the program patches the script that starts it. Steam checks its own
|
and the thing is never run) and the Chromium files are looked for in it. An
|
||||||
files at every start and repairs whatever looks changed, so the patch is written
|
Electron AppImage is covered like any other app; a Tauri one is left alone,
|
||||||
to look unchanged.
|
because WebKitGTK simply has no autoscroll to switch on.
|
||||||
|
|
||||||
As a fallback, for the case where that isn't possible, `-noverifyfiles` goes on
|
|
||||||
everything that starts Steam: its launcher, its autostart entry, and the
|
|
||||||
shortcuts it writes for single games. That one means Steam won't auto-repair
|
|
||||||
damaged files on its own just so you're aware of that.
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ 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
|
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,
|
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
|
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
|
older Chromium, such as Steam's CEF, has no such bar to show anyway. Two
|
||||||
knows the feature under a name of its own and is asked for
|
browsers know the feature under a name of their own and ignore Chromium's:
|
||||||
*HeliumMiddleClickAutoscroll* as well; a name a browser does not know is
|
Helium, which is asked for *HeliumMiddleClickAutoscroll* as well, and Brave,
|
||||||
ignored. It works on any distribution:
|
which is asked for *MiddelButtonClickAutoscroll* - spelled the way Brave spells
|
||||||
|
it. 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.
|
||||||
|
|
||||||
@@ -142,23 +143,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
|
||||||
@@ -173,11 +173,66 @@ 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
|
where the flag goes. Installed as a plain package, a Flatpak or a snap, it is
|
||||||
an ordinary desktop entry and needs nothing special.
|
an ordinary desktop entry and needs nothing special.
|
||||||
|
|
||||||
# WHAT CANNOT BE DETECTED
|
# MIDDLE-CLICK PASTE
|
||||||
|
|
||||||
An AppImage keeps its payload in a compressed filesystem, so there is no way to
|
Inside a Chromium application the argument settles this on its own: with
|
||||||
tell from the outside whether it contains Chromium. Those are listed as *cannot
|
autoscroll on, Blink stops pasting the primary selection on middle click,
|
||||||
tell* and left alone until they are switched on from the applications screen.
|
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.
|
||||||
|
|
||||||
|
# APPIMAGES
|
||||||
|
|
||||||
|
An AppImage is the AppImage runtime, an ordinary executable, with a squashfs
|
||||||
|
image appended to it. The payload is compressed, so none of the files that
|
||||||
|
identify Chromium is on disk where it could be found.
|
||||||
|
|
||||||
|
The names are, though. squashfs keeps the name of everything it holds in one
|
||||||
|
table of its own, a few kilobytes of it, and a name is all this question needs -
|
||||||
|
so that table is read and decompressed in place. Nothing is unpacked, and the
|
||||||
|
image is never run: this can happen from the watcher, and starting a program
|
||||||
|
because it was installed is not something a scan gets to do.
|
||||||
|
|
||||||
|
Two kinds of image are still out of reach. One is packed with *lzo* or *lz4*,
|
||||||
|
which squashfs stores as bare blocks that neither tool will read without the
|
||||||
|
framing their own file formats add. The other is the original AppImage layout,
|
||||||
|
which is an ISO9660 filesystem rather than a squashfs one. Both are listed as
|
||||||
|
*cannot tell* and left alone until they are switched on from the applications
|
||||||
|
screen.
|
||||||
|
|
||||||
|
# WHAT HAS NO FLAG AT ALL
|
||||||
|
|
||||||
|
Autoscroll is a Blink feature, so only an application drawn by Blink can be
|
||||||
|
given it. An application built on WebKitGTK - which is what Tauri uses on
|
||||||
|
Linux, and GNOME Web, and anything else linked against libwebkit2gtk - is not
|
||||||
|
one of those. WebKit has no equivalent feature to ask for, on any command line
|
||||||
|
or in any configuration file.
|
||||||
|
|
||||||
|
Such an application is identified as what it is and then left out of the list
|
||||||
|
entirely, rather than being offered as something that could be switched on. An
|
||||||
|
application that is absent from *list* is absent because there is nothing this
|
||||||
|
tool could do for it.
|
||||||
|
|
||||||
# FILES
|
# FILES
|
||||||
|
|
||||||
@@ -191,9 +246,17 @@ _~/.local/state/middleclick-autoscroll/ledger_
|
|||||||
_~/.local/state/middleclick-autoscroll/backup/_
|
_~/.local/state/middleclick-autoscroll/backup/_
|
||||||
Copies of the files that are edited in place rather than shadowed.
|
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_
|
_~/.cache/middleclick-autoscroll/detect_
|
||||||
Which programs were found to be Chromium, keyed by size and modification
|
Which programs were found to be Chromium, keyed by size and modification
|
||||||
time. Safe to delete.
|
time. Its first line says which version of the program wrote it, and a
|
||||||
|
file from an older one is ignored rather than trusted - an entry for a
|
||||||
|
file that has not changed would otherwise never be looked at again.
|
||||||
|
Safe to delete.
|
||||||
|
|
||||||
# ENVIRONMENT
|
# ENVIRONMENT
|
||||||
|
|
||||||
@@ -206,9 +269,14 @@ Bash 4.2 or newer and GNU coreutils. The watcher needs a systemd user session;
|
|||||||
without one everything else works and applications installed later are picked
|
without one everything else works and applications installed later are picked
|
||||||
up at the next *apply* rather than on their own.
|
up at the next *apply* rather than on their own.
|
||||||
|
|
||||||
|
*gzip* is what reads the name table of an AppImage, and *xz* or *zstd* the ones
|
||||||
|
packed with those instead. None of the three is required: an image whose
|
||||||
|
compressor has no tool installed is listed as *cannot tell*, exactly like the
|
||||||
|
two formats there is no reader for at all.
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|
||||||
*systemctl*(1), *flatpak*(1), *snap*(8)
|
*systemctl*(1), *flatpak*(1), *snap*(8), *kwriteconfig6*(1)
|
||||||
|
|
||||||
# AUTHORS
|
# AUTHORS
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -66,6 +63,9 @@ msgstr "%d – siehe Anwendungsliste"
|
|||||||
msgid "Steam"
|
msgid "Steam"
|
||||||
msgstr "Steam"
|
msgstr "Steam"
|
||||||
|
|
||||||
|
msgid "Middle-click paste"
|
||||||
|
msgstr "Mittelklick-Einfügen"
|
||||||
|
|
||||||
msgid "New applications"
|
msgid "New applications"
|
||||||
msgstr "Neue Anwendungen"
|
msgstr "Neue Anwendungen"
|
||||||
|
|
||||||
@@ -115,6 +115,9 @@ msgstr "Programme, die sich beim Anmelden selbst starten"
|
|||||||
msgid "Spotify"
|
msgid "Spotify"
|
||||||
msgstr "Spotify"
|
msgstr "Spotify"
|
||||||
|
|
||||||
|
msgid "Turn off KDE's middle-click paste"
|
||||||
|
msgstr "KDEs Mittelklick-Einfügen abschalten"
|
||||||
|
|
||||||
msgid "Apply to newly installed applications"
|
msgid "Apply to newly installed applications"
|
||||||
msgstr "Auf neu installierte Anwendungen anwenden"
|
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."
|
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 +179,14 @@ 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."
|
||||||
|
|
||||||
|
msgid "Middle click stops pasting at the next login."
|
||||||
|
msgstr "Mittelklick fügt ab der nächsten Anmeldung nichts mehr ein."
|
||||||
|
|
||||||
|
|
||||||
#. Help
|
#. Help
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -66,6 +63,9 @@ msgstr ""
|
|||||||
msgid "Steam"
|
msgid "Steam"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Middle-click paste"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "New applications"
|
msgid "New applications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -115,6 +115,9 @@ msgstr ""
|
|||||||
msgid "Spotify"
|
msgid "Spotify"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Turn off KDE's middle-click paste"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Apply to newly installed applications"
|
msgid "Apply to newly installed applications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -155,9 +158,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 +179,13 @@ 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 ""
|
||||||
|
|
||||||
|
msgid "Middle click stops pasting at the next login."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+15
-22
@@ -103,7 +103,9 @@ mca_apply() {
|
|||||||
mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")"
|
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
|
||||||
@@ -118,34 +120,25 @@ 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
|
||||||
|
|
||||||
|
# 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
|
mca_prune_orphans
|
||||||
|
|
||||||
if (( MCA_CHANGES )) && mca_have update-desktop-database; then
|
if (( MCA_CHANGES )) && mca_have update-desktop-database; then
|
||||||
|
|||||||
+44
-11
@@ -35,20 +35,31 @@ MCA_PRETTY="Middle-Click Autoscroll"
|
|||||||
# the one that always works.
|
# the one that always works.
|
||||||
MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll"
|
MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll"
|
||||||
|
|
||||||
# Helium ships the feature under its own name and does not answer to the
|
# Two browsers do not answer to the Chromium name. Helium ships the feature
|
||||||
# Chromium one, so browsers are asked for both. A name a browser does not know
|
# under its own name and sets the runtime flag from that alone. Brave takes
|
||||||
# is ignored, which is what makes one list safe to hand to all of them.
|
# the generated feature away and switches the flag on from one of its own,
|
||||||
MCA_BROWSER_FLAG="--enable-features=MiddleClickAutoscroll,HeliumMiddleClickAutoscroll"
|
# spelled MiddelButtonClickAutoscroll - the typo is in Brave's source, and the
|
||||||
|
# correct spelling does nothing. So browsers are asked for all three. 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,MiddelButtonClickAutoscroll"
|
||||||
|
|
||||||
# The feature names on their own, for merging into a list that an application
|
# 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.
|
# (or the user) already carries, and for taking them back out again.
|
||||||
MCA_FEATURE="MiddleClickAutoscroll"
|
MCA_FEATURE="MiddleClickAutoscroll"
|
||||||
MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll"
|
MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll,MiddelButtonClickAutoscroll"
|
||||||
|
|
||||||
# Bumped when the flags above change. An installation that was set up by a
|
# Bumped when the flags above change, and when what gets written where does.
|
||||||
# version with a different answer is taken back and done again once, because a
|
# An installation that was set up by a version with a different answer is taken
|
||||||
# file that is already patched is otherwise left alone. See mca_apply.
|
# back and done again once, because a file that is already patched is otherwise
|
||||||
MCA_FLAG_SCHEME=2
|
# left alone. See mca_apply.
|
||||||
|
#
|
||||||
|
# 3 is what takes -noverifyfiles back out of the Steam entries an older version
|
||||||
|
# put it in.
|
||||||
|
#
|
||||||
|
# 4 adds Brave's name to the browser list, which an entry edited in place would
|
||||||
|
# otherwise never get.
|
||||||
|
MCA_FLAG_SCHEME=4
|
||||||
|
|
||||||
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
|
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
|
||||||
MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}"
|
MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}"
|
||||||
@@ -274,9 +285,12 @@ mca_state_write() {
|
|||||||
# shadow <generated desktop entry> <source entry it was generated from>
|
# shadow <generated desktop entry> <source entry it was generated from>
|
||||||
# inplace <edited file> <basename of its backup copy>
|
# inplace <edited file> <basename of its backup copy>
|
||||||
# flags <flag file> created | appended | merged
|
# 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
|
# The kinds differ in how they are undone: a backup is restored wholesale, a
|
||||||
# wholesale, a flag file only loses the one line that was added to it.
|
# 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() {
|
mca_ledger_add() {
|
||||||
local kind="$1" path="$2" detail="${3:-}"
|
local kind="$1" path="$2" detail="${3:-}"
|
||||||
@@ -289,6 +303,25 @@ 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_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() {
|
mca_ledger_forget() {
|
||||||
local path="$1" tmp
|
local path="$1" tmp
|
||||||
[[ -f $MCA_LEDGER ]] || return 0
|
[[ -f $MCA_LEDGER ]] || return 0
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ mca_config_load() {
|
|||||||
CFG_STEAM=no; mca_config_bool PatchSteam yes && CFG_STEAM=yes
|
CFG_STEAM=no; mca_config_bool PatchSteam yes && CFG_STEAM=yes
|
||||||
CFG_SPOTIFY=no; mca_config_bool PatchSpotify yes && CFG_SPOTIFY=yes
|
CFG_SPOTIFY=no; mca_config_bool PatchSpotify yes && CFG_SPOTIFY=yes
|
||||||
CFG_WATCH=no; mca_config_bool WatchNewApps yes && CFG_WATCH=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 '')"
|
CFG_EXTRA_FLAGS="$(mca_config_get ExtraFlags '')"
|
||||||
|
|
||||||
|
|||||||
+437
-65
@@ -21,6 +21,22 @@ MCA_MARKERS=(
|
|||||||
libvk_swiftshader.so LICENSES.chromium.html libcef.so
|
libvk_swiftshader.so LICENSES.chromium.html libcef.so
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The markers that still mean something when the whole of a tree is searched
|
||||||
|
# rather than just the directory the binary sits in - inside a Flatpak, a snap
|
||||||
|
# or an AppImage, where there is no "next to the binary" to look at.
|
||||||
|
#
|
||||||
|
# Three of the names above are missing here on purpose. icudtl.dat is ICU's
|
||||||
|
# data file and not Chromium's: a Flutter application ships one in data/, and
|
||||||
|
# a whole-tree search would find it. snapshot_blob.bin and resources.pak are
|
||||||
|
# the same kind of shared name. Next to a binary they are still evidence,
|
||||||
|
# because nothing but Chromium unpacks its payload there - a few directories
|
||||||
|
# deeper they are not.
|
||||||
|
MCA_MARKERS_STRICT=(
|
||||||
|
chrome_crashpad_handler chrome-sandbox chrome_100_percent.pak
|
||||||
|
v8_context_snapshot.bin libvk_swiftshader.so LICENSES.chromium.html
|
||||||
|
libcef.so app.asar
|
||||||
|
)
|
||||||
|
|
||||||
# Shared directories, where a marker belongs to the system rather than to the
|
# Shared directories, where a marker belongs to the system rather than to the
|
||||||
# program that happens to live there. An application ships its payload in a
|
# program that happens to live there. An application ships its payload in a
|
||||||
# directory of its own; nothing unpacks Chromium straight into /usr/lib.
|
# directory of its own; nothing unpacks Chromium straight into /usr/lib.
|
||||||
@@ -223,6 +239,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 +290,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 ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -270,14 +325,27 @@ _mca_stat_batch() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The first line of the cache file, and the reason it is there: the verdicts
|
||||||
|
# below it are keyed on size and mtime, so an entry for a file that has not
|
||||||
|
# changed is never looked at again. A cache written when a verdict meant
|
||||||
|
# something else - before an AppImage could come out as anything but "no" -
|
||||||
|
# would therefore keep answering the old way forever. Bumping this is how such
|
||||||
|
# a cache gets dropped instead.
|
||||||
|
MCA_CACHE_FORMAT='# middleclick-autoscroll detect 2'
|
||||||
|
|
||||||
_mca_cache_load() {
|
_mca_cache_load() {
|
||||||
local path stamp verdict
|
local path stamp verdict first=1
|
||||||
|
|
||||||
(( MCA_CACHE_LOADED )) && return 0
|
(( MCA_CACHE_LOADED )) && return 0
|
||||||
MCA_CACHE_LOADED=1
|
MCA_CACHE_LOADED=1
|
||||||
|
|
||||||
[[ -r "$MCA_CACHEDIR/detect" ]] || return 0
|
[[ -r "$MCA_CACHEDIR/detect" ]] || return 0
|
||||||
while IFS=$'\t' read -r path stamp verdict; do
|
while IFS=$'\t' read -r path stamp verdict; do
|
||||||
|
if (( first )); then
|
||||||
|
first=0
|
||||||
|
[[ $path == "$MCA_CACHE_FORMAT" ]] || return 0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
[[ -n $path && -n $stamp ]] || continue
|
[[ -n $path && -n $stamp ]] || continue
|
||||||
MCA_DETECT_CACHE["$path"]="$stamp"$'\t'"$verdict"
|
MCA_DETECT_CACHE["$path"]="$stamp"$'\t'"$verdict"
|
||||||
done < "$MCA_CACHEDIR/detect"
|
done < "$MCA_CACHEDIR/detect"
|
||||||
@@ -292,6 +360,7 @@ mca_cache_flush() {
|
|||||||
mkdir -p "$MCA_CACHEDIR" 2>/dev/null || return 0
|
mkdir -p "$MCA_CACHEDIR" 2>/dev/null || return 0
|
||||||
|
|
||||||
tmp="$(mktemp "$MCA_CACHEDIR/detect.XXXXXX")" || return 0
|
tmp="$(mktemp "$MCA_CACHEDIR/detect.XXXXXX")" || return 0
|
||||||
|
printf '%s\n' "$MCA_CACHE_FORMAT" > "$tmp"
|
||||||
for path in "${!MCA_DETECT_CACHE[@]}"; do
|
for path in "${!MCA_DETECT_CACHE[@]}"; do
|
||||||
entry="${MCA_DETECT_CACHE[$path]}"
|
entry="${MCA_DETECT_CACHE[$path]}"
|
||||||
printf '%s\t%s\n' "$path" "$entry" >> "$tmp"
|
printf '%s\t%s\n' "$path" "$entry" >> "$tmp"
|
||||||
@@ -323,6 +392,26 @@ _mca_has_markers() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _mca_find_markers <directory> <depth>
|
||||||
|
# The same question for a whole tree, which is the shape a Flatpak, a snap and
|
||||||
|
# an unpacked AppImage come in: everything the application ships is somewhere
|
||||||
|
# under one root and there is no single directory that is "next to the binary".
|
||||||
|
# Hence the narrower list - see MCA_MARKERS_STRICT.
|
||||||
|
_mca_find_markers() {
|
||||||
|
local root="$1" depth="$2" m
|
||||||
|
local -a names=()
|
||||||
|
|
||||||
|
[[ -d $root ]] || return 1
|
||||||
|
|
||||||
|
for m in "${MCA_MARKERS_STRICT[@]}"; do
|
||||||
|
(( ${#names[@]} )) && names+=(-o)
|
||||||
|
names+=(-name "$m")
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$(find "$root" -maxdepth "$depth" \
|
||||||
|
\( "${names[@]}" \) -print -quit 2>/dev/null)" ]]
|
||||||
|
}
|
||||||
|
|
||||||
# The plain assignments the script made before it handed over, for
|
# The plain assignments the script made before it handed over, for
|
||||||
# _mca_script_subst to read. A variable of its own rather than something passed
|
# _mca_script_subst to read. A variable of its own rather than something passed
|
||||||
# around: every caller of _mca_script_target reads it through a command
|
# around: every caller of _mca_script_target reads it through a command
|
||||||
@@ -482,18 +571,295 @@ mca_flags_candidates() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# mca_is_chromium <program>
|
# ---------------------------------------------------------------------------
|
||||||
# Succeeds when the program is a Chromium, CEF or Electron process.
|
# Looking inside an AppImage
|
||||||
mca_is_chromium() {
|
# ---------------------------------------------------------------------------
|
||||||
local prog="$1" verdict real stamp cached
|
#
|
||||||
|
# An AppImage is the AppImage runtime - an ordinary ELF executable - with a
|
||||||
|
# squashfs image appended to it. The payload is compressed, so nothing the
|
||||||
|
# application ships is on disk where the marker check could find it, and for a
|
||||||
|
# long time that made every AppImage a "cannot tell".
|
||||||
|
#
|
||||||
|
# It does not have to be. squashfs keeps the names of everything it holds in
|
||||||
|
# one table of its own, the table is a few kilobytes, and the names are the
|
||||||
|
# whole of what this question needs. So that table is read and inflated here -
|
||||||
|
# rather than unpacking a hundred megabytes, and rather than running the image
|
||||||
|
# to ask it, which is the one thing a scan started by a path unit must never
|
||||||
|
# do.
|
||||||
|
#
|
||||||
|
# Everything below fails closed. The moment a field is not where it should be,
|
||||||
|
# or the compressor is one there is no tool for, the answer goes back to
|
||||||
|
# "cannot tell" and the application is left to the applications screen.
|
||||||
|
|
||||||
[[ -n $prog && -e $prog ]] || return 1
|
# _mca_bytes <file> <offset> <count>
|
||||||
|
# Count bytes from an offset, as numbers, in MCA_BYTES. od rather than a shell
|
||||||
|
# read because a variable cannot hold a NUL byte and these headers are full of
|
||||||
|
# them - and a whole header at a time rather than a field at a time, because
|
||||||
|
# the first question below is asked about every program on the system and one
|
||||||
|
# od per field would be four forks per program.
|
||||||
|
MCA_BYTES=()
|
||||||
|
|
||||||
|
_mca_bytes() {
|
||||||
|
local file="$1" off="$2" count="$3"
|
||||||
|
|
||||||
|
MCA_BYTES=()
|
||||||
|
|
||||||
|
# read stops on end of input rather than on the delimiter it was given, so
|
||||||
|
# it reports failure for input that is perfectly complete. The count is
|
||||||
|
# the check.
|
||||||
|
read -r -d '' -a MCA_BYTES \
|
||||||
|
< <(od -An -tu1 -j"$off" -N"$count" -v -- "$file" 2>/dev/null)
|
||||||
|
|
||||||
|
(( ${#MCA_BYTES[@]} == count ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_slice <file> <offset> <length>
|
||||||
|
# Length bytes from an offset, on stdout.
|
||||||
|
_mca_slice() {
|
||||||
|
dd if="$1" bs=65536 iflag=skip_bytes,count_bytes \
|
||||||
|
skip="$2" count="$3" status=none 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_word <index>
|
||||||
|
# One 64-bit little-endian field out of MCA_BYTES, in MCA_WORD. Assigns rather
|
||||||
|
# than prints for the same reason _mca_bytes reads a whole header at a time:
|
||||||
|
# printing means a command substitution, and that is a fork per field.
|
||||||
|
#
|
||||||
|
# Fails for the all-ones value squashfs writes down for a table that is not in
|
||||||
|
# the image, which is a case the callers below have to tell from a real offset
|
||||||
|
# and bash arithmetic - signed, 64-bit - cannot represent.
|
||||||
|
MCA_WORD=0
|
||||||
|
|
||||||
|
_mca_word() {
|
||||||
|
local at="$1"
|
||||||
|
|
||||||
|
(( ${#MCA_BYTES[@]} >= at + 8 )) || return 1
|
||||||
|
(( MCA_BYTES[at+7] == 255 && MCA_BYTES[at+6] == 255 )) && return 1
|
||||||
|
|
||||||
|
MCA_WORD=$(( (MCA_BYTES[at] | MCA_BYTES[at+1] << 8 \
|
||||||
|
| MCA_BYTES[at+2] << 16 | MCA_BYTES[at+3] << 24) \
|
||||||
|
| ((MCA_BYTES[at+4] | MCA_BYTES[at+5] << 8 \
|
||||||
|
| MCA_BYTES[at+6] << 16 | MCA_BYTES[at+7] << 24) << 32) ))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_appimage_offset <file>
|
||||||
|
# Where the appended image starts, in MCA_APPIMAGE_AT: directly after the ELF,
|
||||||
|
# which is where its section header table ends. This is how the runtime works
|
||||||
|
# it out for --appimage-offset, and it is exact.
|
||||||
|
#
|
||||||
|
# Searching for the squashfs magic instead would not be: the runtime carries a
|
||||||
|
# copy of it in its own code. Neither is the AppImage magic at byte 8 of the
|
||||||
|
# ELF header a way in - it is there to be zeroed, and a build pipeline that
|
||||||
|
# does not want its image picked up by a desktop integration daemon does
|
||||||
|
# exactly that. So the offset is computed for any ELF at all and the caller
|
||||||
|
# finds out whether there is an image at it.
|
||||||
|
MCA_APPIMAGE_AT=0
|
||||||
|
|
||||||
|
_mca_appimage_offset() {
|
||||||
|
local shoff shentsize shnum
|
||||||
|
|
||||||
|
MCA_APPIMAGE_AT=0
|
||||||
|
_mca_bytes "$1" 0 64 || return 1
|
||||||
|
|
||||||
|
# \x7fELF, and then the class byte: 2 for the 64-bit header, 1 for the
|
||||||
|
# 32-bit one, which puts every field after it somewhere else.
|
||||||
|
(( MCA_BYTES[0] == 127 && MCA_BYTES[1] == 69 \
|
||||||
|
&& MCA_BYTES[2] == 76 && MCA_BYTES[3] == 70 )) || return 1
|
||||||
|
|
||||||
|
if (( MCA_BYTES[4] == 2 )); then
|
||||||
|
_mca_word 40 || return 1
|
||||||
|
shoff="$MCA_WORD"
|
||||||
|
shentsize=$(( MCA_BYTES[58] | MCA_BYTES[59] << 8 ))
|
||||||
|
shnum=$(( MCA_BYTES[60] | MCA_BYTES[61] << 8 ))
|
||||||
|
elif (( MCA_BYTES[4] == 1 )); then
|
||||||
|
shoff=$(( MCA_BYTES[32] | MCA_BYTES[33] << 8 \
|
||||||
|
| MCA_BYTES[34] << 16 | MCA_BYTES[35] << 24 ))
|
||||||
|
shentsize=$(( MCA_BYTES[46] | MCA_BYTES[47] << 8 ))
|
||||||
|
shnum=$(( MCA_BYTES[48] | MCA_BYTES[49] << 8 ))
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( shoff > 0 && shentsize > 0 && shnum > 0 )) || return 1
|
||||||
|
MCA_APPIMAGE_AT=$(( shoff + shentsize * shnum ))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_inflate <compressor> <file> <offset> <length>
|
||||||
|
# One squashfs metadata block, decompressed onto stdout.
|
||||||
|
#
|
||||||
|
# lzo and lz4 are missing because squashfs stores them as bare blocks and
|
||||||
|
# neither lzop nor the lz4 tool will read one without the framing their own
|
||||||
|
# file format puts around it. An image compressed with either comes out as
|
||||||
|
# "cannot tell", which is where it started.
|
||||||
|
_mca_inflate() {
|
||||||
|
local comp="$1" file="$2" off="$3" len="$4"
|
||||||
|
|
||||||
|
case "$comp" in
|
||||||
|
1)
|
||||||
|
# squashfs stores a zlib stream and gzip only reads its own
|
||||||
|
# container, but underneath both are the same deflate data with a
|
||||||
|
# different wrapper around it - so the wrapper is swapped: zlib's
|
||||||
|
# two header bytes are dropped and a minimal gzip header put in
|
||||||
|
# front. gzip then writes every byte of the block and complains
|
||||||
|
# about the trailer it did not get, which is why its status is
|
||||||
|
# thrown away here and the check is on the output instead.
|
||||||
|
(( len > 2 )) || return 1
|
||||||
|
{
|
||||||
|
printf '\037\213\010\000\000\000\000\000\000\003'
|
||||||
|
_mca_slice "$file" $(( off + 2 )) $(( len - 2 ))
|
||||||
|
} | { gzip -dc 2>/dev/null || true; }
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
mca_have xz || return 1
|
||||||
|
_mca_slice "$file" "$off" "$len" \
|
||||||
|
| { xz -dc --format=lzma 2>/dev/null || true; }
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
mca_have xz || return 1
|
||||||
|
_mca_slice "$file" "$off" "$len" | { xz -dc 2>/dev/null || true; }
|
||||||
|
;;
|
||||||
|
6)
|
||||||
|
mca_have zstd || return 1
|
||||||
|
_mca_slice "$file" "$off" "$len" | { zstd -dc 2>/dev/null || true; }
|
||||||
|
;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_squashfs_names <file> <offset>
|
||||||
|
# The directory table of the image at that offset, inflated onto stdout. It is
|
||||||
|
# not parsed: the names sit in it as plain text between the records that
|
||||||
|
# describe them, and a name is all the caller is looking for.
|
||||||
|
#
|
||||||
|
# Fails unless the walk lands exactly on the end of the table. That is the
|
||||||
|
# integrity check - the block sizes adding up to the table's own length is
|
||||||
|
# what says the fields were read from a real superblock and that the output is
|
||||||
|
# the whole of the names rather than some of them - and it is why the caller
|
||||||
|
# has to collect this before trusting it, never pipe it.
|
||||||
|
_mca_squashfs_names() {
|
||||||
|
local file="$1" base="$2"
|
||||||
|
local comp dir_start end field header size pos
|
||||||
|
|
||||||
|
_mca_bytes "$file" "$base" 96 || return 1
|
||||||
|
comp=$(( MCA_BYTES[20] | MCA_BYTES[21] << 8 ))
|
||||||
|
|
||||||
|
_mca_word 72 || return 1
|
||||||
|
dir_start="$MCA_WORD"
|
||||||
|
(( dir_start > 0 )) || return 1
|
||||||
|
|
||||||
|
# Where the names stop: the first table squashfs writes after them. Which
|
||||||
|
# one that is depends on the image - there is no fragment table when
|
||||||
|
# nothing was packed into a fragment, and no export table unless it was
|
||||||
|
# asked for - so they are tried in the order they are written and the
|
||||||
|
# first one that is actually there wins. bytes_used closes the list for an
|
||||||
|
# image that has none of them.
|
||||||
|
end=0
|
||||||
|
for field in 80 88 48 40; do
|
||||||
|
_mca_word "$field" || continue
|
||||||
|
(( MCA_WORD > dir_start )) || continue
|
||||||
|
end="$MCA_WORD"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
(( end > dir_start )) || return 1
|
||||||
|
|
||||||
|
# Names for a hundred thousand files would still fit in a fraction of
|
||||||
|
# this. A table that claims more than it is a table that was misread.
|
||||||
|
(( end - dir_start > 8388608 )) && return 1
|
||||||
|
|
||||||
|
pos=$(( base + dir_start ))
|
||||||
|
end=$(( base + end ))
|
||||||
|
|
||||||
|
while (( pos < end )); do
|
||||||
|
_mca_bytes "$file" "$pos" 2 || return 1
|
||||||
|
header=$(( MCA_BYTES[0] | MCA_BYTES[1] << 8 ))
|
||||||
|
size=$(( header & 0x7fff ))
|
||||||
|
(( size > 0 && pos + 2 + size <= end )) || return 1
|
||||||
|
|
||||||
|
# The top bit says the block was stored as it is, which squashfs does
|
||||||
|
# for the ones compression made no smaller.
|
||||||
|
if (( header & 0x8000 )); then
|
||||||
|
_mca_slice "$file" $(( pos + 2 )) "$size" || return 1
|
||||||
|
else
|
||||||
|
_mca_inflate "$comp" "$file" $(( pos + 2 )) "$size" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pos=$(( pos + 2 + size ))
|
||||||
|
done
|
||||||
|
|
||||||
|
(( pos == end ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# _mca_appimage_verdict <file>
|
||||||
|
# What the image appended to this file contains, left in MCA_APPIMAGE:
|
||||||
|
# yes - Chromium, CEF or Electron
|
||||||
|
# no - the names of everything inside were read and none of them is
|
||||||
|
# unknown - there is an image, but its contents could not be read
|
||||||
|
# none - not an AppImage; there is nothing appended to this ELF
|
||||||
|
MCA_APPIMAGE=none
|
||||||
|
|
||||||
|
_mca_appimage_verdict() {
|
||||||
|
local file="$1" base names m
|
||||||
|
local -a args=()
|
||||||
|
|
||||||
|
MCA_APPIMAGE=none
|
||||||
|
|
||||||
|
_mca_appimage_offset "$file" || return 0
|
||||||
|
base="$MCA_APPIMAGE_AT"
|
||||||
|
|
||||||
|
# hsqs, the squashfs magic. Nothing there means nothing was appended, so
|
||||||
|
# this is an ordinary executable and not an AppImage at all.
|
||||||
|
_mca_bytes "$file" "$base" 4 || return 0
|
||||||
|
(( MCA_BYTES[0] == 104 && MCA_BYTES[1] == 115 \
|
||||||
|
&& MCA_BYTES[2] == 113 && MCA_BYTES[3] == 115 )) || return 0
|
||||||
|
|
||||||
|
# From here on there is an image, so the worst this can end on is "cannot
|
||||||
|
# tell" - never "no", which would be an answer about contents that were
|
||||||
|
# never read.
|
||||||
|
MCA_APPIMAGE=unknown
|
||||||
|
|
||||||
|
names="$(mktemp "${TMPDIR:-/tmp}/mca-names.XXXXXX")" || return 0
|
||||||
|
|
||||||
|
# Nothing at all in the table means every block failed to inflate - a
|
||||||
|
# compressor whose tool is not installed - which is a "cannot tell" too.
|
||||||
|
if _mca_squashfs_names "$file" "$base" > "$names" 2>/dev/null \
|
||||||
|
&& [[ -s $names ]]
|
||||||
|
then
|
||||||
|
for m in "${MCA_MARKERS_STRICT[@]}"; do args+=(-e "$m"); done
|
||||||
|
if grep -qaF "${args[@]}" -- "$names"; then
|
||||||
|
MCA_APPIMAGE=yes
|
||||||
|
else
|
||||||
|
MCA_APPIMAGE=no
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f -- "$names"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# mca_detect_verdict <program>
|
||||||
|
# What running this program starts, left in MCA_VERDICT:
|
||||||
|
# yes - Chromium, CEF or Electron
|
||||||
|
# no - something else
|
||||||
|
# unknown - an image whose payload could not be read, so neither answer has
|
||||||
|
# been earned and the applications screen offers it as a choice
|
||||||
|
#
|
||||||
|
# Assigns rather than returns three states through an exit code, and is the one
|
||||||
|
# place the memo and the on-disk cache are consulted.
|
||||||
|
MCA_VERDICT=''
|
||||||
|
|
||||||
|
mca_detect_verdict() {
|
||||||
|
local prog="$1" real stamp cached
|
||||||
|
|
||||||
|
MCA_VERDICT=no
|
||||||
|
[[ -n $prog && -e $prog ]] || return 0
|
||||||
|
|
||||||
# Memoized under the path as given, so the same launcher named twice in a
|
# Memoized under the path as given, so the same launcher named twice in a
|
||||||
# scan costs nothing at all the second time.
|
# scan costs nothing at all the second time.
|
||||||
if [[ -n ${MCA_DETECT_MEMO[$prog]+set} ]]; then
|
if [[ -n ${MCA_DETECT_MEMO[$prog]+set} ]]; then
|
||||||
[[ ${MCA_DETECT_MEMO[$prog]} == yes ]]
|
MCA_VERDICT="${MCA_DETECT_MEMO[$prog]}"
|
||||||
return $?
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_mca_cache_load
|
_mca_cache_load
|
||||||
@@ -507,26 +873,45 @@ mca_is_chromium() {
|
|||||||
if [[ -n $stamp && -n ${MCA_DETECT_CACHE[$prog]+set} ]]; then
|
if [[ -n $stamp && -n ${MCA_DETECT_CACHE[$prog]+set} ]]; then
|
||||||
cached="${MCA_DETECT_CACHE[$prog]}"
|
cached="${MCA_DETECT_CACHE[$prog]}"
|
||||||
if [[ "${cached%%$'\t'*}" == "$stamp" ]]; then
|
if [[ "${cached%%$'\t'*}" == "$stamp" ]]; then
|
||||||
verdict="${cached#*$'\t'}"
|
MCA_VERDICT="${cached#*$'\t'}"
|
||||||
MCA_DETECT_MEMO[$prog]="$verdict"
|
MCA_DETECT_MEMO[$prog]="$MCA_VERDICT"
|
||||||
[[ $verdict == yes ]]
|
return 0
|
||||||
return $?
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
real="$(readlink -f -- "$prog" 2>/dev/null)" || real="$prog"
|
real="$(readlink -f -- "$prog" 2>/dev/null)" || real="$prog"
|
||||||
|
|
||||||
verdict=no
|
MCA_DETECT_UNSURE=0
|
||||||
if _mca_detect_uncached "$real"; then verdict=yes; fi
|
if _mca_detect_uncached "$real"; then
|
||||||
|
MCA_VERDICT=yes
|
||||||
|
elif (( MCA_DETECT_UNSURE )); then
|
||||||
|
MCA_VERDICT=unknown
|
||||||
|
else
|
||||||
|
MCA_VERDICT=no
|
||||||
|
fi
|
||||||
|
|
||||||
MCA_DETECT_MEMO[$prog]="$verdict"
|
MCA_DETECT_MEMO[$prog]="$MCA_VERDICT"
|
||||||
if [[ -n $stamp ]]; then
|
if [[ -n $stamp ]]; then
|
||||||
MCA_DETECT_CACHE["$prog"]="$stamp"$'\t'"$verdict"
|
MCA_DETECT_CACHE["$prog"]="$stamp"$'\t'"$MCA_VERDICT"
|
||||||
MCA_CACHE_DIRTY=1
|
MCA_CACHE_DIRTY=1
|
||||||
fi
|
fi
|
||||||
[[ $verdict == yes ]]
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# mca_is_chromium <program>
|
||||||
|
# Succeeds when the program is a Chromium, CEF or Electron process. "Cannot
|
||||||
|
# tell" is not that, so it fails here - anything that has to treat the two
|
||||||
|
# differently asks mca_detect_verdict instead.
|
||||||
|
mca_is_chromium() {
|
||||||
|
mca_detect_verdict "$1"
|
||||||
|
[[ $MCA_VERDICT == yes ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set by _mca_detect_uncached when it reaches the end without finding anything
|
||||||
|
# and the reason is that something could not be read, rather than that there
|
||||||
|
# was nothing there. Only mca_detect_verdict reads it, straight after the call.
|
||||||
|
MCA_DETECT_UNSURE=0
|
||||||
|
|
||||||
_mca_detect_uncached() {
|
_mca_detect_uncached() {
|
||||||
local real="$1" depth="${2:-0}" dir target
|
local real="$1" depth="${2:-0}" dir target
|
||||||
|
|
||||||
@@ -551,15 +936,28 @@ _mca_detect_uncached() {
|
|||||||
_mca_has_markers "$dir" && return 0
|
_mca_has_markers "$dir" && return 0
|
||||||
[[ ${dir##*/} == bin ]] && _mca_has_markers "${dir%/*}" && return 0
|
[[ ${dir##*/} == bin ]] && _mca_has_markers "${dir%/*}" && return 0
|
||||||
|
|
||||||
|
# An AppImage keeps all of that inside a filesystem appended to itself, so
|
||||||
|
# there is nothing next to the binary to find - but the names of everything
|
||||||
|
# in there can be read, and that settles it either way.
|
||||||
|
_mca_appimage_verdict "$real"
|
||||||
|
case "$MCA_APPIMAGE" in
|
||||||
|
yes) return 0 ;;
|
||||||
|
no) return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Last resort: Chromium's own argument table is in the binary. -m1 stops at
|
# Last resort: Chromium's own argument table is in the binary. -m1 stops at
|
||||||
# the first hit, so this reads far less than the file size suggests.
|
# the first hit, so this reads far less than the file size suggests.
|
||||||
#
|
|
||||||
# AppImages are the one thing this cannot see through - their payload is a
|
|
||||||
# compressed filesystem - which is why they come out as unknown and are
|
|
||||||
# left to the applications screen.
|
|
||||||
grep -qaFm1 -- 'enable-blink-features' "$real" 2>/dev/null && return 0
|
grep -qaFm1 -- 'enable-blink-features' "$real" 2>/dev/null && return 0
|
||||||
grep -qaFm1 -- 'CHROME_VERSION_EXTRA' "$real" 2>/dev/null && return 0
|
grep -qaFm1 -- 'CHROME_VERSION_EXTRA' "$real" 2>/dev/null && return 0
|
||||||
|
|
||||||
|
# What is left is an image that could not be read - an unsupported
|
||||||
|
# compressor - or the older AppImage layout, which is an ISO9660 filesystem
|
||||||
|
# and has no name table of this shape at all. Either way the answer is not
|
||||||
|
# "no", it is "nobody looked".
|
||||||
|
if [[ $MCA_APPIMAGE == unknown || $real == *.AppImage || $real == *.appimage ]]; then
|
||||||
|
MCA_DETECT_UNSURE=1
|
||||||
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,16 +974,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 +991,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 +1030,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")
|
||||||
@@ -695,10 +1076,12 @@ mca_scan() {
|
|||||||
# Steam is Chromium inside, but nothing about it can be changed
|
# Steam is Chromium inside, but nothing about it can be changed
|
||||||
# from a command line argument; it has its own module.
|
# from a command line argument; it has its own module.
|
||||||
kind=steam
|
kind=steam
|
||||||
elif mca_is_chromium "$prog"; then
|
else
|
||||||
(( c_browser[i] )) && kind=browser || kind=app
|
mca_detect_verdict "$prog"
|
||||||
elif [[ $prog == *.AppImage || $prog == *.appimage ]]; then
|
case "$MCA_VERDICT" in
|
||||||
kind=unknown
|
yes) (( c_browser[i] )) && kind=browser || kind=app ;;
|
||||||
|
unknown) kind=unknown ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ $kind == no ]] && continue
|
[[ $kind == no ]] && continue
|
||||||
@@ -791,13 +1174,7 @@ mca_snap_is_chromium() {
|
|||||||
|
|
||||||
for d in "${MCA_SNAP_DIRS[@]}"; do
|
for d in "${MCA_SNAP_DIRS[@]}"; do
|
||||||
root="$d/$name/current"
|
root="$d/$name/current"
|
||||||
[[ -d $root ]] || continue
|
if _mca_find_markers "$root" 5; then
|
||||||
if [[ -n "$(find "$root" -maxdepth 5 \
|
|
||||||
\( -name 'chrome_crashpad_handler' -o -name 'app.asar' \
|
|
||||||
-o -name 'libcef.so' -o -name 'v8_context_snapshot.bin' \
|
|
||||||
-o -name 'chrome-sandbox' \) \
|
|
||||||
-print -quit 2>/dev/null)" ]]
|
|
||||||
then
|
|
||||||
MCA_DETECT_MEMO[snap:$name]=yes
|
MCA_DETECT_MEMO[snap:$name]=yes
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -820,12 +1197,7 @@ mca_flatpak_is_chromium() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
loc="$(flatpak info --show-location "$id" 2>/dev/null)"
|
loc="$(flatpak info --show-location "$id" 2>/dev/null)"
|
||||||
if [[ -n $loc && -d "$loc/files" ]] \
|
if [[ -n $loc ]] && _mca_find_markers "$loc/files" 4; then
|
||||||
&& [[ -n "$(find "$loc/files" -maxdepth 4 \
|
|
||||||
\( -name 'chrome_crashpad_handler' -o -name 'app.asar' \
|
|
||||||
-o -name 'libcef.so' -o -name 'v8_context_snapshot.bin' \) \
|
|
||||||
-print -quit 2>/dev/null)" ]]
|
|
||||||
then
|
|
||||||
MCA_DETECT_MEMO[flatpak:$id]=yes
|
MCA_DETECT_MEMO[flatpak:$id]=yes
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
+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_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}"
|
||||||
@@ -197,6 +194,18 @@ mca_ui_status() {
|
|||||||
fi
|
fi
|
||||||
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")"
|
_mca_row "$(mca_msg "Last applied")" "$(mca_time_ago "$last")"
|
||||||
|
|
||||||
if [[ $CFG_ENABLED == yes ]]; then
|
if [[ $CFG_ENABLED == yes ]]; then
|
||||||
@@ -219,6 +228,7 @@ MCA_SETTINGS=(
|
|||||||
"PatchAutostart|bool|yes|Programs that start themselves at login"
|
"PatchAutostart|bool|yes|Programs that start themselves at login"
|
||||||
"PatchSteam|bool|yes|Steam"
|
"PatchSteam|bool|yes|Steam"
|
||||||
"PatchSpotify|bool|yes|Spotify"
|
"PatchSpotify|bool|yes|Spotify"
|
||||||
|
"DisablePaste|bool|yes|Turn off KDE's middle-click paste"
|
||||||
"WatchNewApps|bool|yes|Apply to newly installed applications"
|
"WatchNewApps|bool|yes|Apply to newly installed applications"
|
||||||
"ExtraFlags|text||Additional Chromium arguments"
|
"ExtraFlags|text||Additional Chromium arguments"
|
||||||
)
|
)
|
||||||
|
|||||||
+42
-37
@@ -410,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.
|
||||||
|
|
||||||
@@ -426,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
|
||||||
@@ -456,30 +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
|
|
||||||
|
|
||||||
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")")"
|
|
||||||
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
|
||||||
|
|
||||||
@@ -645,6 +646,10 @@ mca_revert_all() {
|
|||||||
mca_steam_revert "$path" "$detail" \
|
mca_steam_revert "$path" "$detail" \
|
||||||
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||||
;;
|
;;
|
||||||
|
kwin)
|
||||||
|
mca_kde_paste_revert "$path" "$detail" \
|
||||||
|
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done < <(tac "$MCA_LEDGER" 2>/dev/null)
|
done < <(tac "$MCA_LEDGER" 2>/dev/null)
|
||||||
|
|
||||||
|
|||||||
+55
-129
@@ -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}")"
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ set -uo pipefail
|
|||||||
|
|
||||||
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
|
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
|
# shellcheck source=/dev/null
|
||||||
if ! source "$MCA_LIBDIR/$_mod.sh"; then
|
if ! source "$MCA_LIBDIR/$_mod.sh"; then
|
||||||
printf 'middleclick-autoscroll: cannot load %s/%s.sh\n' "$MCA_LIBDIR" "$_mod" >&2
|
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")"
|
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.")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user