From 82afefe5f897fc5a57ddc25d73b04dc30e8d81a9 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Thu, 24 Sep 2026 10:44:52 +0200 Subject: [PATCH] feat: rework the settings --- doc/middleclick-autoscroll.1.scd | 11 +++++-- po/de.po | 39 ++++++++++------------ po/middleclick-autoscroll.pot | 24 +++++--------- src/lib/apply.sh | 39 +++++++++++++++------- src/lib/config.sh | 55 ++++++++++++++++++++++++++++---- src/lib/detect.sh | 31 +++++++----------- src/lib/menu.sh | 44 +++++++++++++------------ src/lib/patch.sh | 24 +++++--------- 8 files changed, 151 insertions(+), 116 deletions(-) diff --git a/doc/middleclick-autoscroll.1.scd b/doc/middleclick-autoscroll.1.scd index 43388c7..d144cd4 100644 --- a/doc/middleclick-autoscroll.1.scd +++ b/doc/middleclick-autoscroll.1.scd @@ -36,7 +36,10 @@ assumed from where the launcher came from. 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 -edited by hand. +edited by hand. Its applications list turns single applications on or off, +Steam and Spotify included. Its settings hold the rest: the desktop's +middle-click paste, the watcher, and more Chromium arguments for every +application. Nothing outside the user's home directory is written to, and running it as root is refused. @@ -112,7 +115,8 @@ Two ways, chosen per application. Programs that start themselves at login write their own entry into _~/.config/autostart_ pointing straight at their binary, bypassing the menu -entry entirely. Those are patched in place as well. +entry entirely. Those are patched in place as well, unless the application is +turned off in the applications list. Shortcuts on the desktop itself are patched in place too. Nothing in the XDG search path looks at that folder, so a shortcut that lives only there would @@ -170,7 +174,8 @@ rather than the one from before the update. The official client is CEF rather than Electron. Installed through *spotify-launcher*, it is started by a program that builds its own command line and has a configuration file with a slot for extra arguments; that slot is -where the flag goes. Installed as a plain package, a Flatpak or a snap, it is +where the flag goes, 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 an ordinary desktop entry and needs nothing special. # MIDDLE-CLICK PASTE diff --git a/po/de.po b/po/de.po index 79fd010..835f05e 100644 --- a/po/de.po +++ b/po/de.po @@ -95,35 +95,28 @@ msgid "Press any key to continue..." msgstr "Beliebige Taste drücken …" #. Settings -msgid "Electron and CEF applications" -msgstr "Electron- und CEF-Anwendungen" +msgid "Turn off middle-click paste" +msgstr "Mittelklick-Einfügen abschalten" -msgid "Chromium-based browsers" -msgstr "Chromium-basierte Browser" +msgid "" +"Stops middle click from pasting anywhere else. Starts with the next login." +msgstr "" +"Mittelklick fügt auch sonst nirgends mehr ein. Ab der nächsten Anmeldung." -msgid "Flatpak applications" -msgstr "Flatpak-Anwendungen" +msgid "Cover new applications" +msgstr "Neue Anwendungen abdecken" -msgid "Snap applications" -msgstr "Snap-Anwendungen" +msgid "Applications installed later get autoscroll on their own." +msgstr "Später installierte Anwendungen bekommen Autoscroll von selbst." -msgid "Programs that start themselves at login" -msgstr "Programme, die sich beim Anmelden selbst starten" +msgid "More Chromium arguments" +msgstr "Weitere Chromium-Argumente" -msgid "Spotify" -msgstr "Spotify" +msgid "Passed to every application along with the autoscroll one." +msgstr "Gehen zusammen mit dem Autoscroll-Argument an jede Anwendung." -msgid "Turn off KDE's middle-click paste" -msgstr "KDEs Mittelklick-Einfügen abschalten" - -msgid "Apply to newly installed applications" -msgstr "Auf neu installierte Anwendungen anwenden" - -msgid "Additional Chromium arguments" -msgstr "Zusätzliche Chromium-Argumente" - -msgid "Up/Down: select, Space or Right: change, q: back" -msgstr "Hoch/Runter: wählen, Leertaste oder Rechts: ändern, q: zurück" +msgid "Up/Down: select, Space: change, q: back" +msgstr "Hoch/Runter: wählen, Leertaste: ändern, q: zurück" msgid "Arguments separated by spaces, empty for none:" msgstr "Argumente mit Leerzeichen getrennt, leer für keine:" diff --git a/po/middleclick-autoscroll.pot b/po/middleclick-autoscroll.pot index bfadf59..dc69e47 100644 --- a/po/middleclick-autoscroll.pot +++ b/po/middleclick-autoscroll.pot @@ -95,34 +95,26 @@ msgid "Press any key to continue..." msgstr "" #. Settings -msgid "Electron and CEF applications" +msgid "Turn off middle-click paste" msgstr "" -msgid "Chromium-based browsers" +msgid "" +"Stops middle click from pasting anywhere else. Starts with the next login." msgstr "" -msgid "Flatpak applications" +msgid "Cover new applications" msgstr "" -msgid "Snap applications" +msgid "Applications installed later get autoscroll on their own." msgstr "" -msgid "Programs that start themselves at login" +msgid "More Chromium arguments" msgstr "" -msgid "Spotify" +msgid "Passed to every application along with the autoscroll one." msgstr "" -msgid "Turn off KDE's middle-click paste" -msgstr "" - -msgid "Apply to newly installed applications" -msgstr "" - -msgid "Additional Chromium arguments" -msgstr "" - -msgid "Up/Down: select, Space or Right: change, q: back" +msgid "Up/Down: select, Space: change, q: back" msgstr "" msgid "Arguments separated by spaces, empty for none:" diff --git a/src/lib/apply.sh b/src/lib/apply.sh index b15d964..ded3570 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -15,6 +15,7 @@ MCA_ROUTES=() # flags - the launcher reads a flag file; write it there # 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 # unknown - cannot tell what engine this is (an AppImage), so nothing is done # off - detected, but switched off # @@ -26,16 +27,16 @@ MCA_ROUTE='' mca_route() { local kind="$1" id="$2" prog="$3" packaging="${4:-native}" - if [[ $kind == steam ]]; then - if mca_config_list_has Skip "$id" || [[ $CFG_STEAM != yes ]]; then + if [[ $kind == steam || $kind == spotify ]]; then + if mca_config_list_has Skip "$id"; then MCA_ROUTE=off else - MCA_ROUTE=steam + MCA_ROUTE="$kind" fi return fi - if ! mca_kind_wanted "$kind" "$id" "$packaging"; then + if ! mca_kind_wanted "$kind" "$id"; then [[ $kind == unknown ]] && MCA_ROUTE=unknown || MCA_ROUTE=off return fi @@ -59,7 +60,7 @@ mca_route() { # as it likes to be. It writes only what differs, which is what keeps the # watcher from chasing its own changes. mca_apply() { - local i id file prog kind packaging route steam_done=0 + local i id file prog kind packaging route steam_done=0 spotify_done=0 MCA_CHANGES=0 MCA_ROUTES=() @@ -111,29 +112,29 @@ mca_apply() { steam_done=1 fi ;; + spotify) + (( spotify_done )) || mca_spotify_apply + spotify_done=1 + ;; esac done # Steam is worth patching even when its desktop entry is missing. A user # who starts it from a script or a game launcher still gets the interface. - if [[ $CFG_STEAM == yes ]] && (( ! steam_done )) && mca_steam_installed; then + if (( ! steam_done )) && mca_steam_wanted && mca_steam_installed; then mca_steam_apply fi # Autostart entries. A Chromium application that starts itself at login # points straight at its binary and never reads the entry in the menu, so # Discord at login used to behave differently from Discord from the menu. - if [[ $CFG_AUTOSTART == yes ]]; then - mca_autostart_apply - fi + mca_autostart_apply # Shortcuts on the desktop itself, which nothing above has seen: the XDG # search path does not go there. Each entry is gated on its own, so there is # nothing to check out here. mca_shortcuts_apply - [[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply - # Not about any one application: middle click goes on pasting everywhere # else on the desktop, and KDE is the one desktop that can be told not to. # See kde.sh. @@ -272,12 +273,28 @@ mca_count_routes() { done } +# mca_steam_wanted +# Whether Steam gets patched, from the routes of the last scan: yes unless it +# was turned off in the applications list. Also yes when it has no entry there +# at all, so there is nothing to turn off. +mca_steam_wanted() { + local i seen=0 + + for i in "${!MCA_KINDS[@]}"; do + [[ ${MCA_KINDS[i]} == steam ]] || continue + [[ ${MCA_ROUTES[i]} == steam ]] && return 0 + seen=1 + done + (( ! seen )) +} + # mca_route_label mca_route_label() { case "$1" in flags) mca_msg "flag file" ;; desktop) mca_msg "launcher" ;; steam) mca_msg "Steam" ;; + spotify) printf 'spotify-launcher' ;; unknown) mca_msg "cannot tell" ;; *) mca_msg "off" ;; esac diff --git a/src/lib/config.sh b/src/lib/config.sh index 91b4dec..c376f0c 100644 --- a/src/lib/config.sh +++ b/src/lib/config.sh @@ -101,6 +101,20 @@ mca_config_set() { MCA_CONFIG_CACHED=0 } +# mca_config_del +mca_config_del() { + local key="$1" tmp + + [[ -w $MCA_CONFIG ]] || return 1 + grep -qE "^[[:space:]]*${key}[[:space:]]*=" "$MCA_CONFIG" || return 0 + + tmp="$(mktemp "${MCA_CONFIG}.XXXXXX")" || return 1 + chmod --reference="$MCA_CONFIG" "$tmp" 2>/dev/null || chmod 0644 "$tmp" + grep -vE "^[[:space:]]*${key}[[:space:]]*=" "$MCA_CONFIG" > "$tmp" + mv -f "$tmp" "$MCA_CONFIG" + MCA_CONFIG_CACHED=0 +} + # mca_config_list_has # The per-application overrides are stored as space separated lists. mca_config_list_has() { @@ -136,13 +150,6 @@ mca_config_list_del() { mca_config_load() { CFG_ENABLED=no; mca_config_bool Enabled no && CFG_ENABLED=yes - CFG_APPS=no; mca_config_bool PatchApps yes && CFG_APPS=yes - CFG_BROWSERS=no; mca_config_bool PatchBrowsers yes && CFG_BROWSERS=yes - CFG_FLATPAK=no; mca_config_bool PatchFlatpak yes && CFG_FLATPAK=yes - CFG_SNAP=no; mca_config_bool PatchSnap yes && CFG_SNAP=yes - CFG_AUTOSTART=no; mca_config_bool PatchAutostart yes && CFG_AUTOSTART=yes - CFG_STEAM=no; mca_config_bool PatchSteam yes && CFG_STEAM=yes - CFG_SPOTIFY=no; mca_config_bool PatchSpotify yes && CFG_SPOTIFY=yes CFG_WATCH=no; mca_config_bool WatchNewApps yes && CFG_WATCH=yes CFG_PASTE=no; mca_config_bool DisablePaste yes && CFG_PASTE=yes @@ -155,6 +162,40 @@ mca_config_load() { CFG_SKIP="$(mca_config_get Skip '')" } +# mca_config_migrate +# Up to 1.5 the settings had a switch per group: applications, browsers, +# Flatpak, snap, programs at login, Steam and Spotify. The applications list +# does that job now, one application at a time. So a group that was switched +# off turns into a Skip for each application in it, once, and the old keys go. +# Called at the end of a scan, because the groups come from there. +mca_config_migrate() { + local i key off='' group pack + + _mca_config_slurp + [[ $MCA_CONFIG_CACHE == *Patch[A-Z]*=* ]] || return 0 + + for key in Apps Browsers Flatpak Snap Steam Spotify; do + mca_config_bool "Patch$key" yes || off+=" $key " + done + + if [[ -n $off ]]; then + for i in "${!MCA_IDS[@]}"; do + mca_config_list_has Include "${MCA_IDS[i]}" && continue + group="${MCA_KINDS[i]^}" + [[ $group == App || $group == Browser ]] && group+=s + pack="${MCA_PACKAGING[i]^}" + if [[ $off == *" $group "* || $off == *" $pack "* ]]; then + mca_config_list_add Skip "${MCA_IDS[i]}" + fi + done + fi + + for key in Apps Browsers Flatpak Snap Autostart Steam Spotify; do + mca_config_del "Patch$key" + done + mca_config_load +} + # mca_flags [kind] # The full argument string that gets injected. Kept in one place so the flag # file writer and the desktop entry writer cannot drift apart. diff --git a/src/lib/detect.sh b/src/lib/detect.sh index a509aad..ab0493a 100644 --- a/src/lib/detect.sh +++ b/src/lib/detect.sh @@ -989,7 +989,7 @@ 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 | unknown | no +MCA_KINDS=() # app | browser | steam | spotify | unknown | no MCA_PACKAGING=() # native | flatpak | snap # A scan reads every desktop entry on the system, so the menu does it once and @@ -1094,6 +1094,10 @@ mca_scan() { # Steam is Chromium inside, but nothing about it can be changed # from a command line argument; it has its own module. kind=steam + elif [[ ${prog##*/} == spotify-launcher ]]; then + # Not Chromium itself, but it starts Spotify, which is CEF. It + # takes the flag from its own configuration file. + kind=spotify else mca_detect_verdict "$prog" case "$MCA_VERDICT" in @@ -1114,6 +1118,7 @@ mca_scan() { mca_cache_flush MCA_SCANNED=1 + mca_config_migrate } # mca_has_flags_file @@ -1224,28 +1229,14 @@ mca_flatpak_is_chromium() { return 1 } -# mca_kind_wanted [packaging] -# Whether the current settings say this entry should be patched. Skip beats -# everything, an explicit include beats detection, and detection beats nothing. -# -# Packaging is a gate in front of the category rather than a category of its -# own: a Flatpak or a snap sees none of the host's configuration and is worth -# switching off as a group, but it is still an application or a browser and -# whichever of those the user turned off applies to it too. +# mca_kind_wanted +# Whether this entry should be patched. Skip beats everything, an explicit +# include beats detection, and detection beats nothing. mca_kind_wanted() { - local kind="$1" id="$2" packaging="${3:-native}" + local kind="$1" id="$2" mca_config_list_has Skip "$id" && return 1 mca_config_list_has Include "$id" && return 0 - case "$packaging" in - flatpak) [[ $CFG_FLATPAK == yes ]] || return 1 ;; - snap) [[ $CFG_SNAP == yes ]] || return 1 ;; - esac - - case "$kind" in - app) [[ $CFG_APPS == yes ]] ;; - browser) [[ $CFG_BROWSERS == yes ]] ;; - *) return 1 ;; - esac + [[ $kind == app || $kind == browser ]] } diff --git a/src/lib/menu.sh b/src/lib/menu.sh index d7f8492..e2863b4 100644 --- a/src/lib/menu.sh +++ b/src/lib/menu.sh @@ -178,7 +178,7 @@ mca_ui_status() { if mca_steam_installed; then if mca_steam_patched; then _mca_row "$(mca_msg "Steam")" "$(_mca_onoff yes)" - elif [[ $CFG_STEAM == yes && $CFG_ENABLED == yes ]]; then + elif [[ $CFG_ENABLED == yes ]] && mca_steam_wanted; then _mca_row "$(mca_msg "Steam")" \ "${MCA_C_YELLOW}$(mca_msg "not patched yet")${MCA_C_RESET}" else @@ -218,19 +218,16 @@ mca_ui_status() { # --------------------------------------------------------------------------- # Settings # --------------------------------------------------------------------------- -# Format: Key|type|default|label-msgid -# type is bool or text. +# Only what is not about one application. Which applications get autoscroll is +# decided in the applications list. +# +# Format: Key|type|default|check|label-msgid|help-msgid +# type is bool or text. The row is only shown when the check passes: a switch +# for something this system does not have is only noise. MCA_SETTINGS=( - "PatchApps|bool|yes|Electron and CEF applications" - "PatchBrowsers|bool|yes|Chromium-based browsers" - "PatchFlatpak|bool|yes|Flatpak applications" - "PatchSnap|bool|yes|Snap applications" - "PatchAutostart|bool|yes|Programs that start themselves at login" - "PatchSteam|bool|yes|Steam" - "PatchSpotify|bool|yes|Spotify" - "DisablePaste|bool|yes|Turn off KDE's middle-click paste" - "WatchNewApps|bool|yes|Apply to newly installed applications" - "ExtraFlags|text||Additional Chromium arguments" + "DisablePaste|bool|yes|mca_kde_available|Turn off middle-click paste|Stops middle click from pasting anywhere else. Starts with the next login." + "WatchNewApps|bool|yes|mca_watch_available|Cover new applications|Applications installed later get autoscroll on their own." + "ExtraFlags|text|||More Chromium arguments|Passed to every application along with the autoscroll one." ) _mca_is_true() { @@ -275,10 +272,9 @@ _mca_setting_display() { # # Returns 0 when something was changed, so the caller knows to re-apply. mca_ui_settings() { - local count=${#MCA_SETTINGS[@]} - local -a names=() types=() defaults=() labels=() values=() - local spec name type default label locale i key frame row pad dirty=1 cursor=0 - local touched=0 + local -a names=() types=() defaults=() labels=() helps=() values=() + local spec name type default check label help locale i key frame row pad + local count width=0 dirty=1 cursor=0 touched=0 locale="$(mca_ui_locale)" mca_msg_into "$locale" "ON"; MCA_LBL_ON="$MCA_MSG_RESULT" @@ -286,15 +282,20 @@ mca_ui_settings() { mca_msg_into "$locale" "(none)"; MCA_LBL_NONE="$MCA_MSG_RESULT" for spec in "${MCA_SETTINGS[@]}"; do - IFS='|' read -r name type default label <<< "$spec" + IFS='|' read -r name type default check label help <<< "$spec" + [[ -z $check ]] || "$check" || continue names+=("$name"); types+=("$type"); defaults+=("$default") mca_msg_into "$locale" "$label" labels+=("$MCA_MSG_RESULT") + (( ${#MCA_MSG_RESULT} > width )) && width=${#MCA_MSG_RESULT} + mca_msg_into "$locale" "$help" + helps+=("$MCA_MSG_RESULT") done + count=${#names[@]} local title hint mca_msg_into "$locale" "Settings"; title="$MCA_MSG_RESULT" - mca_msg_into "$locale" "Up/Down: select, Space or Right: change, q: back" + mca_msg_into "$locale" "Up/Down: select, Space: change, q: back" hint="$MCA_MSG_RESULT" local clearseq @@ -314,7 +315,7 @@ mca_ui_settings() { local marker selected="${MCA_C_BLUE}▸${MCA_C_RESET} " for i in "${!names[@]}"; do _mca_setting_display "${types[i]}" "${values[i]}" - pad=$(( 46 - ${#labels[i]} )) + pad=$(( width + 3 - ${#labels[i]} )) (( pad < 0 )) && pad=0 if (( i == cursor )); then marker="$selected"; else marker=' '; fi printf -v row ' %s%s%*s %s' \ @@ -322,6 +323,7 @@ mca_ui_settings() { frame+="$row"$'\n' done + frame+=$'\n'" ${helps[cursor]}"$'\n' frame+=$'\n'" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n' printf '%s' "$frame" @@ -419,6 +421,7 @@ mca_ui_apps() { 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 s_spotify="${s_on} ${MCA_C_DIM}(spotify-launcher)${MCA_C_RESET}" local clearseq clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' @@ -467,6 +470,7 @@ mca_ui_apps() { unknown) shown="$s_cannot" ;; steam) shown="$s_steam" ;; flags) shown="$s_flags" ;; + spotify) shown="$s_spotify" ;; *) shown="$s_desktop" ;; esac pad=$(( 34 - ${#labels[idx]} )) diff --git a/src/lib/patch.sh b/src/lib/patch.sh index b3a2997..b2cbf7a 100644 --- a/src/lib/patch.sh +++ b/src/lib/patch.sh @@ -418,16 +418,12 @@ mca_desktop_apply() { # Neither can be shadowed from anywhere, so both are edited where they stand, # with the original kept. -# _mca_entry_patch_inplace +# _mca_entry_patch_inplace # One desktop entry that lives outside the XDG search path, edited where it is -# because there is nowhere to shadow it from. -# -# What decides whether an application here is in scope differs by where the -# entry came from, which is what the gate says: an autostart entry follows the -# autostart setting, a shortcut follows the same rules as the application it is -# a shortcut to. +# because there is nowhere to shadow it from. It follows the applications list +# under its own file name, which is the name of the application's own entry. _mca_entry_patch_inplace() { - local file="$1" gate="$2" + local file="$1" local id prog packaging=native content backup kind copy # Already ours, and normally that is the end of it: what is in there is what @@ -476,12 +472,8 @@ _mca_entry_patch_inplace() { mca_desktop_is_browser && kind=browser || kind=app - if [[ $gate == autostart ]]; then - [[ $CFG_AUTOSTART == yes ]] || return 0 - else - id="${file##*/}"; id="${id%.desktop}" - mca_kind_wanted "$kind" "$id" "$packaging" || return 0 - fi + id="${file##*/}"; id="${id%.desktop}" + mca_kind_wanted "$kind" "$id" || return 0 content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \ "$(mca_flags "$kind")")" @@ -503,7 +495,7 @@ mca_autostart_apply() { for file in "$dir"/*.desktop; do [[ -f $file ]] || continue - _mca_entry_patch_inplace "$file" autostart + _mca_entry_patch_inplace "$file" done } @@ -515,7 +507,7 @@ mca_shortcuts_apply() { for file in "$dir"/*.desktop; do [[ -f $file ]] || continue - _mca_entry_patch_inplace "$file" apps + _mca_entry_patch_inplace "$file" done }