diff --git a/README.md b/README.md index edacafc..f415ec3 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,19 @@ flag turns it on: But doing that for every app is kinda bothersome and it also might break with updates. That's why I created this small tool to automate that. +Browsers get the same thing asked for differently: + +--enable-features=MiddleClickAutoscroll + +Both mean the same feature - Blink generates a matching feature name for each of +its runtime flags - but the first one is on Chromium's list of flags worth +warning about, so a browser started with it shows a yellow "unsupported +command-line flag" bar over every page. The second one is not on that list, so +there's no bar. It only works from Chromium 124 on, which is why apps that +embed something older (Steam's CEF, older Electron) keep the first one - they +have no such bar to begin with. Helium, which carries the feature under its own +name, is asked for `HeliumMiddleClickAutoscroll` alongside it. + New apps are picked up by a systemd path unit that watches the relevant directories. Without systemd, `middleclick-autoscroll apply` does the same thing manually. diff --git a/doc/middleclick-autoscroll.1.scd b/doc/middleclick-autoscroll.1.scd index 736aaa2..5ca85f0 100644 --- a/doc/middleclick-autoscroll.1.scd +++ b/doc/middleclick-autoscroll.1.scd @@ -18,7 +18,18 @@ on the command line. *middleclick-autoscroll* finds every Chromium-based application on the system and puts that argument somewhere the application will actually read it, then -keeps doing so for anything installed later. It works on any distribution: +keeps doing so for anything installed later. + +A browser is given *--enable-features=MiddleClickAutoscroll* instead. It asks +for the same feature - Blink generates a feature of that name for each of its +runtime flags - but it is not on the list of flags Chromium warns about, so the +browser does not put a bar reading "You are using an unsupported command-line +flag" above every page. That spelling only works from Chromium 124 onwards, +which is why everything else keeps the first one: an application that embeds an +older Chromium, such as Steam's CEF, has no such bar to show anyway. Helium +knows the feature under a name of its own and is asked for +*HeliumMiddleClickAutoscroll* as well; 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 assumed from where the launcher came from. @@ -71,10 +82,10 @@ Two ways, chosen per application. Where the launcher reads extra arguments from _$XDG_CONFIG_HOME/-flags.conf_. This is the preferred route: it is the supported way to pass arguments, it survives package upgrades, and it - applies to a launch from a terminal as much as one from the menu. An - *--enable-blink-features* line that is already in the file is extended - rather than duplicated - Chromium keeps only the last occurrence of that - option, so a second one would switch the first one off. + applies to a launch from a terminal as much as one from the menu. A + feature list that is already in the file is extended rather than + duplicated - Chromium keeps only the last occurrence of such an option, so + a second one would switch the first one off. Arch's Electron and Chromium packages all wrap their binaries this way, and so do a number of individual vendors' launchers elsewhere. Whether a given diff --git a/src/lib/apply.sh b/src/lib/apply.sh index 6d1a59c..91e78c1 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -66,6 +66,15 @@ mca_apply() { MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0 mca_config_load + + # An installation that was set up by a version whose answer was a different + # flag still has that flag where it put it, and nothing below would touch it + # again: a file that is already marked as patched is left alone. Taking + # everything back once, here, is what carries such an installation over. + if [[ "$(mca_state_read flag_scheme 1)" != "$MCA_FLAG_SCHEME" ]]; then + mca_revert_all + fi + mca_scan for i in "${!MCA_IDS[@]}"; do @@ -87,10 +96,11 @@ mca_apply() { case "$route" in flags) - mca_flags_apply "$prog" || mca_desktop_apply "$id" "$file" + mca_flags_apply "$prog" "$kind" \ + || mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")" ;; desktop) - mca_desktop_apply "$id" "$file" + mca_desktop_apply "$id" "$file" "$(mca_flags "$kind")" ;; steam) mca_steam_desktop_apply "$id" "$file" "$packaging" @@ -143,6 +153,7 @@ mca_apply() { fi mca_state_write last_apply "$(date +%s)" + mca_state_write flag_scheme "$MCA_FLAG_SCHEME" return 0 } diff --git a/src/lib/common.sh b/src/lib/common.sh index 9ae81bf..63887c3 100644 --- a/src/lib/common.sh +++ b/src/lib/common.sh @@ -13,11 +13,42 @@ MCA_PRETTY="Middle-Click Autoscroll" # The whole point of the package. Blink implements Windows-style autoscroll # behind a runtime flag that Chromium does not turn on for Linux, because # middle click is taken by primary-selection paste there. +# +# There are two ways to ask for that flag, and which one is right depends on +# what is being started: +# +# --enable-blink-features=MiddleClickAutoscroll is the one that works +# everywhere, and the only one that works in the Chromium versions embedded +# applications ship - Steam's CEF, an Electron a few years old, anything +# before Chromium 124. A browser started with it puts a yellow bar above the +# page saying an unsupported command line flag is in use, because Chromium +# keeps a list of flags worth warning about and this is on it. +# +# --enable-features=MiddleClickAutoscroll is not on that list, so a browser +# started with it says nothing. Blink generates a feature of the same name +# for every one of its runtime flags, which is what makes the two the same +# request - but only since Chromium 124. Before that the name means nothing +# and is ignored without a word. +# +# So a browser, which is the only thing that shows the bar and the one thing +# that keeps itself up to date, is given the quiet one; everything else keeps +# the one that always works. MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll" -# The Blink feature name on its own, for merging into an --enable-blink-features -# list that an app (or the user) already carries. +# Helium ships the feature under its own name and does not answer to the +# Chromium one, so browsers are asked for both. A name a browser does not know +# is ignored, which is what makes one list safe to hand to all of them. +MCA_BROWSER_FLAG="--enable-features=MiddleClickAutoscroll,HeliumMiddleClickAutoscroll" + +# The feature names on their own, for merging into a list that an application +# (or the user) already carries, and for taking them back out again. MCA_FEATURE="MiddleClickAutoscroll" +MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll" + +# Bumped when the flags above change. An installation that was set up by a +# version with a different answer is taken back and done again once, because a +# file that is already patched is otherwise left alone. See mca_apply. +MCA_FLAG_SCHEME=2 MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}" MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}" diff --git a/src/lib/config.sh b/src/lib/config.sh index a67d41d..9d1213b 100644 --- a/src/lib/config.sh +++ b/src/lib/config.sh @@ -154,10 +154,18 @@ mca_config_load() { CFG_SKIP="$(mca_config_get Skip '')" } +# 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. +# +# A browser gets the flag that leaves it without a warning bar; everything else +# gets the one that works in every Chromium there is. See MCA_FLAG. mca_flags() { - printf '%s' "$MCA_FLAG" + if [[ ${1:-app} == browser ]]; then + printf '%s' "$MCA_BROWSER_FLAG" + else + printf '%s' "$MCA_FLAG" + fi [[ -n ${CFG_EXTRA_FLAGS:-} ]] && printf ' %s' "$CFG_EXTRA_FLAGS" printf '\n' } diff --git a/src/lib/patch.sh b/src/lib/patch.sh index 172f76a..3a01e42 100644 --- a/src/lib/patch.sh +++ b/src/lib/patch.sh @@ -29,6 +29,40 @@ MCA_MARK_BEGIN='# >>> middleclick-autoscroll' MCA_MARK_END='# <<< middleclick-autoscroll' +# --------------------------------------------------------------------------- +# Feature lists +# --------------------------------------------------------------------------- +# Both --enable-features and --enable-blink-features take a comma separated +# list, and Chromium keeps the last one it is given and drops the rest. A +# second one of the same kind would therefore silently switch off whatever the +# first one turned on, so an option that is already there is extended instead. + +# _mca_list_merge +# The two lists as one, in the order they came and without repeating anything. +_mca_list_merge() { + local out="$1" item + for item in ${2//,/ }; do + [[ ",$out," == *",$item,"* ]] && continue + out="${out:+$out,}$item" + done + printf '%s' "$out" +} + +# _mca_list_strip +# The list without any of the named entries. +_mca_list_strip() { + local out='' item + for item in ${1//,/ }; do + [[ ",$2," == *",$item,"* ]] && continue + out="${out:+$out,}$item" + done + printf '%s' "$out" +} + +# The two options, as a regular expression branch, for the places that have to +# recognise either of them. +MCA_FEATURE_OPTS='--enable-blink-features|--enable-features' + # --------------------------------------------------------------------------- # Command lines # --------------------------------------------------------------------------- @@ -70,28 +104,32 @@ _mca_exec_after_program() { # Prints nothing and returns 1 when there was nothing to change, so callers can # tell a real edit from a no-op without diffing. # -# An --enable-blink-features that is already there is extended rather than -# repeated: Chromium keeps the last occurrence of the option and drops the -# rest, so a second one would silently disable whatever the first turned on. +# A feature list that is already on the line is extended rather than repeated. +# See _mca_list_merge. mca_exec_inject() { local line="$1" flagstr="${2:-}" where="${3:-before-fields}" - local flag value existing at changed=0 + local flag opt value existing merged re at changed=0 local -a flags [[ -n $flagstr ]] || flagstr="$(mca_flags)" read -r -a flags <<< "$flagstr" for flag in "${flags[@]}"; do - if [[ $flag == --enable-blink-features=* ]]; then - value="${flag#--enable-blink-features=}" - if [[ $line =~ --enable-blink-features=([A-Za-z0-9,_-]*) ]]; then - existing="${BASH_REMATCH[1]}" - [[ ",$existing," == *",$value,"* ]] && continue - line="${line/--enable-blink-features=$existing/--enable-blink-features=${existing:+$existing,}$value}" - changed=1 - continue - fi - fi + case "$flag" in + --enable-blink-features=*|--enable-features=*) + opt="${flag%%=*}" + value="${flag#*=}" + re="(^|[[:space:]])$opt=([^[:space:]]*)" + if [[ $line =~ $re ]]; then + existing="${BASH_REMATCH[2]}" + merged="$(_mca_list_merge "$existing" "$value")" + [[ $merged == "$existing" ]] && continue + line="${line/$opt=$existing/$opt=$merged}" + changed=1 + continue + fi + ;; + esac [[ " $line " == *" $flag "* ]] && continue @@ -117,12 +155,12 @@ mca_exec_inject() { # Flag files # --------------------------------------------------------------------------- -# mca_flags_apply +# mca_flags_apply [kind] # Writes the flags into the file the program's launcher reads. Returns 0 when # the program has such a file (whether or not anything needed changing), 1 when # it has none and the desktop entry route has to be used instead. mca_flags_apply() { - local prog="$1" name target='' existing_target='' + local prog="$1" kind="${2:-app}" name target='' existing_target='' local -a candidates=() mapfile -t candidates < <(mca_flags_candidates "$prog" | awk '!seen[$0]++') @@ -133,7 +171,7 @@ mca_flags_apply() { for name in "${candidates[@]}"; do if [[ -f "$MCA_XDG_CONFIG/$name" ]]; then existing_target="$name" - _mca_flags_write "$MCA_XDG_CONFIG/$name" + _mca_flags_write "$MCA_XDG_CONFIG/$name" "$kind" fi done [[ -n $existing_target ]] && return 0 @@ -143,19 +181,21 @@ mca_flags_apply() { # creating that one also covers every other application using the same # shared Electron. target="${candidates[-1]}" - _mca_flags_write "$MCA_XDG_CONFIG/$target" + _mca_flags_write "$MCA_XDG_CONFIG/$target" "$kind" return 0 } -# _mca_flags_write -# Adds the flags as a marked block, or merges into an --enable-blink-features -# line that is already in the file. +# _mca_flags_write [kind] +# Adds the flags as a marked block, or merges into a feature list that is +# already in the file. _mca_flags_write() { - local file="$1" content='' line flag value existing merged=0 block='' + local file="$1" kind="${2:-app}" + local content='' line flag opt want existing merged=0 block='' local -a flags + local -A merged_opts=() local created=0 - read -r -a flags <<< "$(mca_flags)" + read -r -a flags <<< "$(mca_flags "$kind")" [[ -f $file ]] || created=1 if (( ! created )); then @@ -166,16 +206,23 @@ _mca_flags_write() { fi fi - # Merge into a blink-features line the user (or a distribution) put there. - if [[ $content == *--enable-blink-features=* ]]; then + # Merge into a feature list the user (or a distribution) put there, rather + # than write a second line of the same option further down. + if [[ $content == *--enable-* ]]; then local out='' while IFS= read -r line; do - if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then - existing="${BASH_REMATCH[1]}" - if [[ ",$existing," != *",$MCA_FEATURE,"* ]]; then - line="--enable-blink-features=${existing:+$existing,}$MCA_FEATURE" + if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then + opt="${BASH_REMATCH[1]}" + existing="${BASH_REMATCH[2]}" + want='' + for flag in "${flags[@]}"; do + [[ $flag == "$opt="* ]] && want="${flag#*=}" + done + if [[ -n $want ]]; then + line="$opt=$(_mca_list_merge "$existing" "$want")" + merged_opts["$opt"]=1 + merged=1 fi - merged=1 fi out+="$line"$'\n' done <<< "$content" @@ -183,7 +230,7 @@ _mca_flags_write() { fi for flag in "${flags[@]}"; do - (( merged )) && [[ $flag == --enable-blink-features=* ]] && continue + [[ -n ${merged_opts["${flag%%=*}"]:-} ]] && continue block+="$flag"$'\n' done @@ -218,7 +265,7 @@ _mca_flags_drop_block() { # mca_flags_revert mca_flags_revert() { - local file="$1" detail="$2" content line out='' existing new + local file="$1" detail="$2" content line out='' opt existing new [[ -f $file ]] || return 0 @@ -238,12 +285,13 @@ mca_flags_revert() { if [[ $detail == merged* ]]; then while IFS= read -r line; do - if [[ $line =~ ^[[:space:]]*--enable-blink-features=([A-Za-z0-9,_-]*)[[:space:]]*$ ]]; then - existing="${BASH_REMATCH[1]}" - new="${existing//$MCA_FEATURE/}" - new="${new//,,/,}"; new="${new#,}"; new="${new%,}" + if [[ $line =~ ^[[:space:]]*($MCA_FEATURE_OPTS)=([^[:space:]]*)[[:space:]]*$ ]]; then + opt="${BASH_REMATCH[1]}" + existing="${BASH_REMATCH[2]}" + # Either name, whichever version of this program put it there. + new="$(_mca_list_strip "$existing" "$MCA_BROWSER_FEATURES")" [[ -z $new ]] && continue - line="--enable-blink-features=$new" + line="$opt=$new" fi out+="$line"$'\n' done <<< "$content" @@ -419,15 +467,17 @@ _mca_entry_patch_inplace() { *) mca_is_chromium "$prog" || return 0 ;; esac + mca_desktop_is_browser && kind=browser || kind=app + if [[ $gate == autostart ]]; then [[ $CFG_AUTOSTART == yes ]] || return 0 else id="${file##*/}"; id="${id%.desktop}" - mca_desktop_is_browser && kind=browser || kind=app mca_kind_wanted "$kind" "$id" "$packaging" || return 0 fi - content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE")" + content="$(_mca_desktop_transform "$file" "$MCA_MARK_INPLACE" \ + "$(mca_flags "$kind")")" fi [[ -n $content ]] || return 0