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

+45 -13
View File
@@ -16,6 +16,7 @@ MCA_ROUTES=()
# desktop - no flag file; shadow or edit the desktop entry
# steam - Steam's own two-part treatment
# 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
# off - detected, but switched off
#
@@ -27,7 +28,7 @@ MCA_ROUTE=''
mca_route() {
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
MCA_ROUTE=off
else
@@ -65,6 +66,7 @@ mca_apply() {
MCA_CHANGES=0
MCA_ROUTES=()
MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0
MCA_GECKO_SEEN=(); MCA_GECKO_WATCH=()
mca_config_load
@@ -116,6 +118,9 @@ mca_apply() {
(( spotify_done )) || mca_spotify_apply
spotify_done=1
;;
gecko)
mca_gecko_apply "${MCA_PROFILES[i]}" "$packaging" "$prog"
;;
esac
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
# in, but its name is translated and the unit that ships with the package
# cannot know it. So that one path is written here, into a drop-in, from the
# name this system actually uses.
# 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_watch_available() {
@@ -196,23 +202,47 @@ mca_watch_enabled() {
# same reason: the watcher runs after every desktop entry that appears anywhere on
# the system, and a daemon-reload each time would be absurd.
_mca_watch_dropin_write() {
local dir file="$MCA_UNIT_DROPIN/desktop.conf" content
dir="$(mca_desktop_folder)"
[[ -d $dir ]] || { _mca_watch_dropin_remove; return $?; }
local dir path content='' changed=1
# A per cent sign starts a specifier in a unit file and has to be doubled
# to mean itself. Rare in a folder name, fatal when it happens.
content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n'
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() {
[[ -e "$MCA_UNIT_DROPIN/desktop.conf" ]] || return 1
rm -f -- "$MCA_UNIT_DROPIN/desktop.conf"
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true
return 0
local changed=1
_mca_watch_conf desktop.conf '' && changed=0
_mca_watch_conf profiles.conf '' && changed=0
return "$changed"
}
mca_watch_enable() {
@@ -226,7 +256,8 @@ mca_watch_enable() {
# mca_watch_refresh
# Keeps a running watcher in step with a desktop folder that has been renamed,
# which is what a change of session language does to it.
# which is what a change of session language does to it, and with the Firefox
# profiles there are now.
mca_watch_refresh() {
mca_watch_available || return 0
_mca_watch_dropin_write || return 0
@@ -295,6 +326,7 @@ mca_route_label() {
desktop) mca_msg "launcher" ;;
steam) mca_msg "Steam" ;;
spotify) printf 'spotify-launcher' ;;
gecko) mca_msg "profile" ;;
unknown) mca_msg "cannot tell" ;;
*) mca_msg "off" ;;
esac
+3 -1
View File
@@ -283,11 +283,13 @@ mca_state_write() {
# inplace <edited file> <basename of its backup copy>
# flags <flag file> created | appended | merged
# 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
# 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.
# 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() {
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
# list anyway, because /usr/bin/discord had not changed and its old "no" was
# 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() {
local path stamp verdict first=1
@@ -862,6 +864,8 @@ _mca_appimage_verdict() {
# 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
# 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
# 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"
MCA_DETECT_UNSURE=0
MCA_DETECT_GECKO=''
if _mca_detect_uncached "$real"; then
MCA_VERDICT=yes
elif [[ -n $MCA_DETECT_GECKO ]]; then
MCA_VERDICT="gecko:$MCA_DETECT_GECKO"
elif (( MCA_DETECT_UNSURE )); then
MCA_VERDICT=unknown
else
@@ -930,6 +937,10 @@ mca_is_chromium() {
# was nothing there. Only mca_detect_verdict reads it, straight after the call.
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() {
local real="$1" depth="${2:-0}" dir target
@@ -943,6 +954,7 @@ _mca_detect_uncached() {
if target="$(_mca_script_target "$real")" && [[ -n $target ]]; then
_mca_detect_uncached "$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")" \
$(( depth + 1 )) && return 0
[[ -n $MCA_DETECT_GECKO ]] && return 1
fi
grep -qE "$MCA_SCRIPT_HINTS" -- "$real" 2>/dev/null && return 0
return 1
@@ -954,6 +966,14 @@ _mca_detect_uncached() {
_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
# 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.
@@ -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_NAMES=() # display 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_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
# then redraws from what it found. Applying rescans on its own, so nothing else
@@ -1003,12 +1024,12 @@ mca_scan_once() {
}
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 c_ids=() c_files=() c_names=() c_progs=() c_browser=() c_stat=()
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
# detection yet: that needs a stat per program, and those are collected so
@@ -1073,6 +1094,7 @@ mca_scan() {
prog="${c_progs[i]}"
kind=no
packaging=native
profiles=''
if [[ $prog == flatpak:* ]]; then
packaging=flatpak
@@ -1080,6 +1102,8 @@ mca_scan() {
kind=steam
elif mca_flatpak_is_chromium "${prog#flatpak:}"; then
(( c_browser[i] )) && kind=browser || kind=app
elif mca_flatpak_gecko "${prog#flatpak:}"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi
prog="${prog#flatpak:}"
elif [[ $prog == snap:* ]]; then
@@ -1089,6 +1113,8 @@ mca_scan() {
kind=steam
elif mca_snap_is_chromium "$prog"; then
(( c_browser[i] )) && kind=browser || kind=app
elif mca_snap_gecko "$prog"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi
elif mca_prog_is_steam "$prog"; then
# Steam is Chromium inside, but nothing about it can be changed
@@ -1103,6 +1129,7 @@ mca_scan() {
case "$MCA_VERDICT" in
yes) (( c_browser[i] )) && kind=browser || kind=app ;;
unknown) kind=unknown ;;
gecko:*) kind=gecko profiles="${MCA_VERDICT#gecko:}" ;;
esac
fi
@@ -1114,6 +1141,7 @@ mca_scan() {
MCA_PROGS+=("$prog")
MCA_KINDS+=("$kind")
MCA_PACKAGING+=("$packaging")
MCA_PROFILES+=("$profiles")
done
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
# same way once that tree has been located.
mca_flatpak_is_chromium() {
local id="$1" loc
mca_have flatpak || return 1
local id="$1"
if [[ -n ${MCA_DETECT_MEMO[flatpak:$id]+set} ]]; then
[[ ${MCA_DETECT_MEMO[flatpak:$id]} == yes ]]
return $?
fi
loc="$(flatpak info --show-location "$id" 2>/dev/null)"
if [[ -n $loc ]] && _mca_find_markers "$loc/files" 4; then
if mca_flatpak_location "$id" && _mca_find_markers "$MCA_FLATPAK_AT/files" 4; then
MCA_DETECT_MEMO[flatpak:$id]=yes
return 0
fi
@@ -1229,6 +1255,25 @@ mca_flatpak_is_chromium() {
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>
# Whether this entry should be patched. Skip beats everything, an explicit
# 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
# drawing loop is a fork per row per keypress, and that is enough to make
# 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" "cannot tell"; l_cannot="$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" "flag file"; l_flagfile="$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_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_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_gecko="${s_on} ${MCA_C_DIM}(${l_profile})${MCA_C_RESET}"
local clearseq
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
@@ -444,7 +446,7 @@ mca_ui_apps() {
count=${#MCA_IDS[@]}
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
fi
@@ -526,6 +528,7 @@ mca_ui_apps() {
steam) shown="$s_steam" ;;
flags) shown="$s_flags" ;;
spotify) shown="$s_spotify" ;;
gecko) shown="$s_gecko" ;;
*) shown="$s_desktop" ;;
esac
pad=$(( 34 - ${#labels[idx]} ))
+10 -1
View File
@@ -260,8 +260,9 @@ _mca_flags_write() {
return 0
}
# _mca_flags_drop_block <file> [begin marker] [end marker]
_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 == e { skip = 0; next }
!skip
@@ -632,6 +633,7 @@ mca_prune_orphans() {
mca_revert_all() {
local kind path detail backup
local -a keep=()
[[ -f $MCA_LEDGER ]] || return 0
@@ -674,10 +676,17 @@ mca_revert_all() {
mca_kde_paste_revert "$path" "$detail" \
&& 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
done < <(tac "$MCA_LEDGER" 2>/dev/null)
rm -f -- "$MCA_LEDGER"
(( ${#keep[@]} )) && printf '%s\n' "${keep[@]}" > "$MCA_LEDGER"
rmdir "$MCA_BACKUPDIR" 2>/dev/null || true
return 0
}
+4 -4
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
#
# 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
# 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
# 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
# do not take arguments at all.
# do not take arguments at all. Firefox has the same feature behind a setting.
#
# Copyright (C) 2026 Felitendo
# SPDX-License-Identifier: GPL-3.0-or-later
@@ -17,7 +17,7 @@ set -uo pipefail
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
if ! source "$MCA_LIBDIR/$_mod.sh"; then
printf 'middleclick-autoscroll: cannot load %s/%s.sh\n' "$MCA_LIBDIR" "$_mod" >&2
@@ -144,7 +144,7 @@ mca_do_list() {
mca_count_routes
if (( ${#MCA_IDS[@]} == 0 )); then
mca_note "$(mca_msg "No Chromium-based applications found.")"
mca_note "$(mca_msg "No supported applications found.")"
return 0
fi