feat: support firefox and other gecko apps

This commit is contained in:
Felitendo committed 2026-10-01 09:33:22 +02:00
1 parent 9b6c64bd97
commit 95d602e1c4
23 files changed
+472 -59

No files matched your search

+4 -1
View File
@@ -7,7 +7,7 @@
<h3 align="center">Middle-click autoscroll in every app that supports it.</h3> <h3 align="center">Middle-click autoscroll in every app that supports it.</h3>
<p align="center"> <p align="center">
Browsers, Electron apps like Discord and Spotify, Steam and anything else built on Chromium. Firefox, Chromium browsers, Electron apps like Discord and Spotify, Steam and anything else built on Chromium.
</p> </p>
<h5 align="center"> <h5 align="center">
@@ -126,6 +126,9 @@ Adding it to every app by hand is a pain, and updates can undo it. This tool doe
**Browsers** get a spelling of the flag that shows no warning bar. Brave and Helium also get their **Browsers** get a spelling of the flag that shows no warning bar. Brave and Helium also get their
own name for the feature. A browser ignores names it does not know. own name for the feature. A browser ignores names it does not know.
**Firefox** has autoscroll too, but as a setting, not a flag. It goes into the `user.js` of each
profile. The same works for LibreWolf, Zen, Floorp, Waterfox and Thunderbird.
**Steam** builds its own command line and repairs any file that looks changed. So the flag goes into **Steam** builds its own command line and repairs any file that looks changed. So the flag goes into
the script that starts its web helper, and the file keeps the size and date Steam expects. Steam the script that starts its web helper, and the file keeps the size and date Steam expects. Steam
finds nothing to repair, and autoscroll works in the store and the library. After a Steam update, finds nothing to repair, and autoscroll works in the store and the library. After a Steam update,
+33 -4
View File
@@ -34,10 +34,13 @@ 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.
Firefox and everything else built on Gecko has autoscroll as well, behind a
setting rather than a flag. See *FIREFOX*.
Run without a command it shows an interactive menu. Everything it can be told Run without a command it shows an interactive menu. Everything it can be told
is reachable from there; the configuration file behind it does not need to be is reachable from there; the configuration file behind it does not need to be
edited by hand. Its applications list turns single applications on or off, edited by hand. Its applications list turns single applications on or off,
Steam and Spotify included. Its settings hold the rest: the desktop's Steam, Spotify and Firefox included. Its settings hold the rest: the desktop's
middle-click paste, the watcher, and more Chromium arguments for every middle-click paste, the watcher, and more Chromium arguments for every
application. application.
@@ -69,8 +72,7 @@ is refused.
last run was. last run was.
*list* *list*
Every Chromium-based application that was found, and how each one is Every application that was found, and how each one is handled.
handled.
*-h*, *--help* *-h*, *--help*
Show a summary of the commands. Show a summary of the commands.
@@ -191,6 +193,29 @@ where the flag goes, and the launcher shows up in the applications list like
any other application. Installed as a plain package, a Flatpak or a snap, it is any other application. 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.
# FIREFOX
Firefox has autoscroll of its own, switched off on Linux. There is no flag for
it, only a setting: *general.autoScroll*. It goes into _user.js_ in every
profile, as a marked block, and Firefox reads that file at every start. The
same goes for everything built on Gecko: LibreWolf, Zen, Floorp, Waterfox and
Thunderbird.
Where the profiles are is read from the application's own _application.ini_,
the way Firefox works it out: _~/.mozilla/firefox_, or
_~/.config/mozilla/firefox_ for a new install of Firefox 147 or later. A Flatpak
or a snap has the same inside its own folder. Every profile in _profiles.ini_
is covered. The watcher looks at that file too, so a new profile is covered
from its second start on.
A profile where the setting is already on, in Firefox's own settings or in
_user.js_, is left alone and not recorded.
Firefox copies the setting into _prefs.js_, so *disable* takes it out of both
files. A Firefox that is still open would write it back when it quits. So for
that profile _user.js_ says *false* instead, and the next *disable* or
*enable* clears it up.
# MIDDLE-CLICK PASTE # MIDDLE-CLICK PASTE
Inside a Chromium application the argument settles this on its own: with Inside a Chromium application the argument settles this on its own: with
@@ -269,8 +294,12 @@ _~/.config/kwinrc_
written when the desktop's middle-click paste is turned off, and put written when the desktop's middle-click paste is turned off, and put
back by *disable*. back by *disable*.
_user.js_ in every Firefox profile
Gets a block between *// >>> middleclick-autoscroll* and
*// <<< middleclick-autoscroll*. Nothing else in the file is touched.
_~/.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 or Gecko, keyed by size and modification
time. Its first line says which version of the program wrote it, and a time. Its first line says which version of the program wrote it, and a
file from an older one is ignored rather than trusted. Otherwise an entry file from an older one is ignored rather than trusted. Otherwise an entry
for a file that has not changed would never be looked at again. for a file that has not changed would never be looked at again.
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "soubor s příznaky"
msgid "launcher" msgid "launcher"
msgstr "spouštěč" msgstr "spouštěč"
msgid "profile"
msgstr "profil"
msgid "cannot tell" msgid "cannot tell"
msgstr "nelze určit" msgstr "nelze určit"
@@ -131,8 +134,8 @@ msgstr "Nastavení se nepodařilo uložit."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Procházím nainstalované aplikace..." msgstr "Procházím nainstalované aplikace..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Nenalezeny žádné aplikace založené na Chromiu." msgstr "Nenalezeny žádné podporované aplikace."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Co je označeno „nelze určit“, zůstane vypnuté, dokud to tu nezapnete." msgstr "Co je označeno „nelze určit“, zůstane vypnuté, dokud to tu nezapnete."
+5 -2
View File
@@ -38,6 +38,9 @@ msgstr "Flag-Datei"
msgid "launcher" msgid "launcher"
msgstr "Startereintrag" msgstr "Startereintrag"
msgid "profile"
msgstr "Profil"
msgid "cannot tell" msgid "cannot tell"
msgstr "nicht erkennbar" msgstr "nicht erkennbar"
@@ -131,8 +134,8 @@ msgstr "Einstellung konnte nicht gespeichert werden."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Installierte Anwendungen werden durchgesehen …" msgstr "Installierte Anwendungen werden durchgesehen …"
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Keine Chromium-basierten Anwendungen gefunden." msgstr "Keine unterstützten Anwendungen gefunden."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Was „nicht erkennbar“ ist, bleibt aus, bis es hier eingeschaltet wird." msgstr "Was „nicht erkennbar“ ist, bleibt aus, bis es hier eingeschaltet wird."
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "archivo de flags"
msgid "launcher" msgid "launcher"
msgstr "lanzador" msgstr "lanzador"
msgid "profile"
msgstr "perfil"
msgid "cannot tell" msgid "cannot tell"
msgstr "no se sabe" msgstr "no se sabe"
@@ -132,8 +135,8 @@ msgstr "No se pudo guardar el ajuste."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Revisando las aplicaciones instaladas..." msgstr "Revisando las aplicaciones instaladas..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "No se encontraron aplicaciones basadas en Chromium." msgstr "No se encontraron aplicaciones compatibles."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Lo marcado como «no se sabe» sigue desactivado hasta activarlo aquí." msgstr "Lo marcado como «no se sabe» sigue desactivado hasta activarlo aquí."
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "fichier de flags"
msgid "launcher" msgid "launcher"
msgstr "lanceur" msgstr "lanceur"
msgid "profile"
msgstr "profil"
msgid "cannot tell" msgid "cannot tell"
msgstr "indéterminé" msgstr "indéterminé"
@@ -132,8 +135,8 @@ msgstr "Impossible d'enregistrer le paramètre."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Examen des applications installées..." msgstr "Examen des applications installées..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Aucune application basée sur Chromium trouvée." msgstr "Aucune application prise en charge trouvée."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "file di flag"
msgid "launcher" msgid "launcher"
msgstr "lanciatore" msgstr "lanciatore"
msgid "profile"
msgstr "profilo"
msgid "cannot tell" msgid "cannot tell"
msgstr "incerto" msgstr "incerto"
@@ -132,8 +135,8 @@ msgstr "Impossibile salvare l'impostazione."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Esame delle applicazioni installate..." msgstr "Esame delle applicazioni installate..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Nessuna applicazione basata su Chromium trovata." msgstr "Nessuna applicazione supportata trovata."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+4 -1
View File
@@ -38,6 +38,9 @@ msgstr ""
msgid "launcher" msgid "launcher"
msgstr "" msgstr ""
msgid "profile"
msgstr ""
msgid "cannot tell" msgid "cannot tell"
msgstr "" msgstr ""
@@ -130,7 +133,7 @@ msgstr ""
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "" msgstr ""
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "" msgstr ""
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "flagbestand"
msgid "launcher" msgid "launcher"
msgstr "starter" msgstr "starter"
msgid "profile"
msgstr "profiel"
msgid "cannot tell" msgid "cannot tell"
msgstr "onbekend" msgstr "onbekend"
@@ -132,8 +135,8 @@ msgstr "De instelling kon niet worden opgeslagen."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Geïnstalleerde toepassingen worden bekeken..." msgstr "Geïnstalleerde toepassingen worden bekeken..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Geen op Chromium gebaseerde toepassingen gevonden." msgstr "Geen ondersteunde toepassingen gevonden."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "plik flag"
msgid "launcher" msgid "launcher"
msgstr "skrót" msgstr "skrót"
msgid "profile"
msgstr "profil"
msgid "cannot tell" msgid "cannot tell"
msgstr "nie wiadomo" msgstr "nie wiadomo"
@@ -131,8 +134,8 @@ msgstr "Nie udało się zapisać ustawienia."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Przeglądanie zainstalowanych aplikacji..." msgstr "Przeglądanie zainstalowanych aplikacji..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Nie znaleziono aplikacji opartych na Chromium." msgstr "Nie znaleziono obsługiwanych aplikacji."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "arquivo de flags"
msgid "launcher" msgid "launcher"
msgstr "lançador" msgstr "lançador"
msgid "profile"
msgstr "perfil"
msgid "cannot tell" msgid "cannot tell"
msgstr "indeterminado" msgstr "indeterminado"
@@ -132,8 +135,8 @@ msgstr "Não foi possível salvar a configuração."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Verificando os aplicativos instalados..." msgstr "Verificando os aplicativos instalados..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Nenhum aplicativo baseado em Chromium encontrado." msgstr "Nenhum aplicativo compatível encontrado."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "файл флагов"
msgid "launcher" msgid "launcher"
msgstr "ярлык" msgstr "ярлык"
msgid "profile"
msgstr "профиль"
msgid "cannot tell" msgid "cannot tell"
msgstr "неизвестно" msgstr "неизвестно"
@@ -132,8 +135,8 @@ msgstr "Не удалось сохранить настройку."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Просмотр установленных приложений..." msgstr "Просмотр установленных приложений..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Приложения на основе Chromium не найдены." msgstr "Поддерживаемые приложения не найдены."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "bayrak dosyası"
msgid "launcher" msgid "launcher"
msgstr "başlatıcı" msgstr "başlatıcı"
msgid "profile"
msgstr "profil"
msgid "cannot tell" msgid "cannot tell"
msgstr "belirsiz" msgstr "belirsiz"
@@ -132,8 +135,8 @@ msgstr "Ayar kaydedilemedi."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Kurulu uygulamalara bakılıyor..." msgstr "Kurulu uygulamalara bakılıyor..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Chromium tabanlı uygulama bulunamadı." msgstr "Desteklenen uygulama bulunamadı."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "\"belirsiz\" olarak işaretlenenler burada açılana kadar kapalı kalır." msgstr "\"belirsiz\" olarak işaretlenenler burada açılana kadar kapalı kalır."
+5 -2
View File
@@ -39,6 +39,9 @@ msgstr "файл прапорців"
msgid "launcher" msgid "launcher"
msgstr "ярлик" msgstr "ярлик"
msgid "profile"
msgstr "профіль"
msgid "cannot tell" msgid "cannot tell"
msgstr "невідомо" msgstr "невідомо"
@@ -131,8 +134,8 @@ msgstr "Не вдалося зберегти налаштування."
msgid "Looking at the installed applications..." msgid "Looking at the installed applications..."
msgstr "Перегляд встановлених застосунків..." msgstr "Перегляд встановлених застосунків..."
msgid "No Chromium-based applications found." msgid "No supported applications found."
msgstr "Застосунків на основі Chromium не знайдено." msgstr "Підтримуваних застосунків не знайдено."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here." msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "" msgstr ""
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Watch for newly installed Chromium-based applications Description=Watch for newly installed applications
Documentation=man:middleclick-autoscroll(1) Documentation=man:middleclick-autoscroll(1)
[Path] [Path]
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Apply middle-click autoscroll to Chromium-based applications Description=Apply middle-click autoscroll
Documentation=man:middleclick-autoscroll(1) Documentation=man:middleclick-autoscroll(1)
[Service] [Service]
+45 -13
View File
@@ -16,6 +16,7 @@ MCA_ROUTES=()
# desktop - no flag file; shadow or edit the desktop entry # desktop - no flag file; shadow or edit the desktop entry
# steam - Steam's own two-part treatment # steam - Steam's own two-part treatment
# spotify - the flag goes into spotify-launcher's configuration file # spotify - the flag goes into spotify-launcher's configuration file
# gecko - a setting in each of the application's profiles
# unknown - cannot tell what engine this is (an AppImage), so nothing is done # unknown - cannot tell what engine this is (an AppImage), so nothing is done
# off - detected, but switched off # off - detected, but switched off
# #
@@ -27,7 +28,7 @@ MCA_ROUTE=''
mca_route() { mca_route() {
local kind="$1" id="$2" prog="$3" packaging="${4:-native}" local kind="$1" id="$2" prog="$3" packaging="${4:-native}"
if [[ $kind == steam || $kind == spotify ]]; then if [[ $kind == steam || $kind == spotify || $kind == gecko ]]; then
if mca_config_list_has Skip "$id"; then if mca_config_list_has Skip "$id"; then
MCA_ROUTE=off MCA_ROUTE=off
else else
@@ -65,6 +66,7 @@ mca_apply() {
MCA_CHANGES=0 MCA_CHANGES=0
MCA_ROUTES=() MCA_ROUTES=()
MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0 MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0
MCA_GECKO_SEEN=(); MCA_GECKO_WATCH=()
mca_config_load mca_config_load
@@ -116,6 +118,9 @@ mca_apply() {
(( spotify_done )) || mca_spotify_apply (( spotify_done )) || mca_spotify_apply
spotify_done=1 spotify_done=1
;; ;;
gecko)
mca_gecko_apply "${MCA_PROFILES[i]}" "$packaging" "$prog"
;;
esac esac
done done
@@ -178,7 +183,8 @@ MCA_UNIT_SERVICE="middleclick-autoscroll.service"
# The desktop folder is watched like every other directory an entry can turn up # The desktop folder is watched like every other directory an entry can turn up
# in, but its name is translated and the unit that ships with the package # in, but its name is translated and the unit that ships with the package
# cannot know it. So that one path is written here, into a drop-in, from the # cannot know it. So that one path is written here, into a drop-in, from the
# name this system actually uses. # name this system actually uses. The same goes for where Firefox and its forks
# keep their profiles, which depends on what is installed.
MCA_UNIT_DROPIN="${MCA_XDG_CONFIG}/systemd/user/${MCA_UNIT_PATH}.d" MCA_UNIT_DROPIN="${MCA_XDG_CONFIG}/systemd/user/${MCA_UNIT_PATH}.d"
mca_watch_available() { mca_watch_available() {
@@ -196,23 +202,47 @@ mca_watch_enabled() {
# same reason: the watcher runs after every desktop entry that appears anywhere on # same reason: the watcher runs after every desktop entry that appears anywhere on
# the system, and a daemon-reload each time would be absurd. # the system, and a daemon-reload each time would be absurd.
_mca_watch_dropin_write() { _mca_watch_dropin_write() {
local dir file="$MCA_UNIT_DROPIN/desktop.conf" content local dir path content='' changed=1
dir="$(mca_desktop_folder)"
[[ -d $dir ]] || { _mca_watch_dropin_remove; return $?; }
# A per cent sign starts a specifier in a unit file and has to be doubled # A per cent sign starts a specifier in a unit file and has to be doubled
# to mean itself. Rare in a folder name, fatal when it happens. # to mean itself. Rare in a folder name, fatal when it happens.
content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n' dir="$(mca_desktop_folder)"
[[ -d $dir ]] && content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n'
_mca_watch_conf desktop.conf "$content" && changed=0
mca_write_if_changed "$file" "$content" # Filled by the apply that ran just before. A new profile is added there.
content=''
for path in "${MCA_GECKO_WATCH[@]}"; do
content+="PathChanged=${path//%/%%}"$'\n'
done
[[ -n $content ]] && content="[Path]"$'\n'"$content"
_mca_watch_conf profiles.conf "$content" && changed=0
return "$changed"
}
# _mca_watch_conf <name> <content>
# Writes one drop-in, or removes it when the content is empty. Returns 0 when
# it changed.
_mca_watch_conf() {
local file="$MCA_UNIT_DROPIN/$1"
if [[ -n $2 ]]; then
mca_write_if_changed "$file" "$2"
return
fi
[[ -e $file ]] || return 1
rm -f -- "$file"
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true
return 0
} }
_mca_watch_dropin_remove() { _mca_watch_dropin_remove() {
[[ -e "$MCA_UNIT_DROPIN/desktop.conf" ]] || return 1 local changed=1
rm -f -- "$MCA_UNIT_DROPIN/desktop.conf" _mca_watch_conf desktop.conf '' && changed=0
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true _mca_watch_conf profiles.conf '' && changed=0
return 0 return "$changed"
} }
mca_watch_enable() { mca_watch_enable() {
@@ -226,7 +256,8 @@ mca_watch_enable() {
# mca_watch_refresh # mca_watch_refresh
# Keeps a running watcher in step with a desktop folder that has been renamed, # Keeps a running watcher in step with a desktop folder that has been renamed,
# which is what a change of session language does to it. # which is what a change of session language does to it, and with the Firefox
# profiles there are now.
mca_watch_refresh() { mca_watch_refresh() {
mca_watch_available || return 0 mca_watch_available || return 0
_mca_watch_dropin_write || return 0 _mca_watch_dropin_write || return 0
@@ -295,6 +326,7 @@ mca_route_label() {
desktop) mca_msg "launcher" ;; desktop) mca_msg "launcher" ;;
steam) mca_msg "Steam" ;; steam) mca_msg "Steam" ;;
spotify) printf 'spotify-launcher' ;; spotify) printf 'spotify-launcher' ;;
gecko) mca_msg "profile" ;;
unknown) mca_msg "cannot tell" ;; unknown) mca_msg "cannot tell" ;;
*) mca_msg "off" ;; *) mca_msg "off" ;;
esac esac
+3 -1
View File
@@ -283,11 +283,13 @@ mca_state_write() {
# 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 # kwin <kwinrc> what EnablePrimarySelection said before
# gecko <user.js in a profile> created | block
# #
# The kinds differ in how they are undone: a backup is restored wholesale, a # The kinds differ in how they are undone: a backup is restored wholesale, a
# flag file only loses the one line that was added to it, and kwinrc gets one # 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 # 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. # copy of it back would take everything else settled since with it. A user.js
# loses its block, and prefs.js the line Firefox copied out of it.
mca_ledger_add() { mca_ledger_add() {
local kind="$1" path="$2" detail="${3:-}" local kind="$1" path="$2" detail="${3:-}"
+53 -8
View File
@@ -343,7 +343,9 @@ _mca_stat_batch() {
# taught the hint scan about Discord's launcher, and Discord stayed out of the # taught the hint scan about Discord's launcher, and Discord stayed out of the
# list anyway, because /usr/bin/discord had not changed and its old "no" was # list anyway, because /usr/bin/discord had not changed and its old "no" was
# still in here. # still in here.
MCA_CACHE_FORMAT="# middleclick-autoscroll detect 2 $MCA_VERSION" #
# 3 adds the gecko verdict.
MCA_CACHE_FORMAT="# middleclick-autoscroll detect 3 $MCA_VERSION"
_mca_cache_load() { _mca_cache_load() {
local path stamp verdict first=1 local path stamp verdict first=1
@@ -862,6 +864,8 @@ _mca_appimage_verdict() {
# no - something else # no - something else
# unknown - an image whose payload could not be read, so neither answer has # unknown - an image whose payload could not be read, so neither answer has
# been earned and the applications screen offers it as a choice # been earned and the applications screen offers it as a choice
# gecko:<rel> - Firefox or another Gecko application, which keeps its
# profiles in <rel>. See gecko.sh.
# #
# Assigns rather than returns three states through an exit code, and is the one # Assigns rather than returns three states through an exit code, and is the one
# place the memo and the on-disk cache are consulted. # place the memo and the on-disk cache are consulted.
@@ -900,8 +904,11 @@ mca_detect_verdict() {
real="$(readlink -f -- "$prog" 2>/dev/null)" || real="$prog" real="$(readlink -f -- "$prog" 2>/dev/null)" || real="$prog"
MCA_DETECT_UNSURE=0 MCA_DETECT_UNSURE=0
MCA_DETECT_GECKO=''
if _mca_detect_uncached "$real"; then if _mca_detect_uncached "$real"; then
MCA_VERDICT=yes MCA_VERDICT=yes
elif [[ -n $MCA_DETECT_GECKO ]]; then
MCA_VERDICT="gecko:$MCA_DETECT_GECKO"
elif (( MCA_DETECT_UNSURE )); then elif (( MCA_DETECT_UNSURE )); then
MCA_VERDICT=unknown MCA_VERDICT=unknown
else else
@@ -930,6 +937,10 @@ mca_is_chromium() {
# was nothing there. Only mca_detect_verdict reads it, straight after the call. # was nothing there. Only mca_detect_verdict reads it, straight after the call.
MCA_DETECT_UNSURE=0 MCA_DETECT_UNSURE=0
# Set by _mca_detect_uncached to where the profiles are, when the program turned
# out to be Gecko rather than Chromium.
MCA_DETECT_GECKO=''
_mca_detect_uncached() { _mca_detect_uncached() {
local real="$1" depth="${2:-0}" dir target local real="$1" depth="${2:-0}" dir target
@@ -943,6 +954,7 @@ _mca_detect_uncached() {
if target="$(_mca_script_target "$real")" && [[ -n $target ]]; then if target="$(_mca_script_target "$real")" && [[ -n $target ]]; then
_mca_detect_uncached "$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")" \ _mca_detect_uncached "$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")" \
$(( depth + 1 )) && return 0 $(( depth + 1 )) && return 0
[[ -n $MCA_DETECT_GECKO ]] && return 1
fi fi
grep -qE "$MCA_SCRIPT_HINTS" -- "$real" 2>/dev/null && return 0 grep -qE "$MCA_SCRIPT_HINTS" -- "$real" 2>/dev/null && return 0
return 1 return 1
@@ -954,6 +966,14 @@ _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
# Not Chromium but Gecko, which has autoscroll of its own.
if _mca_gecko_rel "$dir" \
|| { [[ ${dir##*/} == bin ]] && _mca_gecko_rel "${dir%/*}"; }
then
MCA_DETECT_GECKO="$MCA_GECKO_REL"
return 1
fi
# An AppImage keeps all of that inside a filesystem appended to itself, so # 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 # 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. # in there can be read, and that settles it either way.
@@ -989,8 +1009,9 @@ MCA_IDS=() # desktop file id, without the .desktop suffix
MCA_FILES=() # the desktop entry that is in effect for that id MCA_FILES=() # the desktop entry that is in effect for that id
MCA_NAMES=() # display name MCA_NAMES=() # display name
MCA_PROGS=() # resolved program, or a Flatpak app id or a snap name MCA_PROGS=() # resolved program, or a Flatpak app id or a snap name
MCA_KINDS=() # app | browser | steam | spotify | unknown | no MCA_KINDS=() # app | browser | steam | spotify | gecko | unknown | no
MCA_PACKAGING=() # native | flatpak | snap MCA_PACKAGING=() # native | flatpak | snap
MCA_PROFILES=() # where a Gecko application keeps its profiles, see gecko.sh
# 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
@@ -1003,12 +1024,12 @@ mca_scan_once() {
} }
mca_scan() { mca_scan() {
local dir file id name exec_line prog kind packaging i local dir file id name exec_line prog kind packaging profiles i
local -A seen=() local -A seen=()
local -a c_ids=() c_files=() c_names=() c_progs=() c_browser=() c_stat=() local -a c_ids=() c_files=() c_names=() c_progs=() c_browser=() c_stat=()
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_PROFILES=()
# 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
@@ -1073,6 +1094,7 @@ mca_scan() {
prog="${c_progs[i]}" prog="${c_progs[i]}"
kind=no kind=no
packaging=native packaging=native
profiles=''
if [[ $prog == flatpak:* ]]; then if [[ $prog == flatpak:* ]]; then
packaging=flatpak packaging=flatpak
@@ -1080,6 +1102,8 @@ mca_scan() {
kind=steam kind=steam
elif mca_flatpak_is_chromium "${prog#flatpak:}"; then elif mca_flatpak_is_chromium "${prog#flatpak:}"; then
(( c_browser[i] )) && kind=browser || kind=app (( c_browser[i] )) && kind=browser || kind=app
elif mca_flatpak_gecko "${prog#flatpak:}"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi fi
prog="${prog#flatpak:}" prog="${prog#flatpak:}"
elif [[ $prog == snap:* ]]; then elif [[ $prog == snap:* ]]; then
@@ -1089,6 +1113,8 @@ mca_scan() {
kind=steam kind=steam
elif mca_snap_is_chromium "$prog"; then elif mca_snap_is_chromium "$prog"; then
(( c_browser[i] )) && kind=browser || kind=app (( c_browser[i] )) && kind=browser || kind=app
elif mca_snap_gecko "$prog"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi fi
elif mca_prog_is_steam "$prog"; then elif mca_prog_is_steam "$prog"; then
# Steam is Chromium inside, but nothing about it can be changed # Steam is Chromium inside, but nothing about it can be changed
@@ -1103,6 +1129,7 @@ mca_scan() {
case "$MCA_VERDICT" in case "$MCA_VERDICT" in
yes) (( c_browser[i] )) && kind=browser || kind=app ;; yes) (( c_browser[i] )) && kind=browser || kind=app ;;
unknown) kind=unknown ;; unknown) kind=unknown ;;
gecko:*) kind=gecko profiles="${MCA_VERDICT#gecko:}" ;;
esac esac
fi fi
@@ -1114,6 +1141,7 @@ mca_scan() {
MCA_PROGS+=("$prog") MCA_PROGS+=("$prog")
MCA_KINDS+=("$kind") MCA_KINDS+=("$kind")
MCA_PACKAGING+=("$packaging") MCA_PACKAGING+=("$packaging")
MCA_PROFILES+=("$profiles")
done done
mca_cache_flush mca_cache_flush
@@ -1211,16 +1239,14 @@ mca_snap_is_chromium() {
# Flatpak keeps every application in its own tree, so the marker check works the # Flatpak keeps every application in its own tree, so the marker check works the
# same way once that tree has been located. # same way once that tree has been located.
mca_flatpak_is_chromium() { mca_flatpak_is_chromium() {
local id="$1" loc local id="$1"
mca_have flatpak || return 1
if [[ -n ${MCA_DETECT_MEMO[flatpak:$id]+set} ]]; then if [[ -n ${MCA_DETECT_MEMO[flatpak:$id]+set} ]]; then
[[ ${MCA_DETECT_MEMO[flatpak:$id]} == yes ]] [[ ${MCA_DETECT_MEMO[flatpak:$id]} == yes ]]
return $? return $?
fi fi
loc="$(flatpak info --show-location "$id" 2>/dev/null)" if mca_flatpak_location "$id" && _mca_find_markers "$MCA_FLATPAK_AT/files" 4; then
if [[ -n $loc ]] && _mca_find_markers "$loc/files" 4; then
MCA_DETECT_MEMO[flatpak:$id]=yes MCA_DETECT_MEMO[flatpak:$id]=yes
return 0 return 0
fi fi
@@ -1229,6 +1255,25 @@ mca_flatpak_is_chromium() {
return 1 return 1
} }
# mca_flatpak_location <app id>
# Where Flatpak keeps the application, in MCA_FLATPAK_AT. Assigned rather than
# printed so the memo survives: both the Chromium and the Gecko check ask.
declare -A MCA_FLATPAK_LOC=()
MCA_FLATPAK_AT=''
mca_flatpak_location() {
local id="$1"
if [[ -z ${MCA_FLATPAK_LOC[$id]+set} ]]; then
MCA_FLATPAK_LOC[$id]=''
mca_have flatpak \
&& MCA_FLATPAK_LOC[$id]="$(flatpak info --show-location "$id" 2>/dev/null)"
fi
MCA_FLATPAK_AT="${MCA_FLATPAK_LOC[$id]}"
[[ -n $MCA_FLATPAK_AT ]]
}
# mca_kind_wanted <kind> <id> # mca_kind_wanted <kind> <id>
# Whether this entry should be patched. Skip beats everything, an explicit # Whether this entry should be patched. Skip beats everything, an explicit
# include beats detection, and detection beats nothing. # include beats detection, and detection beats nothing.
+254
View File
@@ -0,0 +1,254 @@
# shellcheck shell=bash
#
# Firefox, and everything else built on Gecko: LibreWolf, Zen, Floorp,
# Waterfox, Thunderbird.
#
# Gecko has autoscroll of its own, off on Linux. There is no command line flag
# for it, only a setting: general.autoScroll. Settings live in each profile, so
# the line goes into the profile's user.js, which Gecko reads at every start.
#
# Gecko copies what user.js sets into prefs.js. So undoing takes the line out
# of prefs.js as well. A browser that is still open would write it back when it
# quits, so for that profile user.js says false instead, and the record stays
# until the next undo can finish the job.
MCA_GECKO_BEGIN='// >>> middleclick-autoscroll'
MCA_GECKO_END='// <<< middleclick-autoscroll'
MCA_GECKO_ON='user_pref("general.autoScroll", true);'
MCA_GECKO_OFF='user_pref("general.autoScroll", false);'
# The profile folders one apply has done already, and the profiles.ini files
# the watcher should look at. Both are filled by mca_gecko_apply.
declare -A MCA_GECKO_SEEN=()
MCA_GECKO_WATCH=()
# Set by mca_gecko_revert when a profile is still open and has to be undone
# again later.
MCA_GECKO_PENDING=0
# _mca_gecko_rel <directory>
# Whether a Gecko application is installed there. If so, where it keeps its
# profiles, relative to the home or config folder, in MCA_GECKO_REL. Read from
# application.ini the way Gecko does it: [App] Profile= when it is set, else
# Vendor/Name, in lowercase.
MCA_GECKO_REL=''
_mca_gecko_rel() {
local dir="$1" line group='' vendor='' name='' profile='' rel=''
MCA_GECKO_REL=''
[[ -e $dir/libxul.so && -r $dir/application.ini ]] || return 1
while IFS= read -r line || [[ -n $line ]]; do
line="${line%$'\r'}"
case "$line" in
'['*) group="$line" ;;
Vendor=*) [[ $group == '[App]' ]] && vendor="${line#*=}" ;;
Name=*) [[ $group == '[App]' ]] && name="${line#*=}" ;;
Profile=*) [[ $group == '[App]' ]] && profile="${line#*=}" ;;
esac
done < "$dir/application.ini"
if [[ -n $profile ]]; then
rel="${profile//\\//}"
while [[ $rel == /* ]]; do rel="${rel#/}"; done
rel="${rel#.}"
elif [[ -n $name ]]; then
rel="${vendor:+$vendor/}$name"
fi
# Gecko lowercases in ASCII. A Turkish locale turns I into a dotless ı.
rel="${rel,,}"
rel="${rel//ı/i}"
[[ -n $rel && /$rel/ != */../* ]] || return 1
MCA_GECKO_REL="$rel"
}
# _mca_gecko_tree <root> <depth>
# The same for a Flatpak or a snap, where libxul.so is somewhere in the tree.
_mca_gecko_tree() {
local lib
MCA_GECKO_REL=''
[[ -d $1 ]] || return 1
lib="$(find "$1" -maxdepth "$2" -name libxul.so -print -quit 2>/dev/null)"
[[ -n $lib ]] && _mca_gecko_rel "${lib%/*}"
}
# mca_flatpak_gecko <app id> / mca_snap_gecko <snap name>
# Whether it is Gecko, with the folder in MCA_GECKO_REL. Memoized like the
# Chromium check, and an empty answer means no.
declare -A MCA_GECKO_MEMO=()
mca_flatpak_gecko() {
local key="flatpak:$1"
if [[ -z ${MCA_GECKO_MEMO[$key]+set} ]]; then
MCA_GECKO_REL=''
mca_flatpak_location "$1" && _mca_gecko_tree "$MCA_FLATPAK_AT/files" 4
MCA_GECKO_MEMO[$key]="$MCA_GECKO_REL"
fi
MCA_GECKO_REL="${MCA_GECKO_MEMO[$key]}"
[[ -n $MCA_GECKO_REL ]]
}
mca_snap_gecko() {
local key="snap:$1" d
if [[ -z ${MCA_GECKO_MEMO[$key]+set} ]]; then
MCA_GECKO_REL=''
for d in "${MCA_SNAP_DIRS[@]}"; do
_mca_gecko_tree "$d/$1/current" 5 && break
done
MCA_GECKO_MEMO[$key]="$MCA_GECKO_REL"
fi
MCA_GECKO_REL="${MCA_GECKO_MEMO[$key]}"
[[ -n $MCA_GECKO_REL ]]
}
# mca_gecko_roots <rel> <packaging> <name>
# Every folder that can hold the application's profiles.ini. An older install
# uses ~/.<rel>. A new one (Firefox 147 and later) uses $XDG_CONFIG_HOME/<rel>,
# or ~/<rel> for a fork with a profile name of its own. A Flatpak or a snap has
# the same inside its own folder.
mca_gecko_roots() {
local rel="$1" packaging="$2" name="$3" d
case "$packaging" in
flatpak)
printf '%s\n' "$HOME/.var/app/$name/.$rel" "$HOME/.var/app/$name/config/$rel"
;;
snap)
for d in common current; do
printf '%s\n' "$HOME/snap/$name/$d/.$rel" "$HOME/snap/$name/$d/.config/$rel"
done
;;
*)
printf '%s\n' "$HOME/.$rel" "$MCA_XDG_CONFIG/$rel"
[[ $rel == */* ]] || printf '%s\n' "$HOME/$rel"
;;
esac
}
# mca_gecko_profiles <root>
# Every profile folder that profiles.ini there lists.
mca_gecko_profiles() {
local root="$1" line path='' relative=1 inprofile=0
[[ -r $root/profiles.ini ]] || return 0
while IFS= read -r line || [[ -n $line ]]; do
line="${line%$'\r'}"
case "$line" in
'['*)
_mca_gecko_profile_emit
[[ $line == '[Profile'* ]] && inprofile=1 || inprofile=0
path='' relative=1
;;
Path=*) path="${line#Path=}" ;;
IsRelative=*) relative="${line#IsRelative=}" ;;
esac
done < "$root/profiles.ini"
_mca_gecko_profile_emit
}
_mca_gecko_profile_emit() {
(( inprofile )) && [[ -n $path ]] || return 0
[[ $relative == 1 ]] && path="$root/$path"
[[ -d $path ]] && printf '%s\n' "$path"
return 0
}
# _mca_gecko_write <user.js> <line>
# Puts the line into user.js as a marked block, replacing an older block.
# Returns like mca_write_if_changed.
_mca_gecko_write() {
local file="$1" line="$2" content=''
[[ -f $file ]] && content="$(_mca_flags_drop_block "$file" "$MCA_GECKO_BEGIN" "$MCA_GECKO_END")"
content="${content:+$content$'\n'}$MCA_GECKO_BEGIN"$'\n'"$line"$'\n'"$MCA_GECKO_END"
mca_write_if_changed "$file" "$content"$'\n'
}
# mca_gecko_apply <rel> <packaging> <name>
# Turns autoscroll on in every profile of the application.
mca_gecko_apply() {
local root profile
while IFS= read -r root; do
[[ -n ${MCA_GECKO_SEEN[$root]+set} ]] && continue
MCA_GECKO_SEEN[$root]=1
# A new profile is written into profiles.ini, so the watcher can pick
# it up.
MCA_GECKO_WATCH+=("$root/profiles.ini")
while IFS= read -r profile; do
_mca_gecko_profile_apply "$profile"
done < <(mca_gecko_profiles "$root")
done < <(mca_gecko_roots "$@")
}
_mca_gecko_profile_apply() {
local dir="$1" file="$1/user.js" detail=created
# A user.js that links somewhere else is part of a setup of its own.
[[ -L $file ]] && return 0
[[ -f $file ]] && detail=block
if ! grep -qxF -- "$MCA_GECKO_BEGIN" "$file" 2>/dev/null; then
# Set by the user already, in user.js or in the browser's own
# settings. Their choice: nothing is written and nothing recorded.
grep -qE '^[[:space:]]*user_pref\([[:space:]]*"general\.autoScroll"' \
-- "$file" 2>/dev/null && return 0
grep -qxF -- "$MCA_GECKO_ON" "$dir/prefs.js" 2>/dev/null && return 0
fi
_mca_gecko_write "$file" "$MCA_GECKO_ON"
case $? in
0) MCA_CHANGES=$(( MCA_CHANGES + 1 )) ;;
2) return 0 ;;
esac
# A file this program created stays one it created.
mca_ledger_has "$file" && detail="$(mca_ledger_detail "$file")"
mca_ledger_add gecko "$file" "$detail"
}
# mca_gecko_revert <user.js> <created|block>
# Returns 0 when something changed. Sets MCA_GECKO_PENDING when the profile is
# open and the record has to stay.
mca_gecko_revert() {
local file="$1" detail="$2" dir="${1%/*}" prefs content tmp rc=0
MCA_GECKO_PENDING=0
[[ -f $file && ! -L $file ]] || return 1
grep -qxF -- "$MCA_GECKO_BEGIN" "$file" 2>/dev/null || return 1
# The lock link only exists while the profile is open.
if [[ -L $dir/lock ]]; then
MCA_GECKO_PENDING=1
_mca_gecko_write "$file" "$MCA_GECKO_OFF"
return
fi
content="$(_mca_flags_drop_block "$file" "$MCA_GECKO_BEGIN" "$MCA_GECKO_END")"
if [[ $detail == created && -z ${content//[[:space:]]/} ]]; then
rm -f -- "$file"
else
printf '%s\n' "$content" > "$file"
fi
prefs="$dir/prefs.js"
if [[ -f $prefs && ! -L $prefs ]] && grep -qxF -- "$MCA_GECKO_ON" "$prefs"; then
tmp="$(mktemp "$prefs.XXXXXX")" || return 0
chmod --reference="$prefs" -- "$tmp" 2>/dev/null || chmod 600 -- "$tmp"
# grep says 1 when no line is left, which is fine here.
grep -vxF -- "$MCA_GECKO_ON" "$prefs" > "$tmp" || rc=$?
if (( rc <= 1 )); then mv -f -- "$tmp" "$prefs"; else rm -f -- "$tmp"; fi
fi
return 0
}
+5 -2
View File
@@ -419,13 +419,14 @@ mca_ui_apps() {
# The per-row labels are resolved once, here. Looking them up inside the # The per-row labels are resolved once, here. Looking them up inside the
# drawing loop is a fork per row per keypress, and that is enough to make # drawing loop is a fork per row per keypress, and that is enough to make
# the arrow keys feel like the screen is reloading. # the arrow keys feel like the screen is reloading.
local l_off l_cannot l_on l_steam l_flagfile l_launcher local l_off l_cannot l_on l_steam l_flagfile l_launcher l_profile
mca_msg_into "$locale" "off"; l_off="$MCA_MSG_RESULT" mca_msg_into "$locale" "off"; l_off="$MCA_MSG_RESULT"
mca_msg_into "$locale" "cannot tell"; l_cannot="$MCA_MSG_RESULT" mca_msg_into "$locale" "cannot tell"; l_cannot="$MCA_MSG_RESULT"
mca_msg_into "$locale" "on"; l_on="$MCA_MSG_RESULT" mca_msg_into "$locale" "on"; l_on="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Steam"; l_steam="$MCA_MSG_RESULT" mca_msg_into "$locale" "Steam"; l_steam="$MCA_MSG_RESULT"
mca_msg_into "$locale" "flag file"; l_flagfile="$MCA_MSG_RESULT" mca_msg_into "$locale" "flag file"; l_flagfile="$MCA_MSG_RESULT"
mca_msg_into "$locale" "launcher"; l_launcher="$MCA_MSG_RESULT" mca_msg_into "$locale" "launcher"; l_launcher="$MCA_MSG_RESULT"
mca_msg_into "$locale" "profile"; l_profile="$MCA_MSG_RESULT"
local s_off="${MCA_C_DIM}${l_off}${MCA_C_RESET}" local s_off="${MCA_C_DIM}${l_off}${MCA_C_RESET}"
local s_cannot="${MCA_C_DIM}${l_cannot}${MCA_C_RESET}" local s_cannot="${MCA_C_DIM}${l_cannot}${MCA_C_RESET}"
@@ -434,6 +435,7 @@ mca_ui_apps() {
local s_flags="${s_on} ${MCA_C_DIM}(${l_flagfile})${MCA_C_RESET}" local s_flags="${s_on} ${MCA_C_DIM}(${l_flagfile})${MCA_C_RESET}"
local s_desktop="${s_on} ${MCA_C_DIM}(${l_launcher})${MCA_C_RESET}" local s_desktop="${s_on} ${MCA_C_DIM}(${l_launcher})${MCA_C_RESET}"
local s_spotify="${s_on} ${MCA_C_DIM}(spotify-launcher)${MCA_C_RESET}" local s_spotify="${s_on} ${MCA_C_DIM}(spotify-launcher)${MCA_C_RESET}"
local s_gecko="${s_on} ${MCA_C_DIM}(${l_profile})${MCA_C_RESET}"
local clearseq local clearseq
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
@@ -444,7 +446,7 @@ mca_ui_apps() {
count=${#MCA_IDS[@]} count=${#MCA_IDS[@]}
if (( count == 0 )); then if (( count == 0 )); then
MCA_UI_MSG="$(mca_msg "No Chromium-based applications found.")" MCA_UI_MSG="$(mca_msg "No supported applications found.")"
return 1 return 1
fi fi
@@ -526,6 +528,7 @@ mca_ui_apps() {
steam) shown="$s_steam" ;; steam) shown="$s_steam" ;;
flags) shown="$s_flags" ;; flags) shown="$s_flags" ;;
spotify) shown="$s_spotify" ;; spotify) shown="$s_spotify" ;;
gecko) shown="$s_gecko" ;;
*) shown="$s_desktop" ;; *) shown="$s_desktop" ;;
esac esac
pad=$(( 34 - ${#labels[idx]} )) pad=$(( 34 - ${#labels[idx]} ))
+10 -1
View File
@@ -260,8 +260,9 @@ _mca_flags_write() {
return 0 return 0
} }
# _mca_flags_drop_block <file> [begin marker] [end marker]
_mca_flags_drop_block() { _mca_flags_drop_block() {
awk -v b="$MCA_MARK_BEGIN" -v e="$MCA_MARK_END" ' awk -v b="${2:-$MCA_MARK_BEGIN}" -v e="${3:-$MCA_MARK_END}" '
$0 == b { skip = 1; next } $0 == b { skip = 1; next }
$0 == e { skip = 0; next } $0 == e { skip = 0; next }
!skip !skip
@@ -632,6 +633,7 @@ mca_prune_orphans() {
mca_revert_all() { mca_revert_all() {
local kind path detail backup local kind path detail backup
local -a keep=()
[[ -f $MCA_LEDGER ]] || return 0 [[ -f $MCA_LEDGER ]] || return 0
@@ -674,10 +676,17 @@ mca_revert_all() {
mca_kde_paste_revert "$path" "$detail" \ mca_kde_paste_revert "$path" "$detail" \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 )) && MCA_CHANGES=$(( MCA_CHANGES + 1 ))
;; ;;
gecko)
mca_gecko_revert "$path" "$detail" \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
# Still open, so not done yet. See gecko.sh.
(( MCA_GECKO_PENDING )) && keep+=("$kind"$'\t'"$path"$'\t'"$detail")
;;
esac esac
done < <(tac "$MCA_LEDGER" 2>/dev/null) done < <(tac "$MCA_LEDGER" 2>/dev/null)
rm -f -- "$MCA_LEDGER" rm -f -- "$MCA_LEDGER"
(( ${#keep[@]} )) && printf '%s\n' "${keep[@]}" > "$MCA_LEDGER"
rmdir "$MCA_BACKUPDIR" 2>/dev/null || true rmdir "$MCA_BACKUPDIR" 2>/dev/null || true
return 0 return 0
} }
+4 -4
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# middleclick-autoscroll: middle-click autoscroll for Chromium-based # middleclick-autoscroll: middle-click autoscroll for Chromium-based
# applications on Linux # applications and Firefox on Linux
# #
# Blink has had Windows-style autoscroll for years, behind a runtime flag that # Blink has had Windows-style autoscroll for years, behind a runtime flag that
# is not turned on for Linux because middle click is taken by primary-selection # is not turned on for Linux because middle click is taken by primary-selection
# paste there. Turning it on is a matter of one command line argument. The # paste there. Turning it on is a matter of one command line argument. The
# work is finding every application that needs it, putting the argument # work is finding every application that needs it, putting the argument
# somewhere it survives an update, and doing the same for the two programs that # somewhere it survives an update, and doing the same for the two programs that
# do not take arguments at all. # do not take arguments at all. Firefox has the same feature behind a setting.
# #
# Copyright (C) 2026 Felitendo # Copyright (C) 2026 Felitendo
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
@@ -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 kde apply menu; do for _mod in common config detect patch steam gecko 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
@@ -144,7 +144,7 @@ mca_do_list() {
mca_count_routes mca_count_routes
if (( ${#MCA_IDS[@]} == 0 )); then if (( ${#MCA_IDS[@]} == 0 )); then
mca_note "$(mca_msg "No Chromium-based applications found.")" mca_note "$(mca_msg "No supported applications found.")"
return 0 return 0
fi fi