Compare commits

...
1 Commits
Author SHA1 Message Date
Felitendo 5b1fc84710 Never mistake an entry edited in place for one we generated
A desktop entry that already lives in ~/.local/share/applications is the
user's own file, patched in place with the original kept aside. It used to
get the same X-MCA-Generated marker as a shadow copy, which made the next
scan skip it, fall back to the system entry, and overwrite the user's file
with a shadow - and then delete it on revert instead of restoring it.

The two cases now carry different markers. Only a shadow is skipped by the
scan and deleted when undoing; an entry patched in place stays in the scan
and is restored from its backup.

Also: resolve the applications screen's labels once instead of per row per
keypress, and drop code nothing calls.
2026-08-16 19:56:19 +02:00
10 changed files with 63 additions and 60 deletions

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
# Overridable so a packager can pass the version it is actually building # Overridable so a packager can pass the version it is actually building
# (`make VERSION=$pkgver`). The literal below is the fallback for builds # (`make VERSION=$pkgver`). The literal below is the fallback for builds
# straight from a checkout, and is what a release tag has to carry. # straight from a checkout, and is what a release tag has to carry.
VERSION ?= 1.0.0 VERSION ?= 1.0.1
PREFIX ?= /usr PREFIX ?= /usr
DESTDIR ?= DESTDIR ?=
+6 -3
View File
@@ -72,9 +72,12 @@ Two ways, chosen per application.
Flatpaks, a copy of the desktop entry with the argument appended is written Flatpaks, a copy of the desktop entry with the argument appended is written
to _~/.local/share/applications_, where it shadows the system one. Entries to _~/.local/share/applications_, where it shadows the system one. Entries
that already live there - AppImages, web app shortcuts - are edited in that already live there - AppImages, web app shortcuts - are edited in
place, with the original kept. Every generated entry is marked with place, with the original kept.
*X-MCA-Generated*; an entry that is not marked is somebody else's and is
left alone. A generated entry is marked *X-MCA-Generated* and an entry edited in place
*X-MCA-Patched*. The two are never confused: the first is deleted when
undoing, the second is restored from its backup. An entry with neither
marker belongs to somebody else and is left alone.
Programs that start themselves at login write their own entry into Programs that start themselves at login write their own entry into
_~/.config/autostart_ pointing straight at their binary, bypassing the menu _~/.config/autostart_ pointing straight at their binary, bypassing the menu
+1 -1
View File
@@ -4,7 +4,7 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.0.0\n" "Project-Id-Version: middleclick-autoscroll 1.0.1\n"
"Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n" "Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n"
"POT-Creation-Date: 2026-08-16 00:00+0200\n" "POT-Creation-Date: 2026-08-16 00:00+0200\n"
"PO-Revision-Date: 2026-08-16 00:00+0200\n" "PO-Revision-Date: 2026-08-16 00:00+0200\n"
+1 -1
View File
@@ -4,7 +4,7 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.0.0\n" "Project-Id-Version: middleclick-autoscroll 1.0.1\n"
"Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n" "Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n"
"POT-Creation-Date: 2026-08-16 00:00+0200\n" "POT-Creation-Date: 2026-08-16 00:00+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-4
View File
@@ -8,7 +8,6 @@
# of the behaviour, it is the behaviour. # of the behaviour, it is the behaviour.
MCA_CHANGES=0 MCA_CHANGES=0
MCA_TOUCHED=()
MCA_ROUTES=() MCA_ROUTES=()
# mca_route <kind> <id> <program> # mca_route <kind> <id> <program>
@@ -63,7 +62,6 @@ mca_apply() {
local i id file prog kind route steam_done=0 local i id file prog kind route steam_done=0
MCA_CHANGES=0 MCA_CHANGES=0
MCA_TOUCHED=()
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
@@ -117,7 +115,6 @@ mca_apply() {
fi fi
mca_state_write last_apply "$(date +%s)" mca_state_write last_apply "$(date +%s)"
mca_state_write covered "${#MCA_TOUCHED[@]}"
return 0 return 0
} }
@@ -131,7 +128,6 @@ mca_revert() {
update-desktop-database "$MCA_APPDIR" 2>/dev/null || true update-desktop-database "$MCA_APPDIR" 2>/dev/null || true
fi fi
mca_state_write covered 0
return 0 return 0
} }
-17
View File
@@ -158,7 +158,6 @@ mca_head() { printf '\n%s%s%s\n\n' "$MCA_C_BOLD$MCA_C_BLUE" "$*" "$MCA_C_RESET";
mca_ok() { [[ -n $MCA_QUIET ]] || printf '%s✔%s %s\n' "$MCA_C_GREEN" "$MCA_C_RESET" "$*"; } mca_ok() { [[ -n $MCA_QUIET ]] || printf '%s✔%s %s\n' "$MCA_C_GREEN" "$MCA_C_RESET" "$*"; }
mca_bad() { MCA_UI_NEEDS_ACK=1; printf '%s✘%s %s\n' "$MCA_C_RED" "$MCA_C_RESET" "$*" >&2; } mca_bad() { MCA_UI_NEEDS_ACK=1; printf '%s✘%s %s\n' "$MCA_C_RED" "$MCA_C_RESET" "$*" >&2; }
mca_note() { MCA_UI_NEEDS_ACK=1; [[ -n $MCA_QUIET ]] || printf '%s•%s %s\n' "$MCA_C_DIM" "$MCA_C_RESET" "$*"; } mca_note() { MCA_UI_NEEDS_ACK=1; [[ -n $MCA_QUIET ]] || printf '%s•%s %s\n' "$MCA_C_DIM" "$MCA_C_RESET" "$*"; }
mca_debug() { [[ -n ${MCA_DEBUG:-} ]] && printf 'debug: %s\n' "$*" >&2; return 0; }
mca_have() { command -v "$1" > /dev/null 2>&1; } mca_have() { command -v "$1" > /dev/null 2>&1; }
@@ -239,22 +238,6 @@ mca_ledger_forget() {
return 0 return 0
} }
# mca_ledger_has <path>
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>
mca_ledger_detail() {
[[ -f $MCA_LEDGER ]] || return 1
awk -F'\t' -v p="$1" '$2 == p { print $3; exit }' "$MCA_LEDGER"
}
mca_ledger_count() {
[[ -f $MCA_LEDGER ]] || { printf '0\n'; return; }
grep -c . "$MCA_LEDGER" 2>/dev/null || printf '0\n'
}
# mca_backup <file> # mca_backup <file>
# Copies a file aside before it is edited in place, and prints the name the # Copies a file aside before it is edited in place, and prints the name the
+5 -4
View File
@@ -97,6 +97,9 @@ _mca_desktop_read() {
Hidden=*) DE_HIDDEN="${line#Hidden=}" ;; Hidden=*) DE_HIDDEN="${line#Hidden=}" ;;
Categories=*) DE_CATEGORIES="${line#Categories=}" ;; Categories=*) DE_CATEGORIES="${line#Categories=}" ;;
MimeType=*) DE_MIME="${line#MimeType=}" ;; MimeType=*) DE_MIME="${line#MimeType=}" ;;
# Only a generated shadow. An entry we edited in place carries
# X-MCA-Patched and is still the application's real entry, so it
# has to stay in the scan.
X-MCA-Generated=*) DE_OURS=1 ;; X-MCA-Generated=*) DE_OURS=1 ;;
esac esac
done < "$file" done < "$file"
@@ -399,8 +402,8 @@ MCA_PROGS=() # resolved program, or a flatpak app id
MCA_KINDS=() # app | browser | flatpak | steam | unknown | no MCA_KINDS=() # app | browser | flatpak | steam | unknown | no
# 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. Anything that changes the answer - applying, # then redraws from what it found. Applying rescans on its own, so nothing else
# reverting - invalidates it explicitly. # has to remember to invalidate this.
MCA_SCANNED=0 MCA_SCANNED=0
mca_scan_once() { mca_scan_once() {
@@ -408,8 +411,6 @@ mca_scan_once() {
mca_scan mca_scan
} }
mca_scan_invalidate() { MCA_SCANNED=0; }
mca_scan() { mca_scan() {
local dir file id name exec_line prog kind i local dir file id name exec_line prog kind i
local -A seen=() local -A seen=()
+27 -7
View File
@@ -371,12 +371,32 @@ mca_ui_apps() {
locale="$(mca_ui_locale)" locale="$(mca_ui_locale)"
local title hint legend local title hint legend warn
mca_msg_into "$locale" "Applications"; title="$MCA_MSG_RESULT" mca_msg_into "$locale" "Applications"; title="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Up/Down select - Space turns one on or off - q goes back" mca_msg_into "$locale" "Up/Down select - Space turns one on or off - q goes back"
hint="$MCA_MSG_RESULT" hint="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Anything not identified is left alone until it is turned on here." mca_msg_into "$locale" "Anything not identified is left alone until it is turned on here."
legend="$MCA_MSG_RESULT" legend="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Autoscroll is off - this is what would be covered."
warn="$MCA_MSG_RESULT"
# 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
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"
local s_off="${MCA_C_DIM}${l_off}${MCA_C_RESET}"
local s_cannot="${MCA_C_DIM}${l_cannot}${MCA_C_RESET}"
local s_on="${MCA_C_GREEN}${l_on}${MCA_C_RESET}"
local s_steam="${s_on} ${MCA_C_DIM}(${l_steam})${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 clearseq local clearseq
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
@@ -409,11 +429,11 @@ mca_ui_apps() {
local marker selected="${MCA_C_BLUE}▸${MCA_C_RESET} " shown local marker selected="${MCA_C_BLUE}▸${MCA_C_RESET} " shown
for i in "${!labels[@]}"; do for i in "${!labels[@]}"; do
case "${states[i]}" in case "${states[i]}" in
off) shown="${MCA_C_DIM}$(mca_msg "off")${MCA_C_RESET}" ;; off) shown="$s_off" ;;
unknown) shown="${MCA_C_DIM}$(mca_msg "cannot tell")${MCA_C_RESET}" ;; unknown) shown="$s_cannot" ;;
steam) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "Steam"))${MCA_C_RESET}" ;; steam) shown="$s_steam" ;;
flags) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "flag file"))${MCA_C_RESET}" ;; flags) shown="$s_flags" ;;
*) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "launcher"))${MCA_C_RESET}" ;; *) shown="$s_desktop" ;;
esac esac
pad=$(( 34 - ${#labels[i]} )) pad=$(( 34 - ${#labels[i]} ))
(( pad < 0 )) && pad=0 (( pad < 0 )) && pad=0
@@ -426,7 +446,7 @@ mca_ui_apps() {
# Without this the list reads as a list of what is switched on, which # Without this the list reads as a list of what is switched on, which
# it is not while the whole thing is off. # it is not while the whole thing is off.
if [[ $CFG_ENABLED != yes ]]; then if [[ $CFG_ENABLED != yes ]]; then
frame+=" ${MCA_C_YELLOW}$(mca_msg "Autoscroll is off - this is what would be covered.")${MCA_C_RESET}"$'\n' frame+=" ${MCA_C_YELLOW}${warn}${MCA_C_RESET}"$'\n'
fi fi
frame+=" ${MCA_C_DIM}${legend}${MCA_C_RESET}"$'\n' frame+=" ${MCA_C_DIM}${legend}${MCA_C_RESET}"$'\n'
frame+=" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n' frame+=" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n'
+21 -19
View File
@@ -195,9 +195,6 @@ _mca_flags_write() {
mca_ledger_add flags "$file" block mca_ledger_add flags "$file" block
fi fi
# Every application whose flag file was touched counts as covered, so the
# status line can say how many there are.
MCA_TOUCHED+=("$file")
return 0 return 0
} }
@@ -255,12 +252,20 @@ mca_flags_revert() {
# Desktop entries # Desktop entries
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# _mca_desktop_transform <file> [flags] [position] # A generated entry and an entry that was edited in place look similar and must
# never be confused: the first one is ours to delete, the second one is the
# user's file with one line changed and has to be restored from its backup. They
# carry different markers so that the scan, the "already done" checks and the
# undo can all tell them apart.
MCA_MARK_SHADOW='X-MCA-Generated'
MCA_MARK_INPLACE='X-MCA-Patched'
# _mca_desktop_transform <file> <marker> [flags] [position]
# The whole file with every Exec line rewritten - the main one and the one in # The whole file with every Exec line rewritten - the main one and the one in
# each Desktop Action, because those are the right-click menu entries and a # each Desktop Action, because those are the right-click menu entries and a
# user who starts Steam from "Library" expects the same behaviour there. # user who starts Steam from "Library" expects the same behaviour there.
_mca_desktop_transform() { _mca_desktop_transform() {
local file="$1" flags="${2:-}" where="${3:-before-fields}" local file="$1" marker="$2" flags="${3:-}" where="${4:-before-fields}"
local line rest new marked=0 local line rest new marked=0
while IFS= read -r line || [[ -n $line ]]; do while IFS= read -r line || [[ -n $line ]]; do
@@ -283,7 +288,7 @@ _mca_desktop_transform() {
printf '%s\n' "$line" printf '%s\n' "$line"
if (( ! marked )) && [[ $line =~ ^[[:space:]]*\[Desktop\ Entry\][[:space:]]*$ ]]; then if (( ! marked )) && [[ $line =~ ^[[:space:]]*\[Desktop\ Entry\][[:space:]]*$ ]]; then
printf 'X-MCA-Generated=%s\n' "$MCA_VERSION" printf '%s=%s\n' "$marker" "$MCA_VERSION"
marked=1 marked=1
fi fi
done < "$file" done < "$file"
@@ -300,9 +305,9 @@ mca_desktop_apply() {
# The user's own entry - an AppImage, a web app shortcut, something # The user's own entry - an AppImage, a web app shortcut, something
# installed by hand. There is nowhere to shadow it from, so it is # installed by hand. There is nowhere to shadow it from, so it is
# edited directly and the original is kept. # edited directly and the original is kept.
grep -q '^X-MCA-Generated=' "$src" 2>/dev/null && return 0 grep -q "^$MCA_MARK_INPLACE=" "$src" 2>/dev/null && return 0
content="$(_mca_desktop_transform "$src" "$flags" "$where")" content="$(_mca_desktop_transform "$src" "$MCA_MARK_INPLACE" "$flags" "$where")"
[[ -n $content ]] || return 1 [[ -n $content ]] || return 1
[[ "$content" == "$(< "$src")" ]] && return 0 [[ "$content" == "$(< "$src")" ]] && return 0
@@ -311,18 +316,18 @@ mca_desktop_apply() {
MCA_CHANGES=$(( MCA_CHANGES + 1 )) MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
mca_ledger_add inplace "$src" "$backup" mca_ledger_add inplace "$src" "$backup"
MCA_TOUCHED+=("$src")
return 0 return 0
fi fi
content="$(_mca_desktop_transform "$src" "$flags" "$where")" content="$(_mca_desktop_transform "$src" "$MCA_MARK_SHADOW" "$flags" "$where")"
[[ -n $content ]] || return 1 [[ -n $content ]] || return 1
mkdir -p "$MCA_APPDIR" 2>/dev/null || return 1 mkdir -p "$MCA_APPDIR" 2>/dev/null || return 1
# Something is already shadowing this entry. If it is not ours it is the # Something is already shadowing this entry. Unless it is a shadow of ours,
# user's own override and is left exactly as it is. # it is the user's own file - possibly one we edited in place earlier - and
if [[ -e $target ]] && ! grep -q '^X-MCA-Generated=' "$target" 2>/dev/null; then # overwriting it here would lose it.
if [[ -e $target ]] && ! grep -q "^$MCA_MARK_SHADOW=" "$target" 2>/dev/null; then
return 1 return 1
fi fi
@@ -330,7 +335,6 @@ mca_desktop_apply() {
MCA_CHANGES=$(( MCA_CHANGES + 1 )) MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
mca_ledger_add shadow "$target" "$src" mca_ledger_add shadow "$target" "$src"
MCA_TOUCHED+=("$target")
return 0 return 0
} }
@@ -349,7 +353,7 @@ mca_autostart_apply() {
for file in "$dir"/*.desktop; do for file in "$dir"/*.desktop; do
[[ -f $file ]] || continue [[ -f $file ]] || continue
grep -q '^X-MCA-Generated=' "$file" 2>/dev/null && continue grep -q "^$MCA_MARK_INPLACE=" "$file" 2>/dev/null && continue
exec_line="$(mca_desktop_get "$file" Exec)" exec_line="$(mca_desktop_get "$file" Exec)"
[[ -n $exec_line ]] || continue [[ -n $exec_line ]] || continue
@@ -358,7 +362,7 @@ mca_autostart_apply() {
prog="$MCA_PROG" prog="$MCA_PROG"
mca_is_chromium "$prog" || continue mca_is_chromium "$prog" || continue
content="$(_mca_desktop_transform "$file")" content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")"
[[ -n $content ]] || continue [[ -n $content ]] || continue
[[ "$content" == "$(< "$file")" ]] && continue [[ "$content" == "$(< "$file")" ]] && continue
@@ -367,7 +371,6 @@ mca_autostart_apply() {
MCA_CHANGES=$(( MCA_CHANGES + 1 )) MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
mca_ledger_add inplace "$file" "$backup" mca_ledger_add inplace "$file" "$backup"
MCA_TOUCHED+=("$file")
done done
} }
@@ -412,7 +415,6 @@ mca_spotify_apply() {
MCA_CHANGES=$(( MCA_CHANGES + 1 )) MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
mca_ledger_add inplace "$file" "${backup:-}" mca_ledger_add inplace "$file" "${backup:-}"
MCA_TOUCHED+=("$file")
return 0 return 0
} }
@@ -431,7 +433,7 @@ mca_revert_all() {
case "$kind" in case "$kind" in
shadow) shadow)
# Only remove what is still recognisably ours. # Only remove what is still recognisably ours.
if [[ -f $path ]] && grep -q '^X-MCA-Generated=' "$path" 2>/dev/null; then if [[ -f $path ]] && grep -q "^$MCA_MARK_SHADOW=" "$path" 2>/dev/null; then
rm -f -- "$path" rm -f -- "$path"
MCA_CHANGES=$(( MCA_CHANGES + 1 )) MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
+1 -3
View File
@@ -18,7 +18,6 @@
# 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_EXEC='exec ./steamwebhelper "$@"'
MCA_STEAM_LAUNCH_FLAG='-noverifyfiles' 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
@@ -106,7 +105,7 @@ mca_steam_apply() {
script="$(mca_steam_script "$root")" || continue script="$(mca_steam_script "$root")" || continue
found=1 found=1
mca_steam_script_patched "$script" && { MCA_TOUCHED+=("$script"); continue; } mca_steam_script_patched "$script" && continue
lineno="$(_mca_steam_exec_line "$script")" lineno="$(_mca_steam_exec_line "$script")"
if [[ -z $lineno ]]; then if [[ -z $lineno ]]; then
@@ -125,7 +124,6 @@ mca_steam_apply() {
fi fi
chmod +x -- "$script" 2>/dev/null || true chmod +x -- "$script" 2>/dev/null || true
mca_ledger_add steam "$script" "$backup" mca_ledger_add steam "$script" "$backup"
MCA_TOUCHED+=("$script")
done < <(mca_steam_roots) done < <(mca_steam_roots)
(( found )) (( found ))