diff --git a/Makefile b/Makefile index 403c54b..dc0f6f1 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ # Overridable so a packager can pass the version it is actually building # (`make VERSION=$pkgver`). The literal below is the fallback for builds # straight from a checkout, and is what a release tag has to carry. -VERSION ?= 1.0.0 +VERSION ?= 1.0.1 PREFIX ?= /usr DESTDIR ?= diff --git a/doc/middleclick-autoscroll.1.scd b/doc/middleclick-autoscroll.1.scd index 618c3dd..6a78410 100644 --- a/doc/middleclick-autoscroll.1.scd +++ b/doc/middleclick-autoscroll.1.scd @@ -72,9 +72,12 @@ Two ways, chosen per application. Flatpaks, a copy of the desktop entry with the argument appended is written to _~/.local/share/applications_, where it shadows the system one. Entries that already live there - AppImages, web app shortcuts - are edited in - place, with the original kept. Every generated entry is marked with - *X-MCA-Generated*; an entry that is not marked is somebody else's and is - left alone. + place, with the original kept. + + 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 _~/.config/autostart_ pointing straight at their binary, bypassing the menu diff --git a/po/de.po b/po/de.po index a6c1228..5acbe6c 100644 --- a/po/de.po +++ b/po/de.po @@ -4,7 +4,7 @@ # msgid "" 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" "POT-Creation-Date: 2026-08-16 00:00+0200\n" "PO-Revision-Date: 2026-08-16 00:00+0200\n" diff --git a/po/middleclick-autoscroll.pot b/po/middleclick-autoscroll.pot index bcb94d5..51944a6 100644 --- a/po/middleclick-autoscroll.pot +++ b/po/middleclick-autoscroll.pot @@ -4,7 +4,7 @@ # msgid "" 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" "POT-Creation-Date: 2026-08-16 00:00+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/src/lib/apply.sh b/src/lib/apply.sh index 5542595..8ecea29 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -8,7 +8,6 @@ # of the behaviour, it is the behaviour. MCA_CHANGES=0 -MCA_TOUCHED=() MCA_ROUTES=() # mca_route @@ -63,7 +62,6 @@ mca_apply() { local i id file prog kind route steam_done=0 MCA_CHANGES=0 - MCA_TOUCHED=() MCA_ROUTES=() MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0 @@ -117,7 +115,6 @@ mca_apply() { fi mca_state_write last_apply "$(date +%s)" - mca_state_write covered "${#MCA_TOUCHED[@]}" return 0 } @@ -131,7 +128,6 @@ mca_revert() { update-desktop-database "$MCA_APPDIR" 2>/dev/null || true fi - mca_state_write covered 0 return 0 } diff --git a/src/lib/common.sh b/src/lib/common.sh index 292cc78..afcabfb 100644 --- a/src/lib/common.sh +++ b/src/lib/common.sh @@ -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_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_debug() { [[ -n ${MCA_DEBUG:-} ]] && printf 'debug: %s\n' "$*" >&2; return 0; } mca_have() { command -v "$1" > /dev/null 2>&1; } @@ -239,22 +238,6 @@ mca_ledger_forget() { return 0 } -# mca_ledger_has -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 -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 # Copies a file aside before it is edited in place, and prints the name the diff --git a/src/lib/detect.sh b/src/lib/detect.sh index 5fd5d58..a27ec17 100644 --- a/src/lib/detect.sh +++ b/src/lib/detect.sh @@ -97,6 +97,9 @@ _mca_desktop_read() { Hidden=*) DE_HIDDEN="${line#Hidden=}" ;; Categories=*) DE_CATEGORIES="${line#Categories=}" ;; 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 ;; esac done < "$file" @@ -399,8 +402,8 @@ MCA_PROGS=() # resolved program, or a flatpak app id MCA_KINDS=() # app | browser | flatpak | steam | unknown | no # 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, -# reverting - invalidates it explicitly. +# then redraws from what it found. Applying rescans on its own, so nothing else +# has to remember to invalidate this. MCA_SCANNED=0 mca_scan_once() { @@ -408,8 +411,6 @@ mca_scan_once() { mca_scan } -mca_scan_invalidate() { MCA_SCANNED=0; } - mca_scan() { local dir file id name exec_line prog kind i local -A seen=() diff --git a/src/lib/menu.sh b/src/lib/menu.sh index 94c0906..5e39aaf 100644 --- a/src/lib/menu.sh +++ b/src/lib/menu.sh @@ -371,12 +371,32 @@ mca_ui_apps() { 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" "Up/Down select - Space turns one on or off - q goes back" hint="$MCA_MSG_RESULT" mca_msg_into "$locale" "Anything not identified is left alone until it is turned on here." 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 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 for i in "${!labels[@]}"; do case "${states[i]}" in - off) shown="${MCA_C_DIM}$(mca_msg "off")${MCA_C_RESET}" ;; - unknown) shown="${MCA_C_DIM}$(mca_msg "cannot tell")${MCA_C_RESET}" ;; - steam) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "Steam"))${MCA_C_RESET}" ;; - flags) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "flag file"))${MCA_C_RESET}" ;; - *) shown="${MCA_C_GREEN}$(mca_msg "on")${MCA_C_RESET} ${MCA_C_DIM}($(mca_msg "launcher"))${MCA_C_RESET}" ;; + off) shown="$s_off" ;; + unknown) shown="$s_cannot" ;; + steam) shown="$s_steam" ;; + flags) shown="$s_flags" ;; + *) shown="$s_desktop" ;; esac pad=$(( 34 - ${#labels[i]} )) (( 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 # it is not while the whole thing is off. 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 frame+=" ${MCA_C_DIM}${legend}${MCA_C_RESET}"$'\n' frame+=" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n' diff --git a/src/lib/patch.sh b/src/lib/patch.sh index 76275d3..d510a95 100644 --- a/src/lib/patch.sh +++ b/src/lib/patch.sh @@ -195,9 +195,6 @@ _mca_flags_write() { mca_ledger_add flags "$file" block 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 } @@ -255,12 +252,20 @@ mca_flags_revert() { # Desktop entries # --------------------------------------------------------------------------- -# _mca_desktop_transform [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 [flags] [position] # 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 # user who starts Steam from "Library" expects the same behaviour there. _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 while IFS= read -r line || [[ -n $line ]]; do @@ -283,7 +288,7 @@ _mca_desktop_transform() { printf '%s\n' "$line" 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 fi done < "$file" @@ -300,9 +305,9 @@ mca_desktop_apply() { # 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 # 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 [[ "$content" == "$(< "$src")" ]] && return 0 @@ -311,18 +316,18 @@ mca_desktop_apply() { MCA_CHANGES=$(( MCA_CHANGES + 1 )) fi mca_ledger_add inplace "$src" "$backup" - MCA_TOUCHED+=("$src") return 0 fi - content="$(_mca_desktop_transform "$src" "$flags" "$where")" + content="$(_mca_desktop_transform "$src" "$MCA_MARK_SHADOW" "$flags" "$where")" [[ -n $content ]] || 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 - # user's own override and is left exactly as it is. - if [[ -e $target ]] && ! grep -q '^X-MCA-Generated=' "$target" 2>/dev/null; then + # Something is already shadowing this entry. Unless it is a shadow of ours, + # it is the user's own file - possibly one we edited in place earlier - and + # overwriting it here would lose it. + if [[ -e $target ]] && ! grep -q "^$MCA_MARK_SHADOW=" "$target" 2>/dev/null; then return 1 fi @@ -330,7 +335,6 @@ mca_desktop_apply() { MCA_CHANGES=$(( MCA_CHANGES + 1 )) fi mca_ledger_add shadow "$target" "$src" - MCA_TOUCHED+=("$target") return 0 } @@ -349,7 +353,7 @@ mca_autostart_apply() { for file in "$dir"/*.desktop; do [[ -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)" [[ -n $exec_line ]] || continue @@ -358,7 +362,7 @@ mca_autostart_apply() { prog="$MCA_PROG" mca_is_chromium "$prog" || continue - content="$(_mca_desktop_transform "$file")" + content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")" [[ -n $content ]] || continue [[ "$content" == "$(< "$file")" ]] && continue @@ -367,7 +371,6 @@ mca_autostart_apply() { MCA_CHANGES=$(( MCA_CHANGES + 1 )) fi mca_ledger_add inplace "$file" "$backup" - MCA_TOUCHED+=("$file") done } @@ -412,7 +415,6 @@ mca_spotify_apply() { MCA_CHANGES=$(( MCA_CHANGES + 1 )) fi mca_ledger_add inplace "$file" "${backup:-}" - MCA_TOUCHED+=("$file") return 0 } @@ -431,7 +433,7 @@ mca_revert_all() { case "$kind" in shadow) # 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" MCA_CHANGES=$(( MCA_CHANGES + 1 )) fi diff --git a/src/lib/steam.sh b/src/lib/steam.sh index e6d8fbb..09c31f7 100644 --- a/src/lib/steam.sh +++ b/src/lib/steam.sh @@ -18,7 +18,6 @@ # The file comes back on every client update, and the watcher re-applies the # patch when that happens. -MCA_STEAM_EXEC='exec ./steamwebhelper "$@"' MCA_STEAM_LAUNCH_FLAG='-noverifyfiles' # 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 found=1 - mca_steam_script_patched "$script" && { MCA_TOUCHED+=("$script"); continue; } + mca_steam_script_patched "$script" && continue lineno="$(_mca_steam_exec_line "$script")" if [[ -z $lineno ]]; then @@ -125,7 +124,6 @@ mca_steam_apply() { fi chmod +x -- "$script" 2>/dev/null || true mca_ledger_add steam "$script" "$backup" - MCA_TOUCHED+=("$script") done < <(mca_steam_roots) (( found ))