feat: turn off KDE's middle-click paste

Inside a Chromium application the flag settles this on its own - with
autoscroll on, Blink stops pasting the primary selection. Everywhere else
on the desktop middle click goes on pasting, which is the thing this whole
program exists to get away from.

KWin has a switch for it, on Wayland, where the paste is a protocol the
compositor either offers or does not: EnablePrimarySelection in kwinrc.
Written with kwriteconfig, the way System Settings would, because kwinrc
has a cascade behind it. What the key said before goes in the ledger, so
disable puts it back - and a key that already said false is left alone and
not recorded, so a paste the user switched off themselves is never
switched back on.

X11 has no such switch and nothing is written there.
This commit is contained in:
Felitendo committed 2026-09-08 13:30:43 +02:00
1 parent 0c4e7645d0
commit 004094ef78
11 files changed
+259 -4

No files matched your search

+1
View File
@@ -18,6 +18,7 @@ Just run `middleclick-autoscroll`. This will open the configuration TUI that loo
Not identified 1 - see the applications list
Steam ON
New applications ON
Middle-click paste off
Last applied 2 minutes ago
Applications pick this up the next time they are started.
+35 -1
View File
@@ -172,6 +172,35 @@ 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
an ordinary desktop entry and needs nothing special.
# MIDDLE-CLICK PASTE
Inside a Chromium application the argument settles this on its own: with
autoscroll on, Blink stops pasting the primary selection on middle click,
because the button is doing something else now. Everywhere else on the desktop
middle click goes on pasting, so the desktop's own switch for it is turned off
too.
KDE has such a switch, and only for a Wayland session, where the paste is a
protocol the compositor either offers or does not:
~/.config/kwinrc, [Wayland], EnablePrimarySelection=false
It is written with *kwriteconfig6*, the same tool System Settings uses, because
kwinrc has a cascade behind it and entries a distribution can mark immutable;
an edit that reaches past all that writes something KDE goes on to ignore. KWin
reads the key once, while it starts, so the change is true from the next login
on rather than straight away.
What the key said before is recorded, and *disable* puts that back. A key that
already said *false* before any of this is left alone and not recorded either,
so a paste the user switched off themselves is never switched back on.
On X11 there is nothing to switch. The primary selection is part of X itself
and every toolkit reaches for it on its own; no one place can say no. The
status screen leaves the row out where there is nothing to report.
Turn it off under *Settings* to keep the desktop's middle-click paste.
# WHAT CANNOT BE DETECTED
An AppImage keeps its payload in a compressed filesystem, so there is no way to
@@ -190,6 +219,11 @@ _~/.local/state/middleclick-autoscroll/ledger_
_~/.local/state/middleclick-autoscroll/backup/_
Copies of the files that are edited in place rather than shadowed.
_~/.config/kwinrc_
KDE's own configuration. One key in it, *EnablePrimarySelection*, is
written when the desktop's middle-click paste is turned off, and put
back by *disable*.
_~/.cache/middleclick-autoscroll/detect_
Which programs were found to be Chromium, keyed by size and modification
time. Safe to delete.
@@ -207,7 +241,7 @@ up at the next *apply* rather than on their own.
# SEE ALSO
*systemctl*(1), *flatpak*(1), *snap*(8)
*systemctl*(1), *flatpak*(1), *snap*(8), *kwriteconfig6*(1)
# AUTHORS
+9
View File
@@ -63,6 +63,9 @@ msgstr "%d – siehe Anwendungsliste"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Mittelklick-Einfügen"
msgid "New applications"
msgstr "Neue Anwendungen"
@@ -112,6 +115,9 @@ msgstr "Programme, die sich beim Anmelden selbst starten"
msgid "Spotify"
msgstr "Spotify"
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"
@@ -179,6 +185,9 @@ msgstr "Steam startet seine Oberfläche auf eine Art, die diese Version nicht ä
msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
msgstr "Steams Oberfläche lässt sich das Flag nicht geben, ohne eine Datei zu hinterlassen, die Steam neu installieren würde; die Änderung wurde zurückgenommen."
msgid "Middle click stops pasting at the next login."
msgstr "Mittelklick fügt ab der nächsten Anmeldung nichts mehr ein."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
+9
View File
@@ -63,6 +63,9 @@ msgstr ""
msgid "Steam"
msgstr ""
msgid "Middle-click paste"
msgstr ""
msgid "New applications"
msgstr ""
@@ -112,6 +115,9 @@ msgstr ""
msgid "Spotify"
msgstr ""
msgid "Turn off KDE's middle-click paste"
msgstr ""
msgid "Apply to newly installed applications"
msgstr ""
@@ -179,6 +185,9 @@ msgstr ""
msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
msgstr ""
msgid "Middle click stops pasting at the next login."
msgstr ""
#. Help
msgid "Usage: middleclick-autoscroll [command]"
+5
View File
@@ -134,6 +134,11 @@ mca_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.
[[ $CFG_PASTE == yes ]] && mca_kde_paste_apply
mca_prune_orphans
if (( MCA_CHANGES )) && mca_have update-desktop-database; then
+14 -2
View File
@@ -278,9 +278,12 @@ mca_state_write() {
# shadow <generated desktop entry> <source entry it was generated from>
# inplace <edited file> <basename of its backup copy>
# flags <flag file> created | appended | merged
# kwin <kwinrc> what EnablePrimarySelection said before
#
# `inplace` and `flags` differ in how they are undone: a backup is restored
# wholesale, a flag file only loses the one line that was added to it.
# 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.
mca_ledger_add() {
local kind="$1" path="$2" detail="${3:-}"
@@ -303,6 +306,15 @@ mca_ledger_has() {
awk -F'\t' -v p="$1" '$2 == p { found = 1 } END { exit !found }' "$MCA_LEDGER"
}
# mca_ledger_detail <path>
# The third column of that file's line, empty when the ledger has none. What it
# means is the kind's business; for `kwin` it is the value to put back.
mca_ledger_detail() {
[[ -f $MCA_LEDGER ]] || return 1
awk -F'\t' -v p="$1" '$2 == p { d = $3 } END { if (!length(d)) exit 1; print d }' \
"$MCA_LEDGER"
}
mca_ledger_forget() {
local path="$1" tmp
[[ -f $MCA_LEDGER ]] || return 0
+1
View File
@@ -144,6 +144,7 @@ mca_config_load() {
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
CFG_EXTRA_FLAGS="$(mca_config_get ExtraFlags '')"
+167
View File
@@ -0,0 +1,167 @@
# shellcheck shell=bash
#
# KDE's middle-click paste.
#
# Inside a Chromium application the flag settles this on its own: with
# autoscroll on, Blink stops pasting the primary selection on middle click,
# because the button is doing something else now. Everywhere else on the
# desktop middle click goes on pasting, which is the behaviour this whole
# program exists to get away from - so the desktop's own switch for it is
# turned off too.
#
# KWin has such a switch, and only for a Wayland session, where the paste is a
# protocol the compositor either offers or does not:
#
# ~/.config/kwinrc, [Wayland], EnablePrimarySelection=false
#
# On X11 there is nothing to switch. The primary selection is part of X itself
# and every toolkit reaches for it on its own; no one place can say no.
#
# KWin reads the key once, while it starts, so what is written here is true
# from the next login on rather than straight away.
MCA_KWINRC="${MCA_XDG_CONFIG}/kwinrc"
MCA_KWIN_GROUP="Wayland"
MCA_KWIN_KEY="EnablePrimarySelection"
# The kconfig command line tools, whichever generation this session has. They
# are what System Settings writes kwinrc with, and worth the fork: the file has
# a cascade behind it - /etc/xdg, the kdedefaults directory - and entries a
# distribution can mark immutable, and an edit that reaches past all that with
# sed writes something KDE goes on to ignore.
MCA_KDE_TOOL=''
MCA_KDE_TOOL_LOOKED=0
_mca_kde_tool() {
local gen
if (( ! MCA_KDE_TOOL_LOOKED )); then
MCA_KDE_TOOL_LOOKED=1
for gen in 6 5; do
if mca_have "kreadconfig$gen" && mca_have "kwriteconfig$gen"; then
MCA_KDE_TOOL="$gen"
break
fi
done
fi
[[ -n $MCA_KDE_TOOL ]] || return 1
printf '%s\n' "$MCA_KDE_TOOL"
}
# mca_kde_session
# Whether this is a KWin Wayland session.
mca_kde_session() {
local desktop="${XDG_CURRENT_DESKTOP:-}"
# The watcher is a systemd service, and one started before the session
# imported its environment sees none of these variables. A running
# kwin_wayland answers both halves of the question at once, and is what
# such a run ends up going by.
if [[ -z $desktop && -z ${KDE_FULL_SESSION:-} ]]; then
mca_have pgrep || return 1
pgrep -u "$(id -u)" -x kwin_wayland > /dev/null 2>&1
return
fi
# XDG_CURRENT_DESKTOP is the one every desktop sets, and the one to go by
# wherever it is set; KDE_FULL_SESSION is what answers when it is not.
[[ -z $desktop || ${desktop^^} == *KDE* ]] || return 1
[[ ${XDG_SESSION_TYPE:-} == wayland || -n ${WAYLAND_DISPLAY:-} ]]
}
mca_kde_available() {
mca_kde_session && _mca_kde_tool > /dev/null
}
# mca_kde_read [file]
# What the key says now, lowercased, empty when it is not set at all.
mca_kde_read() {
local file="${1:-$MCA_KWINRC}" gen val
gen="$(_mca_kde_tool)" || return 1
# kreadconfig creates the file it is pointed at, and the status screen -
# which asks on every redraw - has no business writing anything. A file
# that is not there has no value in it to read anyway.
[[ -f $file ]] || return 0
val="$("kreadconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
--key "$MCA_KWIN_KEY" 2>/dev/null)"
printf '%s\n' "${val,,}"
}
# mca_kde_write [file] <value>
# An empty value takes the key back out rather than writing nothing into it -
# a key that is missing and a key that says nothing are not the same thing to
# KDE, and only the first one is what an untouched kwinrc looks like.
mca_kde_write() {
local file="$1" value="$2" gen
gen="$(_mca_kde_tool)" || return 1
if [[ -z $value ]]; then
"kwriteconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
--key "$MCA_KWIN_KEY" --delete 2>/dev/null
else
"kwriteconfig$gen" --file "$file" --group "$MCA_KWIN_GROUP" \
--key "$MCA_KWIN_KEY" "$value" 2>/dev/null
fi
}
# mca_kde_paste_apply
# Turns the paste off, and remembers what the key said before so that undoing
# puts that back rather than a guess.
#
# A key that already says false without the ledger knowing about it was set by
# the user or by the distribution. That one is left alone and nothing is
# recorded, so `disable` cannot switch a paste back on that this program never
# switched off.
mca_kde_paste_apply() {
local current previous changed=0
mca_kde_available || return 1
current="$(mca_kde_read)" || return 1
if mca_ledger_has "$MCA_KWINRC"; then
# Ours already. What goes back is the value recorded the first time
# round, not what reading the file now would say - that is false, and
# false is what is being undone.
previous="$(mca_ledger_detail "$MCA_KWINRC")" || previous=unset
elif [[ $current == false ]]; then
return 0
else
previous="${current:-unset}"
fi
if [[ $current != false ]]; then
mca_kde_write "$MCA_KWINRC" false || return 1
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
changed=1
fi
mca_ledger_add kwin "$MCA_KWINRC" "$previous"
(( changed )) && mca_note "$(mca_msg "Middle click stops pasting at the next login.")"
return 0
}
# mca_kde_paste_revert <file> <previous value>
mca_kde_paste_revert() {
local file="$1" previous="$2"
_mca_kde_tool > /dev/null || return 1
[[ -f $file ]] || return 1
# Only what is still recognisably ours. A key somebody has since put a
# value of their own into is theirs now.
[[ "$(mca_kde_read "$file")" == false ]] || return 1
[[ $previous == unset ]] && previous=''
mca_kde_write "$file" "$previous" || return 1
# Nothing was in the file but the key, which means there was no file before
# this program wrote one. An empty kwinrc says nothing that its absence
# does not say better.
[[ -s $file ]] || rm -f -- "$file"
return 0
}
+13
View File
@@ -194,6 +194,18 @@ mca_ui_status() {
fi
fi
# Not an ON/OFF like the rows around it: what is being reported is the state
# of the paste, not of a switch, and off is the state this program is after.
if mca_kde_available; then
if [[ "$(mca_kde_read)" == false ]]; then
_mca_row "$(mca_msg "Middle-click paste")" \
"${MCA_C_GREEN}$(mca_msg "off")${MCA_C_RESET}"
else
_mca_row "$(mca_msg "Middle-click paste")" \
"${MCA_C_DIM}$(mca_msg "on")${MCA_C_RESET}"
fi
fi
_mca_row "$(mca_msg "Last applied")" "$(mca_time_ago "$last")"
if [[ $CFG_ENABLED == yes ]]; then
@@ -216,6 +228,7 @@ MCA_SETTINGS=(
"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"
)
+4
View File
@@ -646,6 +646,10 @@ mca_revert_all() {
mca_steam_revert "$path" "$detail" \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
;;
kwin)
mca_kde_paste_revert "$path" "$detail" \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
;;
esac
done < <(tac "$MCA_LEDGER" 2>/dev/null)
+1 -1
View File
@@ -17,7 +17,7 @@ set -uo pipefail
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
for _mod in common config detect patch steam apply menu; do
for _mod in common config detect patch steam 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