Compare commits

..
52 Commits
Author SHA1 Message Date
Felitendo de6cf98dcc chore: 1.7.0
check / syntax and shellcheck (push) Successful in 1m10s
release / Debian package (push) Failing after 14s
release / RPM package (push) Failing after 24s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-10-01 09:36:40 +02:00
Felitendo 95d602e1c4 feat: support firefox and other gecko apps 2026-10-01 09:33:22 +02:00
Felitendo 9b6c64bd97 chore: add a more info field to the app issue template 2026-10-01 09:12:45 +02:00
Felitendo 8a7e67b1f9 chore: ask for the app in the issue title 2026-10-01 09:06:20 +02:00
Felitendo c0fca75cbc chore: add issue template for apps that do not work 2026-10-01 09:00:19 +02:00
Felitendo 15a7cb9cf9 chore: 1.6.1
release / Debian package (push) Failing after 14s
release / RPM package (push) Failing after 21s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-09-29 08:16:25 +02:00
Felitendo 0234e81811 fix: patch symlinked entries instead of writing through them 2026-09-29 08:15:38 +02:00
Felitendo 6f62c7f21c fix: patch the steamrt3 Steam client 2026-09-29 08:15:33 +02:00
Felitendo 3c000b8a6e docs: show new ui as screenshots in the readme 2026-09-28 15:08:16 +02:00
Felitendo 0f9b041b20 chore: show feature requests before bug reports 2026-09-26 21:19:26 +02:00
Felitendo 98f6ac9c48 chore: commit different topics separately 2026-09-25 09:30:38 +02:00
Felitendo 9861d24c2a chore: switch to ko-fi 2026-09-24 13:21:21 +02:00
Felitendo f74fc3e8ca chore: add issue templates 2026-09-24 12:39:52 +02:00
Felitendo 09b2c49eb3 chore: 1.6.0
release / Debian package (push) Failing after 13s
release / RPM package (push) Failing after 24s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-09-24 11:11:27 +02:00
Felitendo ab62d2a3cf docs: show the search field in the screenshot 2026-09-24 11:11:27 +02:00
Felitendo fa55571118 feat: translate into ten more languages 2026-09-24 11:11:27 +02:00
Felitendo a516cf8f59 feat: scroll long application lists 2026-09-24 11:11:27 +02:00
Felitendo 074a654597 feat: show results in the menu instead of waiting for a key 2026-09-24 10:44:52 +02:00
Felitendo 472cf5382c fix: explain "cannot tell" only when the list has one 2026-09-24 10:44:52 +02:00
Felitendo 82afefe5f8 feat: rework the settings 2026-09-24 10:44:52 +02:00
Felitendo 677660c1c6 fix: start faster by skipping lines scripts do not need 2026-09-24 10:23:59 +02:00
Felitendo e9311431f8 feat: search field in the applications list 2026-09-24 10:23:59 +02:00
Felitendo e5d2481d6d chore: 1.5.4
release / Debian package (push) Failing after 14s
release / RPM package (push) Failing after 22s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-09-24 09:47:59 +02:00
Felitendo fc9dae95f9 fix: drop the detection cache on every update 2026-09-24 09:47:57 +02:00
Felitendo dd0671440d docs: commit and push only after a local check 2026-09-24 08:42:01 +02:00
Felitendo 0b1b33ea84 docs: reword how it works 2026-09-24 08:38:06 +02:00
Felitendo e3278ecf27 chore: 1.5.3
release / Debian package (push) Failing after 15s
release / RPM package (push) Failing after 34s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-09-24 08:25:50 +02:00
Felitendo 2401855b70 fix: keep the flag in front of a bare double dash
Discord's entry is `discord --url -- %u`. The flag went in before the
field code, which put it after the double dash, and that is where
Chromium stops reading switches and starts treating everything as an
address. The feature never came on.
2026-09-24 08:24:15 +02:00
Felitendo da291cefa5 fix: recognise Discord's bootstrap launcher
Discord no longer ships its application. /usr/bin/discord is a few lines
of shell that run updater_bootstrap, which downloads Electron into the
user's config directory on first start and execs it from there. So there
is no binary next to the launcher to find markers around, and the path it
hands over to is built out of $HOME and the version it just downloaded,
which the script reader cannot resolve. Discord came out as "not
Chromium" and was dropped from the list entirely.

The name of the bootstrap it ships is the one thing left that says
Discord, so that is the hint now.
2026-09-24 08:24:15 +02:00
Felitendo e2e05748f7 docs: explain steam and the browser flag names 2026-09-24 08:22:09 +02:00
Felitendo c20ea98de4 chore: 1.5.2
release / RPM package (push) Failing after 23s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
release / Debian package (push) Failing after 13s
2026-09-24 02:01:16 +02:00
Felitendo d0d977ff56 docs: cut the readme down, fold the details 2026-09-24 02:00:33 +02:00
Felitendo cf48fc47df fix: say 1 minute ago, not 1 minutes ago 2026-09-24 02:00:33 +02:00
Felitendo 335d4cc9ec docs: show the menu as real screenshots 2026-09-24 02:00:33 +02:00
Felitendo c7222d22bd docs: add a changelog and the release notes flow 2026-09-24 02:00:33 +02:00
Felitendo 4fffba005d ci: take the release notes from the changelog 2026-09-24 01:13:05 +02:00
Felitendo b93d0f5f1e docs: add commit rules to claude.md 2026-09-24 01:13:05 +02:00
Felitendo d39e669743 docs: coffee button and bug report link 2026-09-24 01:13:05 +02:00
Felitendo f9826edc24 chore: switch funding to buy me a coffee 2026-09-24 01:13:05 +02:00
Felitendo 5e645acbd0 docs: add icon and centered readme header 2026-09-21 14:06:54 +02:00
Felitendo 1042639a08 docs: add claude.md and avoid dashes 2026-09-21 14:00:02 +02:00
Felitendo ab8c6477cb chore: move to LoonixTools 2026-09-21 13:07:19 +02:00
Felitendo 0fbe8bdb39 chore: 1.5.1
release / Debian package (push) Failing after 13s
release / RPM package (push) Failing after 22s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
2026-09-15 19:12:54 +02:00
Felitendo a992efb0cb fix: pass Brave's autoscroll feature name to browsers 2026-09-15 19:01:01 +02:00
Felitendo b97ccf0379 chore: toned down readme 2026-09-15 08:44:43 +02:00
Felitendo 7a446364dc feat: identify AppImages by reading their squashfs name table
Every AppImage came out as "cannot tell" because the payload is a
compressed filesystem and none of the Chromium markers is on disk to be
found. The names are reachable, though: squashfs keeps the name of
everything it holds in one table of a few kilobytes, and a name is all
this question needs. That table is now read and inflated in place -
nothing is unpacked, and the image is never run, which a scan started by
the path unit has no business doing anyway.

So a Tauri AppImage such as Modrinth is recognised as WebKitGTK and
dropped from the list instead of being offered as something that could be
switched on - there is no autoscroll in WebKit to ask for - and an
Electron one is covered like any other application.

Left as "cannot tell": lzo and lz4, which squashfs stores as bare blocks
no tool will read without their own framing, and the original ISO9660
layout.

Alongside:

- The detect cache carries a format line. Its entries are keyed on size
  and mtime, so an AppImage that has not changed would otherwise keep
  answering the way an older version decided it did, forever.

- A marker list for whole-tree searches, which is what a Flatpak, a snap
  and an AppImage need. icudtl.dat, snapshot_blob.bin and resources.pak
  are out of it: Flutter ships an icudtl.dat in data/, and next to a
  binary those names are evidence while four directories down they are
  not. The Flatpak and snap searches now share that list.
2026-09-14 14:06:23 +02:00
Felitendo 449a20ce9b chore: added funding 2026-09-08 14:33:51 +02:00
Felitendo ae4e8b9294 chore: 1.5.0
release / Debian package (push) Failing after 14s
release / RPM package (push) Failing after 22s
release / Release and repositories (push) Skipped
release / Install from the RPM repository (push) Skipped
KDE's own middle-click paste is turned off along with everything else. Inside a
Chromium application the flag already settles it - with autoscroll on, Blink
stops pasting the primary selection - but everywhere else on the desktop middle
click goes on pasting, which is the thing this program exists to get away from.

KWin has a switch for it on a Wayland session, where the paste is a protocol
the compositor either offers or does not, and that is what gets written. It is
true from the next login on. What the key said before is recorded, so disable
puts it back, and a key that already said no is left alone and never switched
back on. X11 has no such switch and nothing is written there.

Off under Settings for anyone who wants the paste kept.
2026-09-08 14:01:06 +02:00
Felitendo b4fa0edf28 docs: reword the install steps 2026-09-08 14:01:06 +02:00
Felitendo e09b145f3a fix: install the user units where systemd will find them
A build with a prefix of its own put them in $(PREFIX)/lib/systemd/user, which
is right for /usr/local and wrong for the one prefix an install without root
can use: systemd searches $XDG_DATA_HOME/systemd/user and never
~/.local/lib/systemd/user. An install into ~/.local came out with a working
program whose watcher could not be enabled at all.

They go under share now, which both a system prefix and a home one are
searched for. Nothing changes for the packages - the deb and the rpm pass
USERUNITDIR in themselves.

That prefix is what an atomic distribution leaves a user, so the readme says
how to install on Bazzite: layer the rpm and reboot, or put the whole thing in
$HOME, which is where everything it writes ends up anyway.
2026-09-08 13:38:39 +02:00
Felitendo 004094ef78 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.
2026-09-08 13:30:43 +02:00
Felitendo 0c4e7645d0 docs: rework the readme 2026-09-08 13:30:43 +02:00
48 changed files with 5237 additions and 606 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
ko_fi: felitendo
@@ -0,0 +1,35 @@
name: 💡 Feature request
description: An idea to make middleclick-autoscroll better.
labels:
- enhancement
body:
- type: checkboxes
id: checks
attributes:
label: Before you start
options:
- label: I searched the issues and this is not requested yet.
required: true
- type: textarea
id: problem
attributes:
label: What problem would this solve?
description: What are you trying to do, and what gets in the way?
validations:
required: true
- type: textarea
id: idea
attributes:
label: What would you like?
description: How it could work. A rough idea is fine.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Other ways you thought of
- type: textarea
id: more
attributes:
label: Anything else?
description: Mockups, screenshots, or other tools that do it well.
+85
View File
@@ -0,0 +1,85 @@
name: 🐛 Bug report
description: Something does not work as it should.
labels:
- bug
body:
- type: markdown
attributes:
value: Thanks for taking the time! The more you fill in, the faster it can be fixed.
- type: checkboxes
id: checks
attributes:
label: Before you start
options:
- label: I searched the issues and this is not reported yet.
required: true
- label: I use the latest version.
required: true
- type: textarea
id: what
attributes:
label: What happened?
description: What did you do, and what went wrong?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
placeholder: |
1.
2.
3.
- type: textarea
id: expected
attributes:
label: What did you expect?
validations:
required: true
- type: input
id: app
attributes:
label: App
description: 'Which app, and how it is installed: native package, Flatpak, Snap or AppImage.'
placeholder: Discord, Flatpak
- type: input
id: version
attributes:
label: Version
description: The output of `middleclick-autoscroll --version`.
placeholder: middleclick-autoscroll 1.6.0
validations:
required: true
- type: dropdown
id: install
attributes:
label: How did you install it?
options:
- AUR (Arch, CachyOS, EndeavourOS, Manjaro)
- Fedora package
- Bazzite
- Debian or Ubuntu package
- openSUSE package
- Built from source
- Other
validations:
required: true
- type: input
id: system
attributes:
label: System
description: Distribution, desktop and session (Wayland or X11).
placeholder: CachyOS, KDE Plasma 6.4, Wayland
validations:
required: true
- type: textarea
id: logs
attributes:
label: Status and logs
description: The output of `middleclick-autoscroll status` and `middleclick-autoscroll list`.
render: shell
- type: textarea
id: more
attributes:
label: Anything else?
description: Screenshots, videos or anything else that could help.
+23
View File
@@ -0,0 +1,23 @@
name: 🧩 App does not work
description: Autoscroll does not work in an app. Just tell us which one.
title: 'App: '
labels:
- app
body:
- type: markdown
attributes:
value: Just add the app's name to the title. The rest is optional.
- type: dropdown
id: install
attributes:
label: How is it installed?
description: Skip it if you are not sure.
options:
- Native package
- Flatpak
- Snap
- AppImage
- type: textarea
id: more
attributes:
label: More info
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
#
# Builds the GitHub release notes for a tag, the way big projects such as
# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome,
# the highlights), a support section, then every commit since the last
# release, sorted by kind, and a link to the full changelog.
#
# .github/release-notes.sh v1.2.0 the notes
# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists)
#
# Commit authors come from the GitHub API through gh. Without it the list
# goes without them.
set -euo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
title=0
if [[ ${1:-} == --title ]]; then
title=1
shift
fi
tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}"
if ! grep -qx "## $tag" CHANGELOG.md; then
echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2
exit 1
fi
if (( title )); then
printf '%s\n' "$tag"
exit 0
fi
repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
name="${repo#*/}"
# The entry: up to the next one, without the date line (GitHub shows the
# date), one heading level up, outside code blocks.
entry="$(awk -v h="## $tag" '
$0 == h { on = 1; next }
on && /^## / { exit }
!on { next }
/^```/ { code = !code }
!code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next }
!code && /^###/ { sub(/^#/, "") }
{ print }
' CHANGELOG.md | sed -e '/./,$!d')"
# The commits: from the last release, or from the start. A tag that does not
# exist yet is a preview of what HEAD would become.
if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then
target="$tag"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)"
else
target="$(git rev-parse HEAD)"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)"
fi
declare -A login=()
if command -v gh > /dev/null; then
if [[ -n $prev ]]; then
api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv')
else
api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv')
fi
while IFS=$'\t' read -r sha who; do
[[ -n $who ]] && login[$sha]="$who"
done < <(gh "${api[@]}" 2>/dev/null || true)
fi
declare -A list=()
count_maint=0
while IFS=$'\t' read -r sha subject body; do
# Version bumps say nothing a reader needs.
[[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
case "$subject" in
*!:*) kind=breaking ;;
feat:* | feat\(*) kind=feat ;;
fix:* | fix\(*) kind=fix ;;
perf:* | perf\(*) kind=enh ;;
docs:* | docs\(*) kind=docs ;;
chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;;
# Older commits without a prefix, sorted by what they say.
*README* | *readme* | *Readme*) kind=docs ;;
Add\ * | Put\ * | Introduce\ *) kind=feat ;;
Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;;
*\ *) kind=enh ;;
*) kind=maint ;;
esac
[[ $body == *"BREAKING CHANGE"* ]] && kind=breaking
[[ $subject == "Initial commit" ]] && kind=maint
line="* $subject"
[[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}"
line+=" in https://github.com/$repo/commit/$sha"
list[$kind]+="$line"$'\n'
[[ $kind == maint ]] && count_maint=$((count_maint + 1))
done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //')
changes=''
for pair in "breaking:🚨 Breaking Changes" "feat:🚀 Features" "enh:🌟 Enhancements" "fix:🐛 Bug fixes" "docs:📚 Documentation"; do
kind="${pair%%:*}"
[[ -n ${list[$kind]:-} ]] || continue
changes+="### ${pair#*:}"$'\n'"${list[$kind]}"
done
if [[ -n ${list[maint]:-} ]]; then
changes+=$'\n'"<details><summary>🧰 Maintenance ($count_maint)</summary>"$'\n\n'"${list[maint]}"$'\n'"</details>"$'\n'
fi
if [[ -n $prev ]]; then
full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag"
else
full="**Full Changelog**: https://github.com/$repo/commits/$tag"
fi
# A release with highlights is a big one: it gets a heading and the support
# section, as in Immich. A patch is a sentence or two and the list.
if grep -q '^## Highlights$' <<< "$entry"; then
cat <<EOF
# 🚀 $name $tag
$entry
## ☕ Support $name
If $name is useful to you, you can buy me a coffee. It keeps these tools going. Found a bug or have an idea? Tell me in the [issues](https://github.com/$repo/issues).
<a href="https://ko-fi.com/felitendo"><img src="https://storage.ko-fi.com/cdn/kofi5.png?v=6" alt="Buy me a coffee on Ko-fi" height="48"></a>
----
EOF
else
printf '%s\n\n' "$entry"
fi
printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
+31
View File
@@ -0,0 +1,31 @@
name: issues
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
# The "fixed" and "not chromium" labels answer and close the issue.
close:
name: close on label
if: >-
github.event.issue.state == 'open'
&& (github.event.label.name == 'fixed' || github.event.label.name == 'not chromium')
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ github.token }}
LABEL: ${{ github.event.label.name }}
NUMBER: ${{ github.event.issue.number }}
run: |
if [ "$LABEL" = fixed ]; then
reason=completed
comment='Fixed, thanks for the report! The fix comes with the next release.'
else
reason='not planned'
comment='Thanks for the report! This app is not built on Chromium, Electron or CEF. So it has no autoscroll built in that this tool could turn on.'
fi
gh issue close "$NUMBER" --repo "$GITHUB_REPOSITORY" --reason "$reason" --comment "$comment"
+11 -3
View File
@@ -33,6 +33,10 @@ jobs:
- name: Check the tag against the Makefile
run: packaging/check-version.sh "${{ github.ref_name }}"
- name: Check CHANGELOG.md has this release
if: startsWith(github.ref, 'refs/tags/v')
run: .github/release-notes.sh --title "${{ github.ref_name }}"
- run: packaging/build-deb.sh
- name: Look inside what was built
@@ -106,6 +110,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# All tags, for the link to the changes since the last release.
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
@@ -117,9 +124,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
.github/release-notes.sh "${{ github.ref_name }}" > notes.md
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--generate-notes \
--title "$(.github/release-notes.sh --title "${{ github.ref_name }}")" \
--notes-file notes.md \
incoming/*.deb incoming/*.rpm \
|| gh release upload "${{ github.ref_name }}" \
incoming/*.deb incoming/*.rpm --clobber
@@ -143,7 +151,7 @@ jobs:
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
else
echo "present=no" >> "$GITHUB_OUTPUT"
echo "::warning::No GPG_PRIVATE_KEY secret - the apt and dnf repositories were not updated. The packages are on the release."
echo "::warning::No GPG_PRIVATE_KEY secret, so the apt and dnf repositories were not updated. The packages are on the release."
exit 0
fi
echo "present=yes" >> "$GITHUB_OUTPUT"
+125
View File
@@ -0,0 +1,125 @@
# Changelog
What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/middleclick-autoscroll/releases) add every commit that went into it. Versions before `v1.1.0` came without notes.
## v1.7.0
_2026-10-01_
Welcome to middleclick-autoscroll `v1.7.0`! Firefox gets autoscroll now, and so do LibreWolf, Zen, Floorp, Waterfox and Thunderbird. It starts working after your next login, or right away with **Re-apply everything**. Then restart Firefox.
### Highlights
- Autoscroll in Firefox
- LibreWolf, Zen, Floorp, Waterfox and Thunderbird too
- Flatpak and snap included
- New Firefox profiles get it on their own
## v1.6.1
_2026-09-29_
A small patch. Autoscroll now works in Steam with **Use experimental SteamRT3 Steam Client** turned on. And apps that start at login through a link, like Mullvad VPN, get autoscroll now instead of an error.
## v1.6.0
_2026-09-24_
Welcome to middleclick-autoscroll `v1.6.0`! The menu got quicker and simpler: search your apps, fewer settings, and no more waiting for a key. It now speaks eleven languages. The ten new ones are machine translated, so corrections are very welcome.
<p align="center">
<img width="620" alt="The applications list in Konsole, with a search field on top" src="https://raw.githubusercontent.com/LoonixTools/middleclick-autoscroll/v1.6.0/res/screenshots/applications.png">
</p>
### Highlights
- Search field in the applications list
- Long lists scroll
- Faster start
- Simpler settings
- Spotify in the applications list
- Results show up in the menu
- Ten more languages
## v1.5.4
_2026-09-24_
A small patch. For some of you Discord was still missing after `v1.5.3`, because an old result from before the update kept it out. Every update now starts with a fresh scan, so Discord shows up again after your next login, or right away with **Re-apply everything**.
## v1.5.3
_2026-09-24_
A small patch. Discord changed the way it installs itself and slipped out of the list, so autoscroll never reached it. It is back.
## v1.5.2
_2026-09-24_
A small patch. The menu now says "1 minute ago" instead of "1 minutes ago".
## v1.5.1
_2026-09-15_
A small patch. Autoscroll now works in Brave, which has its own name for it. AppImages are recognised too: an Electron AppImage gets autoscroll like any other app, and one that cannot autoscroll at all, like Modrinth, is left out of the list.
## v1.5.0
_2026-09-08_
Welcome to middleclick-autoscroll `v1.5.0`! A middle click now scrolls everywhere instead of pasting text.
<p align="center">
<img width="620" alt="The middleclick-autoscroll menu in Konsole, with middle-click paste off" src="https://raw.githubusercontent.com/LoonixTools/middleclick-autoscroll/22bd4f2a3dd89c4cb90fb497c69762b5df7dc6e7/res/screenshots/menu.png">
</p>
### Highlights
- Middle-click paste is off
## v1.4.0
_2026-09-06_
Welcome to middleclick-autoscroll `v1.4.0`! This one is all about Steam.
### Highlights
- Steam stops reinstalling itself
- Scripts that start Steam
## v1.3.0
_2026-09-04_
Welcome to middleclick-autoscroll `v1.3.0`! Browsers look clean again, and Helium joins the list.
### Highlights
- No more warning bar in browsers
- Autoscroll in Helium
## v1.2.0
_2026-09-01_
Welcome to middleclick-autoscroll `v1.2.0`! Steam keeps its autoscroll now, however you start it.
### Highlights
- Steam keeps autoscroll
- More ways to start Steam
## v1.1.0
_2026-08-24_
Welcome to middleclick-autoscroll `v1.1.0`! It now works far beyond Arch, with packages that keep themselves up to date.
### Highlights
- Debian, Ubuntu, Fedora and openSUSE
- Packages that update with your system
- Steam fixes
+76
View File
@@ -0,0 +1,76 @@
# CLAUDE.md
## Style
- Keep everything short: replies, explanations, comments, docs.
- Use simple English: short sentences, common words.
- Avoid em dashes (—). Do not just swap them for "-" either. Rewrite the sentence instead, for
example with a comma, a colon, brackets or two sentences.
## Commits
- English only.
- Conventional Commits prefix: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `ci:`, `build:`.
- Subject as short as possible. Imperative, lowercase, no trailing period.
- Body only when something genuinely cannot be inferred from the diff.
- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions.
- Do not commit or push before I have checked the changes locally and said they are fine. Then
commit and push. Several attempts at the same thing make one commit, and attempts that did not
work make none. Different things done in one session get a commit each. This also goes for
releases and tags.
## Releases
Releases look like the ones of big projects such as Immich. `.github/release-notes.sh <tag>` builds
the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit
since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and
the full changelog link. So commit subjects end up in public: keep them clear.
1. Read `git log <last tag>..HEAD` and pick the version: only fixes → patch, something new → minor,
something that breaks or needs the user to act → major.
2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`).
3. Push, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow
builds the packages and creates the release. It stops before building when the entry is missing.
4. PKGBUILDS picks up the new release for the AUR on its own.
A minor or major release (has `### Highlights`, gets a heading and the support section):
```markdown
## v1.4.0
_2026-09-24_
Welcome to middleclick-autoscroll `v1.4.0`! One or two sentences on what this release is about.
<p align="center">
<img width="480" alt="What the picture shows" src="https://raw.githubusercontent.com/LoonixTools/middleclick-autoscroll/v1.4.0/<path>">
</p>
### 🚨 Breaking changes
- Only if there are any: what changed, and what the user has to do.
### Highlights
- First highlight, a few words
- Second highlight
```
A picture under the welcome is optional. To show the menu or another screen of the program, use a
real screenshot of it running in Konsole, in English. Never a text copy of the screen. The same goes
for the README. The highlights stay a plain list: no heading or text per highlight, the list of
commits explains the rest.
A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes
when you press Enter." The list of commits follows on its own.
- Write for users: what they notice, not how the code does it. Friendly and simple.
- Commands and settings they type go in backticks, buttons and labels in bold.
- To change an old release: edit its entry, commit, then
`gh release edit <tag> --title <tag> --notes "$(.github/release-notes.sh <tag>)"`.
## README
- New UI (a window, a screen, a menu, a setting, a notification) gets a screenshot in the README,
next to the text about it. Like for releases: a real screenshot of it running, in English.
- When a screen changes, take its screenshot again. The README never shows an old one.
+16 -12
View File
@@ -1,4 +1,4 @@
# middleclick-autoscroll - build and install
# middleclick-autoscroll: build and install
#
# Everything here is plain shell; "building" only means compiling the gettext
# catalogs and rendering the man page. Both targets degrade to a no-op when
@@ -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.4.0
VERSION ?= 1.7.0
PREFIX ?= /usr
DESTDIR ?=
@@ -19,20 +19,24 @@ LOCALEDIR ?= $(DATADIR)/locale
MANDIR ?= $(DATADIR)/man
# Where systemd looks for user units. For a normal install into /usr this is
# asked of systemd itself, because the answer is not the same everywhere - a
# distribution that still keeps /lib separate from /usr/lib says so here - and
# asked of systemd itself, because the answer is not the same everywhere. A
# distribution that still keeps /lib separate from /usr/lib says so here. It is
# only guessed at when there is no systemd installed to ask.
#
# A build with a prefix of its own keeps the units under that prefix instead.
# systemd searches $(PREFIX)/lib/systemd/user as well, and a file outside the
# prefix it was asked for is not this build's to place.
# A build with a prefix of its own keeps the units under that prefix instead,
# and under share rather than lib. systemd searches both for a system prefix,
# but only share for a home one: $XDG_DATA_HOME/systemd/user is a search path
# and ~/.local/lib/systemd/user is not, so an install into ~/.local that put
# the units in lib would leave the watcher impossible to enable. That prefix is
# the one an atomic distribution leaves a user: there is no writing to /usr on
# Bazzite or Silverblue without layering a package and rebooting.
ifeq ($(PREFIX),/usr)
USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user)
else
USERUNITDIR ?= $(PREFIX)/lib/systemd/user
USERUNITDIR ?= $(DATADIR)/systemd/user
endif
LINGUAS := de
LINGUAS := cs de es fr it nl pl pt_BR ru tr uk
MOFILES := $(patsubst %,po/%.mo,$(LINGUAS))
MANPAGE := doc/middleclick-autoscroll.1
@@ -57,14 +61,14 @@ po/%.mo: po/%.po
ifdef MSGFMT
$(MSGFMT) --check --output-file=$@ $<
else
@echo "msgfmt not found - skipping $@"
@echo "msgfmt not found, skipping $@"
endif
$(MANPAGE): doc/middleclick-autoscroll.1.scd
ifdef SCDOC
$(SCDOC) < $< > $@
else
@echo "scdoc not found - skipping $@"
@echo "scdoc not found, skipping $@"
endif
# Syntax-check every shell file, and run shellcheck when it is available.
@@ -76,7 +80,7 @@ check:
shellcheck -x -e SC1090,SC1091 src/middleclick-autoscroll $(LIBS); \
echo "ok shellcheck"; \
else \
echo "shellcheck not found - skipped"; \
echo "shellcheck not found, skipped"; \
fi
@if command -v desktop-file-validate >/dev/null 2>&1; then \
echo "ok desktop-file-validate (nothing to check)"; \
+109 -110
View File
@@ -1,166 +1,165 @@
# middleclick-autoscroll
<p align="center">
<img width="200" src="res/middleclick-autoscroll.svg" alt="middleclick-autoscroll">
</p>
Middle-click autoscroll is a CLI for Linux that enables autoscroll in every application that supports it.
Works with browsers, Electron apps like Discord and Spotify, Steam, and anything else that runs on Chromium under the hood.
<h1 align="center">middleclick-autoscroll</h1>
There is no app list. My tool just looks at what uses chromium under the hood and applies the necessary steps to get it working.
<h3 align="center">Middle-click autoscroll in every app that supports it.</h3>
## How to install
<p align="center">
Firefox, Chromium browsers, Electron apps like Discord and Spotify, Steam and anything else built on Chromium.
</p>
**Arch**
<h5 align="center">
<a href="#install">Install</a> |
<a href="#how-to-use">How to use</a> |
<a href="https://github.com/LoonixTools/middleclick-autoscroll/issues">Report a bug</a>
</h5>
<p align="center">
<a href="https://ko-fi.com/felitendo"><img src="https://storage.ko-fi.com/cdn/kofi5.png?v=6" alt="Buy me a coffee on Ko-fi" height="48"></a>
</p>
## Install
<details>
<summary><b>Arch</b></summary>
```bash
yay -S middleclick-autoscroll
```
**Fedora**
</details>
<details>
<summary><b>Fedora</b></summary>
```bash
sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
https://felitendo.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
https://loonixtools.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
sudo dnf install middleclick-autoscroll
```
**Debian**
</details>
<details>
<summary><b>Bazzite</b></summary>
```bash
sudo curl -fsSL -o /etc/yum.repos.d/middleclick-autoscroll.repo \
https://loonixtools.github.io/middleclick-autoscroll/middleclick-autoscroll.repo
sudo rpm-ostree install middleclick-autoscroll
systemctl reboot
```
</details>
<details>
<summary><b>Debian</b>, Ubuntu</summary>
```bash
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://felitendo.github.io/middleclick-autoscroll/KEY.gpg \
curl -fsSL https://loonixtools.github.io/middleclick-autoscroll/KEY.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/middleclick-autoscroll.gpg
echo "deb [signed-by=/etc/apt/keyrings/middleclick-autoscroll.gpg] https://felitendo.github.io/middleclick-autoscroll/deb ./" \
echo "deb [signed-by=/etc/apt/keyrings/middleclick-autoscroll.gpg] https://loonixtools.github.io/middleclick-autoscroll/deb ./" \
| sudo tee /etc/apt/sources.list.d/middleclick-autoscroll.list
sudo apt update && sudo apt install middleclick-autoscroll
```
**openSUSE**
</details>
<details>
<summary><b>openSUSE</b></summary>
```bash
sudo rpm --import https://felitendo.github.io/middleclick-autoscroll/KEY.gpg
sudo rpm --import https://loonixtools.github.io/middleclick-autoscroll/KEY.gpg
sudo zypper addrepo --gpgcheck --refresh \
https://felitendo.github.io/middleclick-autoscroll/rpm middleclick-autoscroll
https://loonixtools.github.io/middleclick-autoscroll/rpm middleclick-autoscroll
sudo zypper install middleclick-autoscroll
```
</details>
## How to use
Just run `middleclick-autoscroll`. This will open the configuration TUI that looks like this:
```
Middle-Click Autoscroll
Autoscroll ON
Applications covered 11 of 13
Not identified 1 - see the applications list
Steam ON
New applications ON
Last applied 2 minutes ago
Applications pick this up the next time they are started.
[1] Turn autoscroll on or off
[2] Re-apply everything
[3] Applications
[4] Settings
[q] Quit
>
```bash
middleclick-autoscroll
```
`[1]` is all you need for the normal case. `[3]` lists every app that was found
and what is being done with it, so you can leave a single one out or switch on
an AppImage that couldn't be identified:
<p align="center">
<img src="res/screenshots/menu.png" alt="The middleclick-autoscroll menu in Konsole: autoscroll on, 19 of 19 applications covered" width="620">
</p>
Press **1**. Autoscroll _✨just works✨_, like on Windows.
<details>
<summary>Applications</summary>
**3** lists every app it found. Type to search, then Space turns one on or off.
<p align="center">
<img src="res/screenshots/applications.png" alt="The applications list in Konsole, with a search field on top and how each app gets autoscroll" width="620">
</p>
</details>
## More
<details>
<summary>How it works</summary>
Chromium, Electron and CEF all have autoscroll built in. On Linux it is off, because middle click
pastes there. One flag turns it back on:
```
Applications
▸ Steam on (Steam)
Chromium on (launcher)
Discord on (flag file)
Obsidian on (launcher)
Spotify on (launcher)
Slack off
Cursor cannot tell
Anything not identified is left alone until it is turned on here.
Up/Down select - Space turns one on or off - q goes back
```
`[4]` has the same switches per category - browsers, Electron and CEF apps,
Flatpaks, snaps, autostart entries, Steam, Spotify - plus a field for extra
Chromium arguments.
## How it works
Blink (the engine in Chromium, Electron, and CEF) already has autoscroll, but
it's off on Linux because middle click does primary-selection paste there. This
flag turns it on:
--enable-blink-features=MiddleClickAutoscroll
```
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.
Adding it to every app by hand is a pain, and updates can undo it. This tool does it for you:
Browsers get the same thing asked for differently:
- It finds every app built on Chromium. AppImages too, without unpacking or starting them.
- It puts the flag where the app reads it: the app's flags file, or a copy of its menu entry in
your home folder.
- It watches for new apps and covers them on their own.
- It only writes to your home folder, and `disable` puts everything back.
--enable-features=MiddleClickAutoscroll
**Browsers** get a spelling of the flag that shows no warning bar. Brave and Helium also get their
own name for the feature. A browser ignores names it does not know.
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.
**Firefox** has autoscroll too, but as a setting, not a flag. It goes into the `user.js` of each
profile. The same works for LibreWolf, Zen, Floorp, Waterfox and Thunderbird.
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.
**Steam** builds its own command line and repairs any file that looks changed. So the flag goes into
the script that starts its web helper, and the file keeps the size and date Steam expects. Steam
finds nothing to repair, and autoscroll works in the store and the library. After a Steam update,
the patch comes back on its own.
## Steam
</details>
Steam's web UI supports autoscroll but has no way to pass extra arguments to its
helper, so the program patches the script that starts it. Steam checks its own
files against its manifest - by size and timestamp - and reinstalls the whole
client package over anything that differs, so the patch is written to look
unchanged: the bytes the argument costs are taken back out of the script's
comments and the timestamp is put back.
<details>
<summary>Commands</summary>
If that can't be done - a script with no comments left to pay for the argument,
or one this version doesn't recognise - nothing is written and Steam is left
alone. A file of the wrong length costs the client package downloaded and
installed again, and a Steam that quits at the end of it instead of coming up;
no autoscroll in Steam is the smaller problem. Nothing is added to Steam's
launcher entry or to the shortcuts it writes for games, and Steam keeps
repairing its own installation.
## Commands
| Command | |
| | |
|---|---|
| `middleclick-autoscroll` | Interactive menu |
| `… enable` | Turn on, apply, start watching |
| `middleclick-autoscroll` | The menu |
| `… enable` | Turn on |
| `… disable` | Undo everything |
| `… apply` | Apply to new apps |
| `… apply --rebuild` | Redo from scratch |
| `… status` | What's covered |
| `… list` | All apps and how they're handled |
| `… apply` | Pick up new apps |
| `… status` | What is covered |
| `… list` | All apps and how they are handled |
## Building from source
</details>
<details>
<summary>Build from source</summary>
```bash
make
sudo make install
```
Optionally needs `msgfmt` (gettext) for translations and `scdoc` for the man
page. Supports `PREFIX` and `DESTDIR`. `make check` runs syntax checks and
shellcheck.
Optional: `msgfmt` for translations, `scdoc` for the man page.
See [packaging/README.md](packaging/README.md) for release builds and repo
signing.
## License
</details>
GPL-3.0-or-later.
+150 -34
View File
@@ -21,21 +21,28 @@ and puts that argument somewhere the application will actually read it, then
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
for the same feature, because 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:
older Chromium, such as Steam's CEF, has no such bar to show anyway. Two
browsers know the feature under a name of their own and ignore Chromium's:
Helium, which is asked for *HeliumMiddleClickAutoscroll* as well, and Brave,
which is asked for *MiddelButtonClickAutoscroll* (spelled the way Brave spells
it). 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.
Firefox and everything else built on Gecko has autoscroll as well, behind a
setting rather than a flag. See *FIREFOX*.
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, Spotify and Firefox 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.
@@ -54,7 +61,7 @@ is refused.
watcher calls; running it by hand is only needed when the watcher is off.
Entries left behind by an application that has since been uninstalled are
removed here too - the entry shadowing it lives in the user's home, where
removed here too. The entry shadowing it lives in the user's home, where
the package manager that removed the application cannot see it.
With *--rebuild* everything is taken back first and written again from
@@ -65,8 +72,7 @@ is refused.
last run was.
*list*
Every Chromium-based application that was found, and how each one is
handled.
Every application that was found, and how each one is handled.
*-h*, *--help*
Show a summary of the commands.
@@ -84,7 +90,7 @@ Two ways, chosen per application.
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. 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
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
@@ -96,8 +102,8 @@ Two ways, chosen per application.
For applications that ship their own binary with no wrapper, and for
everything inside a Flatpak or a snap, 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.
shadows the system one. Entries that already live there (AppImages, web
app shortcuts) are edited in place, with the original kept.
This is the route everything takes on the distributions whose Chromium
wrappers keep their equivalent file under _/etc_, where it is the system's
@@ -111,11 +117,14 @@ 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. An entry that is a symlink, often to an
application's entry in _/usr/share/applications_, is replaced by a patched
file rather than written through, and undoing puts the link back.
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
otherwise be invisible - and Steam puts one there for every game somebody asks
otherwise be invisible. And Steam puts one there for every game somebody asks
for a shortcut to. The folder's name is translated, and the name in use is read
from _~/.config/user-dirs.dirs_ rather than guessed; the watcher is told about
it in a drop-in written when autoscroll is turned on.
@@ -128,19 +137,25 @@ helper, inside Steam's own installation:
~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
The steamrt3 client, a beta for now, has a script of its own:
~/.local/share/Steam/steamrt64/steamwebhelper.sh
Both are patched where both are there, so switching clients keeps autoscroll.
Where that installation is depends on how Steam was installed:
_~/.local/share/Steam_ for Valve's own package and Arch's,
_~/.steam/debian-installation_ for Debian's, and the private tree of the
sandbox for the Flatpak and the snap. All of them are looked at.
Steam compares the installed files against its manifest at every start - by
size and timestamp rather than by content - and restores whatever differs. So
Steam compares the installed files against its manifest at every start (by
size and timestamp, not by content) and restores whatever differs. So
the patch is written to look untouched: the bytes the argument costs are taken
back out of the script's own comments and the timestamp is put back afterwards,
leaving a file exactly as long and exactly as old as Steam left it. A client
that checks its files finds nothing to repair, and the argument survives
however Steam was started - from the menu, from a game shortcut, from a
launcher like Heroic or Lutris, from a terminal.
however Steam was started: from the menu, from a game shortcut, from a
launcher like Heroic or Lutris, or from a terminal.
That is what has to work, because it is the only thing that does. A patch that
changes the length is never written, whatever else could be done to cover for
@@ -149,11 +164,16 @@ it was given arguments for, and that one carries no arguments of anybody's. One
wrong length costs the whole client package downloaded, extracted and installed
again, and a client that quits at the end of it instead of coming up.
So where the comments cannot pay for the argument, or a client update has
changed how the helper is started, nothing is written and the status screen
says Steam is not patched. Nothing goes on Steam's launcher entry, its
autostart entry or the shortcuts it writes for single games either, and Steam
goes on repairing its own installation.
The steamrt3 script has no comments to pay with. So it is copied aside as
_steamwebhelper.sh.orig_, and a stub of the same size takes its place. The stub
runs the copy with the argument added. Steam only checks the files it knows,
and the copy is not one of them.
So where neither fits, or a client update has changed how the helper is
started, nothing is written and the status screen says Steam is not patched.
Nothing goes on Steam's launcher entry, its autostart entry or the shortcuts it
writes for single games either, and Steam goes on repairing its own
installation.
An installation patched by an earlier version, which appended where the
comments were short, is written again to fit at the next apply, or taken back
@@ -169,14 +189,93 @@ 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.
# WHAT CANNOT BE DETECTED
# FIREFOX
An AppImage keeps its payload in a compressed filesystem, so there is no way to
tell from the outside whether it contains Chromium. Those are listed as *cannot
tell* and left alone until they are switched on from the applications screen.
Firefox has autoscroll of its own, switched off on Linux. There is no flag for
it, only a setting: *general.autoScroll*. It goes into _user.js_ in every
profile, as a marked block, and Firefox reads that file at every start. The
same goes for everything built on Gecko: LibreWolf, Zen, Floorp, Waterfox and
Thunderbird.
Where the profiles are is read from the application's own _application.ini_,
the way Firefox works it out: _~/.mozilla/firefox_, or
_~/.config/mozilla/firefox_ for a new install of Firefox 147 or later. A Flatpak
or a snap has the same inside its own folder. Every profile in _profiles.ini_
is covered. The watcher looks at that file too, so a new profile is covered
from its second start on.
A profile where the setting is already on, in Firefox's own settings or in
_user.js_, is left alone and not recorded.
Firefox copies the setting into _prefs.js_, so *disable* takes it out of both
files. A Firefox that is still open would write it back when it quits. So for
that profile _user.js_ says *false* instead, and the next *disable* or
*enable* clears it up.
# 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.
# APPIMAGES
An AppImage is the AppImage runtime, an ordinary executable, with a squashfs
image appended to it. The payload is compressed, so none of the files that
identify Chromium is on disk where it could be found.
The names are, though. squashfs keeps the name of everything it holds in one
table of its own, a few kilobytes of it, and a name is all this question needs -
so that table is read and decompressed in place. Nothing is unpacked, and the
image is never run: this can happen from the watcher, and starting a program
because it was installed is not something a scan gets to do.
Two kinds of image are still out of reach. One is packed with *lzo* or *lz4*,
which squashfs stores as bare blocks that neither tool will read without the
framing their own file formats add. The other is the original AppImage layout,
which is an ISO9660 filesystem rather than a squashfs one. Both are listed as
*cannot tell* and left alone until they are switched on from the applications
screen.
# WHAT HAS NO FLAG AT ALL
Autoscroll is a Blink feature, so only an application drawn by Blink can be
given it. An application built on WebKitGTK is not one of those. That
includes Tauri on Linux, GNOME Web, and anything else linked against
libwebkit2gtk. WebKit has no equivalent feature to ask for, on any command line
or in any configuration file.
Such an application is identified as what it is and then left out of the list
entirely, rather than being offered as something that could be switched on. An
application that is absent from *list* is absent because there is nothing this
tool could do for it.
# FILES
@@ -190,9 +289,21 @@ _~/.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*.
_user.js_ in every Firefox profile
Gets a block between *// >>> middleclick-autoscroll* and
*// <<< middleclick-autoscroll*. Nothing else in the file is touched.
_~/.cache/middleclick-autoscroll/detect_
Which programs were found to be Chromium, keyed by size and modification
time. Safe to delete.
Which programs were found to be Chromium or Gecko, keyed by size and modification
time. Its first line says which version of the program wrote it, and a
file from an older one is ignored rather than trusted. Otherwise an entry
for a file that has not changed would never be looked at again.
Safe to delete.
# ENVIRONMENT
@@ -205,11 +316,16 @@ Bash 4.2 or newer and GNU coreutils. The watcher needs a systemd user session;
without one everything else works and applications installed later are picked
up at the next *apply* rather than on their own.
*gzip* is what reads the name table of an AppImage, and *xz* or *zstd* the ones
packed with those instead. None of the three is required: an image whose
compressor has no tool installed is listed as *cannot tell*, exactly like the
two formats there is no reader for at all.
# SEE ALSO
*systemctl*(1), *flatpak*(1), *snap*(8)
*systemctl*(1), *flatpak*(1), *snap*(8), *kwriteconfig6*(1)
# AUTHORS
Felitendo. Source and issue tracker at
https://github.com/Felitendo/middleclick-autoscroll
https://github.com/LoonixTools/middleclick-autoscroll
+12 -10
View File
@@ -1,7 +1,7 @@
# Packaging and releases
The Makefile installs everything; these only wrap what it produced. That is
deliberate — a packaging script that lists the files again is a second
on purpose: a packaging script that lists the files again is a second
description of the layout, and two descriptions drift.
| | |
@@ -29,12 +29,14 @@ argument.
## Making a release
1. Bump `VERSION` in the Makefile.
2. Commit, then `git tag vX.Y.Z && git push --tags`.
2. Add the release to `CHANGELOG.md`, in the format CLAUDE.md describes.
3. Commit, then `git tag vX.Y.Z && git push --tags`.
The `release` workflow builds both packages in a Debian and a Fedora container,
refuses the tag if it disagrees with the Makefile, attaches the packages to a
GitHub release, and adds them to the APT and RPM repositories on the `gh-pages`
branch. Nothing else has to be done by hand.
refuses the tag if it disagrees with the Makefile or has no changelog entry,
attaches the packages to a GitHub release with the entry as its notes, and adds
them to the APT and RPM repositories on the `gh-pages` branch. Nothing else has
to be done by hand.
## Trying the release path first
@@ -44,14 +46,14 @@ gh workflow run release.yml -f dry_run=true
Builds both packages, builds both repositories with a key generated on the
spot, checks the three signatures it wrote, and then installs the packages back
out of the repositories — apt on the runner, dnf in a Fedora container. Nothing
out of the repositories (apt on the runner, dnf in a Fedora container). Nothing
is pushed and no release is made. This is worth running after any change to the
packaging, because the alternative is finding out from a tag.
## Setting up the signing, once
The repositories are signed, so this needs a key. Make one that exists for
nothing else — not a personal key — and give it no passphrase: it lives as an
nothing else (not a personal key) and give it no passphrase. It lives as an
encrypted repository secret, and `rpmsign` cannot be handed a passphrase
unattended.
@@ -66,7 +68,7 @@ gpg --armor --export-secret-keys 'middleclick-autoscroll repository' \
Without the secret the workflow still builds both packages and attaches them to
the release; it says so in the log and leaves the repositories alone.
## Pointing Pages at it, once — and in this order
## Pointing Pages at it, once, in this order
The `gh-pages` branch does not exist until a release has put something on it,
and a branch that does not exist cannot be picked in the Pages settings. So:
@@ -77,8 +79,8 @@ and a branch that does not exist cannot be picked in the Pages settings. So:
root.
Doing it the other way round is a wall, and leaving Pages pointed at `main`
serves the source tree at the address the install instructions name — the key
and the indexes are 404 and nothing installs.
serves the source tree at the address the install instructions name. Then the
key and the indexes are 404 and nothing installs.
## What users end up with
+1 -1
View File
@@ -5,7 +5,7 @@
#
# Everything the package contains comes out of `make install`. This only wraps
# what that produced, so there is exactly one description of where a file goes
# and it is the Makefile - a packaging script that lists the files again is a
# and it is the Makefile. A packaging script that lists the files again is a
# second description, and the two drift.
#
# Needs: make, dpkg-deb, msgfmt (gettext), scdoc.
+3 -3
View File
@@ -3,12 +3,12 @@
# Refuses a release whose tag and Makefile disagree.
#
# The version is baked into the program at install time from the Makefile, and
# the packages take theirs from the same place - but the tag is what people see
# the packages take theirs from the same place. But the tag is what people see
# and what the release is named after. A tag that says something else produces
# a package called 1.0.4 containing a program that reports 1.0.3, and nothing
# would have complained.
#
# Anything that is not a v-tag - a run started by hand from a branch - is not a
# Anything that is not a v-tag (a run started by hand from a branch) is not a
# release and has nothing to check.
set -euo pipefail
@@ -19,7 +19,7 @@ ref="${1:-}"
case "$ref" in
v[0-9]*) ;;
*)
echo "not a release tag (${ref:-none}) - nothing to check against"
echo "not a release tag (${ref:-none}), nothing to check against"
exit 0
;;
esac
+2 -2
View File
@@ -6,9 +6,9 @@ Architecture: all
Maintainer: Felitendo <felitendoyt@gmail.com>
Depends: bash (>= 4.2), coreutils, findutils, grep, sed, mawk | gawk | original-awk
Recommends: gettext-base, systemd, desktop-file-utils
Homepage: https://github.com/Felitendo/middleclick-autoscroll
Homepage: https://github.com/LoonixTools/middleclick-autoscroll
Description: middle-click autoscroll for Chromium-based applications
Blink - the engine inside Chromium, Electron and CEF - has had Windows-style
Blink (the engine inside Chromium, Electron and CEF) has had Windows-style
autoscroll for years: hold the middle mouse button, move the pointer, the page
scrolls. On Linux it is switched off, because middle click is already taken by
primary-selection paste.
+1 -1
View File
@@ -1,6 +1,6 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: middleclick-autoscroll
Source: https://github.com/Felitendo/middleclick-autoscroll
Source: https://github.com/LoonixTools/middleclick-autoscroll
Files: *
Copyright: 2026 Felitendo
+5 -5
View File
@@ -64,15 +64,15 @@
<h1>middleclick-autoscroll</h1>
<p class="lead">
Middle-click autoscroll — hold the middle mouse button, move the pointer, the
page scrolls — in every Chromium-based application on the system, and in
anything installed later.
Middle-click autoscroll in every Chromium-based application on the system,
and in anything installed later: hold the middle mouse button, move the
pointer, and the page scrolls.
</p>
<p>
This page is the package repository. Set it up once and every new version
arrives with the rest of your system updates. The
<a href="https://github.com/Felitendo/middleclick-autoscroll">source and the
<a href="https://github.com/LoonixTools/middleclick-autoscroll">source and the
documentation</a> are on GitHub.
</p>
@@ -103,7 +103,7 @@ sudo zypper install middleclick-autoscroll</code></pre>
<p>
That is the whole setup. Nothing else has to be configured and no file has to
be edited. Run <code>middleclick-autoscroll disable</code> before removing the
package — it puts back everything that was changed.
package. It puts back everything that was changed.
</p>
<footer>
+2 -2
View File
@@ -11,7 +11,7 @@
# Usage: publish-repos.sh <gh-pages checkout> <directory of new packages>
#
# Needs: dpkg-dev, apt-utils, createrepo-c, gpg, and a secret key already
# imported - its id is taken from the keyring.
# imported. Its id is taken from the keyring.
set -euo pipefail
@@ -19,7 +19,7 @@ pages="$(cd -- "$1" && pwd)"
incoming="$(cd -- "$2" && pwd)"
here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
base_url="${MCA_REPO_URL:-https://felitendo.github.io/middleclick-autoscroll}"
base_url="${MCA_REPO_URL:-https://loonixtools.github.io/middleclick-autoscroll}"
keyid="$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ { print $5; exit }')"
[[ -n $keyid ]] || { echo "$0: no secret key in the keyring" >&2; exit 1; }
+2 -2
View File
@@ -9,7 +9,7 @@ Release: 1%{?dist}
Summary: Middle-click autoscroll for Chromium-based applications
License: GPL-3.0-or-later
URL: https://github.com/Felitendo/middleclick-autoscroll
URL: https://github.com/LoonixTools/middleclick-autoscroll
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
@@ -33,7 +33,7 @@ Recommends: systemd
Recommends: desktop-file-utils
%description
Blink - the engine inside Chromium, Electron and CEF - has had Windows-style
Blink (the engine inside Chromium, Electron and CEF) has had Windows-style
autoscroll for years: hold the middle mouse button, move the pointer, the page
scrolls. On Linux it is switched off, because middle click is already taken by
primary-selection paste.
+268
View File
@@ -0,0 +1,268 @@
# Czech translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Czech\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "ZAPNUTO"
msgid "OFF"
msgstr "VYPNUTO"
msgid "on"
msgstr "zapnuto"
msgid "off"
msgstr "vypnuto"
msgid "(none)"
msgstr "(žádné)"
msgid "flag file"
msgstr "soubor s příznaky"
msgid "launcher"
msgstr "spouštěč"
msgid "profile"
msgstr "profil"
msgid "cannot tell"
msgstr "nelze určit"
msgid "not patched yet"
msgstr "zatím nepoužito"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Pokryté aplikace"
msgid "%d of %d"
msgstr "%d z %d"
msgid "Not identified"
msgstr "Nerozpoznáno"
msgid "%d (see the applications list)"
msgstr "%d (viz seznam aplikací)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Vkládání kolečkem"
msgid "New applications"
msgstr "Nové aplikace"
msgid "Last applied"
msgstr "Naposledy použito"
msgid "Applications pick this up the next time they are started."
msgstr "Aplikace to převezmou při příštím spuštění."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Zapnout nebo vypnout autoscroll"
msgid "Re-apply everything"
msgstr "Znovu použít vše"
msgid "Applications"
msgstr "Aplikace"
msgid "Settings"
msgstr "Nastavení"
msgid "Quit"
msgstr "Konec"
msgid "Press any key to continue..."
msgstr "Stiskněte libovolnou klávesu..."
msgid "One moment..."
msgstr "Okamžik..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Vypnout vkládání kolečkem"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr "Kolečko už jinde nevkládá. Platí od příštího přihlášení."
msgid "Cover new applications"
msgstr "Pokrýt nové aplikace"
msgid "Applications installed later get autoscroll on their own."
msgstr "Později nainstalované aplikace dostanou autoscroll samy."
msgid "More Chromium arguments"
msgstr "Další argumenty Chromia"
msgid "Passed to every application along with the autoscroll one."
msgstr "Předávají se každé aplikaci spolu s tím pro autoscroll."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Nahoru/Dolů: vybrat, Mezerník: změnit, q: zpět"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argumenty oddělené mezerami, prázdné pro žádné:"
msgid "Could not save the setting."
msgstr "Nastavení se nepodařilo uložit."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Procházím nainstalované aplikace..."
msgid "No supported applications found."
msgstr "Nenalezeny žádné podporované aplikace."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Co je označeno „nelze určit“, zůstane vypnuté, dokud to tu nezapnete."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll je vypnutý. Toto by bylo pokryto."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Nahoru/Dolů: vybrat, Mezerník: zapnout či vypnout, q: zpět"
msgid "Search:"
msgstr "Hledat:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Pište pro hledání, Dolů: do seznamu, Esc: zpět"
msgid "No matches."
msgstr "Žádné výsledky."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll je zapnutý. Aplikací: %d."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Již spuštěné aplikace to převezmou po restartu."
msgid "Could not start the watcher for newly installed applications."
msgstr "Sledování nových aplikací se nepodařilo spustit."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Chybí uživatelská relace systemd, takže nově nainstalované aplikace nebudou "
"zachyceny samy."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll je vypnutý a všechny změny byly vráceny."
msgid "Autoscroll is off. Turn it on first."
msgstr "Autoscroll je vypnutý. Nejdřív ho zapněte."
msgid "Applied again to %d applications."
msgstr "Znovu použito. Aplikací: %d."
msgid "Applied to %d applications."
msgstr "Použito. Aplikací: %d."
msgid "Already applied to %d applications."
msgstr "Už použito. Aplikací: %d."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam spouští své rozhraní způsobem, který tato verze nedokáže změnit, aniž "
"by si toho Steam všiml; Steam zůstává beze změny."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"Rozhraní Steamu nelze dát příznak bez souboru, který by Steam znovu "
"nainstaloval; změna byla vrácena."
msgid "Middle click stops pasting at the next login."
msgstr "Kolečko přestane vkládat od příštího přihlášení."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Použití: middleclick-autoscroll [příkaz]"
msgid "Commands:"
msgstr "Příkazy:"
msgid "Turn autoscroll on"
msgstr "Zapnout autoscroll"
msgid "Turn it off and put everything back"
msgstr "Vypnout a vše vrátit"
msgid "Apply to anything installed since the last run"
msgstr "Použít na vše nainstalované od posledního spuštění"
msgid "Take everything back and apply it again"
msgstr "Vše vrátit a použít znovu"
msgid "Show what is covered"
msgstr "Ukázat, co je pokryto"
msgid "List every application that was found"
msgstr "Vypsat všechny nalezené aplikace"
msgid "Show this help"
msgstr "Zobrazit tuto nápovědu"
msgid "Show the version"
msgstr "Zobrazit verzi"
msgid "Without a command an interactive menu is shown."
msgstr "Bez příkazu se zobrazí interaktivní nabídka."
msgid "Unknown command: %s"
msgstr "Neznámý příkaz: %s"
#. Time
msgid "never"
msgstr "nikdy"
msgid "just now"
msgstr "právě teď"
msgid "1 minute ago"
msgstr "před 1 minutou"
msgid "%d minutes ago"
msgstr "před %d min"
msgid "1 hour ago"
msgstr "před 1 hodinou"
msgid "%d hours ago"
msgstr "před %d h"
msgid "1 day ago"
msgstr "před 1 dnem"
msgid "%d days ago"
msgstr "před %d dny"
+81 -46
View File
@@ -4,10 +4,11 @@
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.0.3\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"
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: Felitendo\n"
"Language-Team: German\n"
"Language: de\n"
@@ -37,13 +38,15 @@ msgstr "Flag-Datei"
msgid "launcher"
msgstr "Startereintrag"
msgid "profile"
msgstr "Profil"
msgid "cannot tell"
msgstr "nicht erkennbar"
msgid "not patched yet"
msgstr "noch nicht gepatcht"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
@@ -57,12 +60,15 @@ msgstr "%d von %d"
msgid "Not identified"
msgstr "Nicht erkannt"
msgid "%d - see the applications list"
msgstr "%d – siehe Anwendungsliste"
msgid "%d (see the applications list)"
msgstr "%d (siehe Anwendungsliste)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Mittelklick-Einfügen"
msgid "New applications"
msgstr "Neue Anwendungen"
@@ -72,7 +78,6 @@ msgstr "Zuletzt angewendet"
msgid "Applications pick this up the next time they are started."
msgstr "Anwendungen übernehmen das beim nächsten Start."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Autoscroll ein- oder ausschalten"
@@ -92,34 +97,32 @@ msgstr "Beenden"
msgid "Press any key to continue..."
msgstr "Beliebige Taste drücken …"
msgid "One moment..."
msgstr "Einen Moment …"
#. 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 "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 changes - q goes back"
msgstr "Hoch/Runter wählt – Leertaste oder Rechts ändert – q geht 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:"
@@ -127,36 +130,49 @@ msgstr "Argumente mit Leerzeichen getrennt, leer für keine:"
msgid "Could not save the setting."
msgstr "Einstellung konnte nicht gespeichert werden."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Installierte Anwendungen werden durchgesehen …"
msgid "No Chromium-based applications found."
msgstr "Keine Chromium-basierten Anwendungen gefunden."
msgid "No supported applications found."
msgstr "Keine unterstützten Anwendungen gefunden."
msgid "Anything not identified is left alone until it is turned on here."
msgstr "Nicht erkannte Anwendungen bleiben unangetastet, bis sie hier eingeschaltet werden."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Was „nicht erkennbar“ ist, bleibt aus, bis es hier eingeschaltet wird."
msgid "Autoscroll is off - this is what would be covered."
msgstr "Autoscroll ist aus – das hier wäre abgedeckt."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll ist aus. Das hier wäre abgedeckt."
msgid "Up/Down select - Space turns one on or off - q goes back"
msgstr "Hoch/Runter wählt – Leertaste schaltet einzeln ein oder aus – q geht zurück"
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Hoch/Runter: wählen, Leertaste: ein- oder ausschalten, q: zurück"
msgid "Search:"
msgstr "Suchen:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Tippen zum Suchen, Runter: zur Liste, Esc: zurück"
msgid "No matches."
msgstr "Keine Treffer."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll ist für %d Anwendungen an."
msgid "Applications that are already running pick this up when they are restarted."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Bereits laufende Anwendungen übernehmen das nach einem Neustart."
msgid "Could not start the watcher for newly installed applications."
msgstr "Die Überwachung neu installierter Anwendungen konnte nicht gestartet werden."
msgstr ""
"Die Überwachung neu installierter Anwendungen konnte nicht gestartet werden."
msgid "No systemd user session - newly installed applications will not be picked up on their own."
msgstr "Keine systemd-Benutzersitzung – neu installierte Anwendungen werden nicht von selbst erfasst."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Keine systemd-Benutzersitzung, daher werden neu installierte Anwendungen "
"nicht von selbst erfasst."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll ist aus, alle Änderungen wurden zurückgenommen."
@@ -173,12 +189,23 @@ msgstr "Auf %d Anwendungen angewendet."
msgid "Already applied to %d applications."
msgstr "Bereits auf %d Anwendungen angewendet."
msgid "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone."
msgstr "Steam startet seine Oberfläche auf eine Art, die diese Version nicht ändern kann, ohne dass Steam es merkt; Steam bleibt unangetastet."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam startet seine Oberfläche auf eine Art, die diese Version nicht ändern "
"kann, ohne dass Steam es merkt; Steam bleibt unangetastet."
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 ""
"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]"
@@ -217,7 +244,6 @@ msgstr "Ohne Befehl wird ein interaktives Menü angezeigt."
msgid "Unknown command: %s"
msgstr "Unbekannter Befehl: %s"
#. Time
msgid "never"
msgstr "nie"
@@ -225,11 +251,20 @@ msgstr "nie"
msgid "just now"
msgstr "gerade eben"
msgid "1 minute ago"
msgstr "vor 1 Minute"
msgid "%d minutes ago"
msgstr "vor %d Minuten"
msgid "1 hour ago"
msgstr "vor 1 Stunde"
msgid "%d hours ago"
msgstr "vor %d Stunden"
msgid "1 day ago"
msgstr "vor 1 Tag"
msgid "%d days ago"
msgstr "vor %d Tagen"
+269
View File
@@ -0,0 +1,269 @@
# Spanish translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "ACTIVADO"
msgid "OFF"
msgstr "DESACTIVADO"
msgid "on"
msgstr "activado"
msgid "off"
msgstr "desactivado"
msgid "(none)"
msgstr "(ninguno)"
msgid "flag file"
msgstr "archivo de flags"
msgid "launcher"
msgstr "lanzador"
msgid "profile"
msgstr "perfil"
msgid "cannot tell"
msgstr "no se sabe"
msgid "not patched yet"
msgstr "aún sin aplicar"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Aplicaciones cubiertas"
msgid "%d of %d"
msgstr "%d de %d"
msgid "Not identified"
msgstr "Sin identificar"
msgid "%d (see the applications list)"
msgstr "%d (ver la lista de aplicaciones)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Pegar con clic central"
msgid "New applications"
msgstr "Aplicaciones nuevas"
msgid "Last applied"
msgstr "Aplicado por última vez"
msgid "Applications pick this up the next time they are started."
msgstr "Las aplicaciones lo toman la próxima vez que se inicien."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Activar o desactivar autoscroll"
msgid "Re-apply everything"
msgstr "Volver a aplicar todo"
msgid "Applications"
msgstr "Aplicaciones"
msgid "Settings"
msgstr "Ajustes"
msgid "Quit"
msgstr "Salir"
msgid "Press any key to continue..."
msgstr "Pulsa cualquier tecla para continuar..."
msgid "One moment..."
msgstr "Un momento..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Desactivar pegar con clic central"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"El clic central ya no pega en ningún otro sitio. Desde el próximo inicio."
msgid "Cover new applications"
msgstr "Cubrir aplicaciones nuevas"
msgid "Applications installed later get autoscroll on their own."
msgstr "Las aplicaciones instaladas más tarde reciben autoscroll solas."
msgid "More Chromium arguments"
msgstr "Más argumentos de Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Se pasan a cada aplicación junto con el de autoscroll."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Arriba/Abajo: elegir, Espacio: cambiar, q: volver"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argumentos separados por espacios, vacío para ninguno:"
msgid "Could not save the setting."
msgstr "No se pudo guardar el ajuste."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Revisando las aplicaciones instaladas..."
msgid "No supported applications found."
msgstr "No se encontraron aplicaciones compatibles."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "Lo marcado como «no se sabe» sigue desactivado hasta activarlo aquí."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll está desactivado. Esto es lo que se cubriría."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Arriba/Abajo: elegir, Espacio: activar o desactivar, q: volver"
msgid "Search:"
msgstr "Buscar:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Escribe para buscar, Abajo: ir a la lista, Esc: volver"
msgid "No matches."
msgstr "Sin resultados."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll está activado para %d aplicaciones."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Las aplicaciones que ya están abiertas lo toman al reiniciarse."
msgid "Could not start the watcher for newly installed applications."
msgstr "No se pudo iniciar la vigilancia de aplicaciones nuevas."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"No hay sesión de usuario de systemd, así que las aplicaciones nuevas no se "
"detectarán solas."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll está desactivado y todo lo cambiado se ha restaurado."
msgid "Autoscroll is off. Turn it on first."
msgstr "Autoscroll está desactivado. Actívalo primero."
msgid "Applied again to %d applications."
msgstr "Aplicado de nuevo a %d aplicaciones."
msgid "Applied to %d applications."
msgstr "Aplicado a %d aplicaciones."
msgid "Already applied to %d applications."
msgstr "Ya aplicado a %d aplicaciones."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam inicia su interfaz de una forma que esta versión no puede cambiar sin "
"que Steam lo note; se deja como está."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"No se puede dar el flag a la interfaz de Steam sin dejar un archivo que "
"Steam reinstalaría; el cambio se ha deshecho."
msgid "Middle click stops pasting at the next login."
msgstr "El clic central dejará de pegar en el próximo inicio de sesión."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Uso: middleclick-autoscroll [comando]"
msgid "Commands:"
msgstr "Comandos:"
msgid "Turn autoscroll on"
msgstr "Activar autoscroll"
msgid "Turn it off and put everything back"
msgstr "Desactivarlo y restaurar todo"
msgid "Apply to anything installed since the last run"
msgstr "Aplicar a todo lo instalado desde la última vez"
msgid "Take everything back and apply it again"
msgstr "Deshacer todo y volver a aplicarlo"
msgid "Show what is covered"
msgstr "Mostrar qué está cubierto"
msgid "List every application that was found"
msgstr "Listar todas las aplicaciones encontradas"
msgid "Show this help"
msgstr "Mostrar esta ayuda"
msgid "Show the version"
msgstr "Mostrar la versión"
msgid "Without a command an interactive menu is shown."
msgstr "Sin comando se muestra un menú interactivo."
msgid "Unknown command: %s"
msgstr "Comando desconocido: %s"
#. Time
msgid "never"
msgstr "nunca"
msgid "just now"
msgstr "ahora mismo"
msgid "1 minute ago"
msgstr "hace 1 minuto"
msgid "%d minutes ago"
msgstr "hace %d minutos"
msgid "1 hour ago"
msgstr "hace 1 hora"
msgid "%d hours ago"
msgstr "hace %d horas"
msgid "1 day ago"
msgstr "hace 1 día"
msgid "%d days ago"
msgstr "hace %d días"
+272
View File
@@ -0,0 +1,272 @@
# French translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "ACTIVÉ"
msgid "OFF"
msgstr "DÉSACTIVÉ"
msgid "on"
msgstr "activé"
msgid "off"
msgstr "désactivé"
msgid "(none)"
msgstr "(aucun)"
msgid "flag file"
msgstr "fichier de flags"
msgid "launcher"
msgstr "lanceur"
msgid "profile"
msgstr "profil"
msgid "cannot tell"
msgstr "indéterminé"
msgid "not patched yet"
msgstr "pas encore appliqué"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Applications couvertes"
msgid "%d of %d"
msgstr "%d sur %d"
msgid "Not identified"
msgstr "Non identifiées"
msgid "%d (see the applications list)"
msgstr "%d (voir la liste des applications)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Collage au clic du milieu"
msgid "New applications"
msgstr "Nouvelles applications"
msgid "Last applied"
msgstr "Dernier passage"
msgid "Applications pick this up the next time they are started."
msgstr "Les applications le prennent en compte à leur prochain démarrage."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Activer ou désactiver l'autoscroll"
msgid "Re-apply everything"
msgstr "Tout réappliquer"
msgid "Applications"
msgstr "Applications"
msgid "Settings"
msgstr "Paramètres"
msgid "Quit"
msgstr "Quitter"
msgid "Press any key to continue..."
msgstr "Appuyez sur une touche pour continuer..."
msgid "One moment..."
msgstr "Un instant..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Désactiver le collage au clic du milieu"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr "Le clic du milieu ne colle plus ailleurs. Dès la prochaine connexion."
msgid "Cover new applications"
msgstr "Couvrir les nouvelles applications"
msgid "Applications installed later get autoscroll on their own."
msgstr ""
"Les applications installées plus tard reçoivent l'autoscroll d'elles-mêmes."
msgid "More Chromium arguments"
msgstr "Autres arguments Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Transmis à chaque application avec celui de l'autoscroll."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Haut/Bas : choisir, Espace : changer, q : retour"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Arguments séparés par des espaces, vide pour aucun :"
msgid "Could not save the setting."
msgstr "Impossible d'enregistrer le paramètre."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Examen des applications installées..."
msgid "No supported applications found."
msgstr "Aucune application prise en charge trouvée."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Ce qui est marqué « indéterminé » reste désactivé jusqu'à être activé ici."
msgid "Autoscroll is off. This is what would be covered."
msgstr "L'autoscroll est désactivé. Voici ce qui serait couvert."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Haut/Bas : choisir, Espace : activer ou désactiver, q : retour"
msgid "Search:"
msgstr "Rechercher :"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Tapez pour rechercher, Bas : aller à la liste, Échap : retour"
msgid "No matches."
msgstr "Aucun résultat."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "L'autoscroll est activé pour %d applications."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr ""
"Les applications déjà ouvertes le prennent en compte après un redémarrage."
msgid "Could not start the watcher for newly installed applications."
msgstr "Impossible de démarrer la surveillance des nouvelles applications."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Pas de session utilisateur systemd, les nouvelles applications ne seront "
"donc pas prises en charge d'elles-mêmes."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr ""
"L'autoscroll est désactivé et tout ce qui avait été modifié a été rétabli."
msgid "Autoscroll is off. Turn it on first."
msgstr "L'autoscroll est désactivé. Activez-le d'abord."
msgid "Applied again to %d applications."
msgstr "Réappliqué à %d applications."
msgid "Applied to %d applications."
msgstr "Appliqué à %d applications."
msgid "Already applied to %d applications."
msgstr "Déjà appliqué à %d applications."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam lance son interface d'une façon que cette version ne peut pas modifier "
"sans que Steam le remarque ; Steam reste tel quel."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"L'interface de Steam ne peut pas recevoir le flag sans laisser un fichier "
"que Steam réinstallerait ; la modification a été annulée."
msgid "Middle click stops pasting at the next login."
msgstr "Le clic du milieu ne collera plus à la prochaine connexion."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Utilisation : middleclick-autoscroll [commande]"
msgid "Commands:"
msgstr "Commandes :"
msgid "Turn autoscroll on"
msgstr "Activer l'autoscroll"
msgid "Turn it off and put everything back"
msgstr "Le désactiver et tout rétablir"
msgid "Apply to anything installed since the last run"
msgstr "Appliquer à tout ce qui a été installé depuis le dernier passage"
msgid "Take everything back and apply it again"
msgstr "Tout annuler et réappliquer"
msgid "Show what is covered"
msgstr "Afficher ce qui est couvert"
msgid "List every application that was found"
msgstr "Lister toutes les applications trouvées"
msgid "Show this help"
msgstr "Afficher cette aide"
msgid "Show the version"
msgstr "Afficher la version"
msgid "Without a command an interactive menu is shown."
msgstr "Sans commande, un menu interactif s'affiche."
msgid "Unknown command: %s"
msgstr "Commande inconnue : %s"
#. Time
msgid "never"
msgstr "jamais"
msgid "just now"
msgstr "à l'instant"
msgid "1 minute ago"
msgstr "il y a 1 minute"
msgid "%d minutes ago"
msgstr "il y a %d minutes"
msgid "1 hour ago"
msgstr "il y a 1 heure"
msgid "%d hours ago"
msgstr "il y a %d heures"
msgid "1 day ago"
msgstr "il y a 1 jour"
msgid "%d days ago"
msgstr "il y a %d jours"
+272
View File
@@ -0,0 +1,272 @@
# Italian translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Italian\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "ACCESO"
msgid "OFF"
msgstr "SPENTO"
msgid "on"
msgstr "acceso"
msgid "off"
msgstr "spento"
msgid "(none)"
msgstr "(nessuno)"
msgid "flag file"
msgstr "file di flag"
msgid "launcher"
msgstr "lanciatore"
msgid "profile"
msgstr "profilo"
msgid "cannot tell"
msgstr "incerto"
msgid "not patched yet"
msgstr "non ancora applicato"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Applicazioni coperte"
msgid "%d of %d"
msgstr "%d di %d"
msgid "Not identified"
msgstr "Non identificate"
msgid "%d (see the applications list)"
msgstr "%d (vedi l'elenco delle applicazioni)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Incolla con clic centrale"
msgid "New applications"
msgstr "Nuove applicazioni"
msgid "Last applied"
msgstr "Applicato l'ultima volta"
msgid "Applications pick this up the next time they are started."
msgstr "Le applicazioni lo recepiscono al prossimo avvio."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Accendi o spegni l'autoscroll"
msgid "Re-apply everything"
msgstr "Riapplica tutto"
msgid "Applications"
msgstr "Applicazioni"
msgid "Settings"
msgstr "Impostazioni"
msgid "Quit"
msgstr "Esci"
msgid "Press any key to continue..."
msgstr "Premi un tasto per continuare..."
msgid "One moment..."
msgstr "Un momento..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Disattiva incolla con clic centrale"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"Il clic centrale non incolla più da nessun'altra parte. Dal prossimo accesso."
msgid "Cover new applications"
msgstr "Copri le nuove applicazioni"
msgid "Applications installed later get autoscroll on their own."
msgstr "Le applicazioni installate in seguito ricevono l'autoscroll da sole."
msgid "More Chromium arguments"
msgstr "Altri argomenti di Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Passati a ogni applicazione insieme a quello per l'autoscroll."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Su/Giù: scegli, Spazio: cambia, q: indietro"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argomenti separati da spazi, vuoto per nessuno:"
msgid "Could not save the setting."
msgstr "Impossibile salvare l'impostazione."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Esame delle applicazioni installate..."
msgid "No supported applications found."
msgstr "Nessuna applicazione supportata trovata."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Ciò che è segnato come «incerto» resta spento finché non viene acceso qui."
msgid "Autoscroll is off. This is what would be covered."
msgstr "L'autoscroll è spento. Ecco cosa verrebbe coperto."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Su/Giù: scegli, Spazio: accendi o spegni, q: indietro"
msgid "Search:"
msgstr "Cerca:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Scrivi per cercare, Giù: vai all'elenco, Esc: indietro"
msgid "No matches."
msgstr "Nessun risultato."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "L'autoscroll è acceso per %d applicazioni."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Le applicazioni già aperte lo recepiscono dopo un riavvio."
msgid "Could not start the watcher for newly installed applications."
msgstr "Impossibile avviare il controllo delle nuove applicazioni."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Nessuna sessione utente di systemd, quindi le nuove applicazioni non "
"verranno rilevate da sole."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr ""
"L'autoscroll è spento e tutto ciò che era stato modificato è stato "
"ripristinato."
msgid "Autoscroll is off. Turn it on first."
msgstr "L'autoscroll è spento. Prima accendilo."
msgid "Applied again to %d applications."
msgstr "Riapplicato a %d applicazioni."
msgid "Applied to %d applications."
msgstr "Applicato a %d applicazioni."
msgid "Already applied to %d applications."
msgstr "Già applicato a %d applicazioni."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam avvia la sua interfaccia in un modo che questa versione non può "
"cambiare senza che Steam se ne accorga; Steam resta com'è."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"All'interfaccia di Steam non si può dare il flag senza lasciare un file che "
"Steam reinstallerebbe; la modifica è stata annullata."
msgid "Middle click stops pasting at the next login."
msgstr "Il clic centrale smetterà di incollare al prossimo accesso."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Uso: middleclick-autoscroll [comando]"
msgid "Commands:"
msgstr "Comandi:"
msgid "Turn autoscroll on"
msgstr "Accendi l'autoscroll"
msgid "Turn it off and put everything back"
msgstr "Spegnilo e ripristina tutto"
msgid "Apply to anything installed since the last run"
msgstr "Applica a tutto ciò che è stato installato dall'ultima volta"
msgid "Take everything back and apply it again"
msgstr "Annulla tutto e riapplica"
msgid "Show what is covered"
msgstr "Mostra cosa è coperto"
msgid "List every application that was found"
msgstr "Elenca tutte le applicazioni trovate"
msgid "Show this help"
msgstr "Mostra questo aiuto"
msgid "Show the version"
msgstr "Mostra la versione"
msgid "Without a command an interactive menu is shown."
msgstr "Senza comando viene mostrato un menu interattivo."
msgid "Unknown command: %s"
msgstr "Comando sconosciuto: %s"
#. Time
msgid "never"
msgstr "mai"
msgid "just now"
msgstr "proprio ora"
msgid "1 minute ago"
msgstr "1 minuto fa"
msgid "%d minutes ago"
msgstr "%d minuti fa"
msgid "1 hour ago"
msgstr "1 ora fa"
msgid "%d hours ago"
msgstr "%d ore fa"
msgid "1 day ago"
msgstr "1 giorno fa"
msgid "%d days ago"
msgstr "%d giorni fa"
+55 -29
View File
@@ -4,9 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.0.3\n"
"Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n"
"POT-Creation-Date: 2026-08-16 00:00+0200\n"
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -37,13 +38,15 @@ msgstr ""
msgid "launcher"
msgstr ""
msgid "profile"
msgstr ""
msgid "cannot tell"
msgstr ""
msgid "not patched yet"
msgstr ""
#. Status block
msgid "Autoscroll"
msgstr ""
@@ -57,12 +60,15 @@ msgstr ""
msgid "Not identified"
msgstr ""
msgid "%d - see the applications list"
msgid "%d (see the applications list)"
msgstr ""
msgid "Steam"
msgstr ""
msgid "Middle-click paste"
msgstr ""
msgid "New applications"
msgstr ""
@@ -72,7 +78,6 @@ msgstr ""
msgid "Applications pick this up the next time they are started."
msgstr ""
#. Menu
msgid "Turn autoscroll on or off"
msgstr ""
@@ -92,33 +97,30 @@ msgstr ""
msgid "Press any key to continue..."
msgstr ""
msgid "One moment..."
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 "Apply to newly installed applications"
msgstr ""
msgid "Additional Chromium arguments"
msgstr ""
msgid "Up/Down select - Space or Right changes - q goes back"
msgid "Up/Down: select, Space: change, q: back"
msgstr ""
msgid "Arguments separated by spaces, empty for none:"
@@ -127,35 +129,45 @@ msgstr ""
msgid "Could not save the setting."
msgstr ""
#. Applications screen
msgid "Looking at the installed applications..."
msgstr ""
msgid "No Chromium-based applications found."
msgid "No supported applications found."
msgstr ""
msgid "Anything not identified is left alone until it is turned on here."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
msgid "Autoscroll is off - this is what would be covered."
msgid "Autoscroll is off. This is what would be covered."
msgstr ""
msgid "Up/Down select - Space turns one on or off - q goes back"
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr ""
msgid "Search:"
msgstr ""
msgid "Type to search, Down: go to the list, Esc: back"
msgstr ""
msgid "No matches."
msgstr ""
#. Results
msgid "Autoscroll is on for %d applications."
msgstr ""
msgid "Applications that are already running pick this up when they are restarted."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr ""
msgid "Could not start the watcher for newly installed applications."
msgstr ""
msgid "No systemd user session - newly installed applications will not be picked up on their own."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
msgid "Autoscroll is off and everything that was changed has been put back."
@@ -173,12 +185,18 @@ msgstr ""
msgid "Already applied to %d applications."
msgstr ""
msgid "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
msgid "Steam's interface cannot be given the flag without leaving a file Steam would reinstall; the change has been taken back."
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]"
@@ -217,7 +235,6 @@ msgstr ""
msgid "Unknown command: %s"
msgstr ""
#. Time
msgid "never"
msgstr ""
@@ -225,11 +242,20 @@ msgstr ""
msgid "just now"
msgstr ""
msgid "1 minute ago"
msgstr ""
msgid "%d minutes ago"
msgstr ""
msgid "1 hour ago"
msgstr ""
msgid "%d hours ago"
msgstr ""
msgid "1 day ago"
msgstr ""
msgid "%d days ago"
msgstr ""
+270
View File
@@ -0,0 +1,270 @@
# Dutch translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Dutch\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "AAN"
msgid "OFF"
msgstr "UIT"
msgid "on"
msgstr "aan"
msgid "off"
msgstr "uit"
msgid "(none)"
msgstr "(geen)"
msgid "flag file"
msgstr "flagbestand"
msgid "launcher"
msgstr "starter"
msgid "profile"
msgstr "profiel"
msgid "cannot tell"
msgstr "onbekend"
msgid "not patched yet"
msgstr "nog niet toegepast"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Gedekte toepassingen"
msgid "%d of %d"
msgstr "%d van %d"
msgid "Not identified"
msgstr "Niet herkend"
msgid "%d (see the applications list)"
msgstr "%d (zie de lijst met toepassingen)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Plakken met middelklik"
msgid "New applications"
msgstr "Nieuwe toepassingen"
msgid "Last applied"
msgstr "Laatst toegepast"
msgid "Applications pick this up the next time they are started."
msgstr "Toepassingen nemen dit over bij de volgende start."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Autoscroll aan- of uitzetten"
msgid "Re-apply everything"
msgstr "Alles opnieuw toepassen"
msgid "Applications"
msgstr "Toepassingen"
msgid "Settings"
msgstr "Instellingen"
msgid "Quit"
msgstr "Afsluiten"
msgid "Press any key to continue..."
msgstr "Druk op een toets om door te gaan..."
msgid "One moment..."
msgstr "Een moment..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Plakken met middelklik uitzetten"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"Middelklik plakt dan nergens anders meer. Vanaf de volgende aanmelding."
msgid "Cover new applications"
msgstr "Nieuwe toepassingen meenemen"
msgid "Applications installed later get autoscroll on their own."
msgstr "Later geïnstalleerde toepassingen krijgen vanzelf autoscroll."
msgid "More Chromium arguments"
msgstr "Meer Chromium-argumenten"
msgid "Passed to every application along with the autoscroll one."
msgstr "Gaan samen met dat voor autoscroll naar elke toepassing."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Omhoog/Omlaag: kiezen, Spatie: wijzigen, q: terug"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argumenten gescheiden door spaties, leeg voor geen:"
msgid "Could not save the setting."
msgstr "De instelling kon niet worden opgeslagen."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Geïnstalleerde toepassingen worden bekeken..."
msgid "No supported applications found."
msgstr "Geen ondersteunde toepassingen gevonden."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Wat als \"onbekend\" gemarkeerd is, blijft uit tot het hier aangezet wordt."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll staat uit. Dit zou gedekt worden."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Omhoog/Omlaag: kiezen, Spatie: aan- of uitzetten, q: terug"
msgid "Search:"
msgstr "Zoeken:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Typ om te zoeken, Omlaag: naar de lijst, Esc: terug"
msgid "No matches."
msgstr "Geen resultaten."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll staat aan voor %d toepassingen."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Toepassingen die al draaien, nemen dit over na een herstart."
msgid "Could not start the watcher for newly installed applications."
msgstr "Kon het bewaken van nieuwe toepassingen niet starten."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Geen systemd-gebruikerssessie, dus nieuw geïnstalleerde toepassingen worden "
"niet vanzelf opgepikt."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll staat uit en alles wat veranderd was, is teruggezet."
msgid "Autoscroll is off. Turn it on first."
msgstr "Autoscroll staat uit. Zet het eerst aan."
msgid "Applied again to %d applications."
msgstr "Opnieuw toegepast op %d toepassingen."
msgid "Applied to %d applications."
msgstr "Toegepast op %d toepassingen."
msgid "Already applied to %d applications."
msgstr "Al toegepast op %d toepassingen."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam start zijn interface op een manier die deze versie niet kan veranderen "
"zonder dat Steam het merkt; Steam blijft zoals het is."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"De interface van Steam kan de flag niet krijgen zonder een bestand achter te "
"laten dat Steam opnieuw zou installeren; de wijziging is teruggedraaid."
msgid "Middle click stops pasting at the next login."
msgstr "Middelklik plakt vanaf de volgende aanmelding niet meer."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Gebruik: middleclick-autoscroll [opdracht]"
msgid "Commands:"
msgstr "Opdrachten:"
msgid "Turn autoscroll on"
msgstr "Autoscroll aanzetten"
msgid "Turn it off and put everything back"
msgstr "Uitzetten en alles terugzetten"
msgid "Apply to anything installed since the last run"
msgstr "Toepassen op alles wat sinds de vorige keer geïnstalleerd is"
msgid "Take everything back and apply it again"
msgstr "Alles terugdraaien en opnieuw toepassen"
msgid "Show what is covered"
msgstr "Tonen wat gedekt is"
msgid "List every application that was found"
msgstr "Alle gevonden toepassingen tonen"
msgid "Show this help"
msgstr "Deze hulp tonen"
msgid "Show the version"
msgstr "De versie tonen"
msgid "Without a command an interactive menu is shown."
msgstr "Zonder opdracht wordt een interactief menu getoond."
msgid "Unknown command: %s"
msgstr "Onbekende opdracht: %s"
#. Time
msgid "never"
msgstr "nooit"
msgid "just now"
msgstr "zojuist"
msgid "1 minute ago"
msgstr "1 minuut geleden"
msgid "%d minutes ago"
msgstr "%d minuten geleden"
msgid "1 hour ago"
msgstr "1 uur geleden"
msgid "%d hours ago"
msgstr "%d uur geleden"
msgid "1 day ago"
msgstr "1 dag geleden"
msgid "%d days ago"
msgstr "%d dagen geleden"
+269
View File
@@ -0,0 +1,269 @@
# Polish translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Polish\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "WŁ."
msgid "OFF"
msgstr "WYŁ."
msgid "on"
msgstr "wł."
msgid "off"
msgstr "wył."
msgid "(none)"
msgstr "(brak)"
msgid "flag file"
msgstr "plik flag"
msgid "launcher"
msgstr "skrót"
msgid "profile"
msgstr "profil"
msgid "cannot tell"
msgstr "nie wiadomo"
msgid "not patched yet"
msgstr "jeszcze nie zastosowano"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Objęte aplikacje"
msgid "%d of %d"
msgstr "%d z %d"
msgid "Not identified"
msgstr "Nierozpoznane"
msgid "%d (see the applications list)"
msgstr "%d (zob. listę aplikacji)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Wklejanie kółkiem myszy"
msgid "New applications"
msgstr "Nowe aplikacje"
msgid "Last applied"
msgstr "Ostatnio zastosowano"
msgid "Applications pick this up the next time they are started."
msgstr "Aplikacje przejmą to przy następnym uruchomieniu."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Włącz lub wyłącz autoscroll"
msgid "Re-apply everything"
msgstr "Zastosuj wszystko ponownie"
msgid "Applications"
msgstr "Aplikacje"
msgid "Settings"
msgstr "Ustawienia"
msgid "Quit"
msgstr "Zakończ"
msgid "Press any key to continue..."
msgstr "Naciśnij dowolny klawisz, aby kontynuować..."
msgid "One moment..."
msgstr "Chwileczkę..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Wyłącz wklejanie kółkiem myszy"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr "Kółko myszy nie wkleja już nigdzie indziej. Od następnego logowania."
msgid "Cover new applications"
msgstr "Obejmuj nowe aplikacje"
msgid "Applications installed later get autoscroll on their own."
msgstr "Aplikacje zainstalowane później same dostaną autoscroll."
msgid "More Chromium arguments"
msgstr "Więcej argumentów Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Przekazywane każdej aplikacji razem z tym od autoscrolla."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Góra/Dół: wybierz, Spacja: zmień, q: wstecz"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argumenty oddzielone spacjami, puste oznacza brak:"
msgid "Could not save the setting."
msgstr "Nie udało się zapisać ustawienia."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Przeglądanie zainstalowanych aplikacji..."
msgid "No supported applications found."
msgstr "Nie znaleziono obsługiwanych aplikacji."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Pozycje „nie wiadomo” zostają wyłączone, dopóki nie włączysz ich tutaj."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll jest wyłączony. Tyle zostałoby objęte."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Góra/Dół: wybierz, Spacja: włącz lub wyłącz, q: wstecz"
msgid "Search:"
msgstr "Szukaj:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Pisz, aby szukać, Dół: do listy, Esc: wstecz"
msgid "No matches."
msgstr "Brak wyników."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll włączony dla %d aplikacji."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Uruchomione już aplikacje przejmą to po ponownym uruchomieniu."
msgid "Could not start the watcher for newly installed applications."
msgstr "Nie udało się uruchomić obserwowania nowych aplikacji."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Brak sesji użytkownika systemd, więc nowo zainstalowane aplikacje nie "
"zostaną wykryte same."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll jest wyłączony, a wszystkie zmiany zostały cofnięte."
msgid "Autoscroll is off. Turn it on first."
msgstr "Autoscroll jest wyłączony. Najpierw go włącz."
msgid "Applied again to %d applications."
msgstr "Ponownie zastosowano dla %d aplikacji."
msgid "Applied to %d applications."
msgstr "Zastosowano dla %d aplikacji."
msgid "Already applied to %d applications."
msgstr "Już zastosowano dla %d aplikacji."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam uruchamia swój interfejs w sposób, którego ta wersja nie może zmienić "
"bez wiedzy Steama; Steam pozostaje bez zmian."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"Interfejsowi Steama nie można dodać flagi bez zostawienia pliku, który Steam "
"zainstalowałby ponownie; zmiana została cofnięta."
msgid "Middle click stops pasting at the next login."
msgstr "Kółko myszy przestanie wklejać od następnego logowania."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Użycie: middleclick-autoscroll [polecenie]"
msgid "Commands:"
msgstr "Polecenia:"
msgid "Turn autoscroll on"
msgstr "Włącz autoscroll"
msgid "Turn it off and put everything back"
msgstr "Wyłącz i cofnij wszystko"
msgid "Apply to anything installed since the last run"
msgstr "Zastosuj do wszystkiego zainstalowanego od ostatniego razu"
msgid "Take everything back and apply it again"
msgstr "Cofnij wszystko i zastosuj ponownie"
msgid "Show what is covered"
msgstr "Pokaż, co jest objęte"
msgid "List every application that was found"
msgstr "Wypisz wszystkie znalezione aplikacje"
msgid "Show this help"
msgstr "Pokaż tę pomoc"
msgid "Show the version"
msgstr "Pokaż wersję"
msgid "Without a command an interactive menu is shown."
msgstr "Bez polecenia wyświetlane jest menu interaktywne."
msgid "Unknown command: %s"
msgstr "Nieznane polecenie: %s"
#. Time
msgid "never"
msgstr "nigdy"
msgid "just now"
msgstr "przed chwilą"
msgid "1 minute ago"
msgstr "1 minutę temu"
msgid "%d minutes ago"
msgstr "%d min temu"
msgid "1 hour ago"
msgstr "1 godzinę temu"
msgid "%d hours ago"
msgstr "%d godz. temu"
msgid "1 day ago"
msgstr "1 dzień temu"
msgid "%d days ago"
msgstr "%d dni temu"
+271
View File
@@ -0,0 +1,271 @@
# Brazilian Portuguese translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Brazilian Portuguese\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "LIGADO"
msgid "OFF"
msgstr "DESLIGADO"
msgid "on"
msgstr "ligado"
msgid "off"
msgstr "desligado"
msgid "(none)"
msgstr "(nenhum)"
msgid "flag file"
msgstr "arquivo de flags"
msgid "launcher"
msgstr "lançador"
msgid "profile"
msgstr "perfil"
msgid "cannot tell"
msgstr "indeterminado"
msgid "not patched yet"
msgstr "ainda não aplicado"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Aplicativos cobertos"
msgid "%d of %d"
msgstr "%d de %d"
msgid "Not identified"
msgstr "Não identificados"
msgid "%d (see the applications list)"
msgstr "%d (veja a lista de aplicativos)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Colar com clique do meio"
msgid "New applications"
msgstr "Novos aplicativos"
msgid "Last applied"
msgstr "Aplicado pela última vez"
msgid "Applications pick this up the next time they are started."
msgstr "Os aplicativos passam a usar isso na próxima vez que forem abertos."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Ligar ou desligar o autoscroll"
msgid "Re-apply everything"
msgstr "Aplicar tudo de novo"
msgid "Applications"
msgstr "Aplicativos"
msgid "Settings"
msgstr "Configurações"
msgid "Quit"
msgstr "Sair"
msgid "Press any key to continue..."
msgstr "Pressione qualquer tecla para continuar..."
msgid "One moment..."
msgstr "Um momento..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Desligar colar com clique do meio"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"O clique do meio para de colar em outros lugares. A partir do próximo login."
msgid "Cover new applications"
msgstr "Cobrir novos aplicativos"
msgid "Applications installed later get autoscroll on their own."
msgstr "Aplicativos instalados depois recebem o autoscroll sozinhos."
msgid "More Chromium arguments"
msgstr "Mais argumentos do Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Passados a todo aplicativo junto com o do autoscroll."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Cima/Baixo: escolher, Espaço: mudar, q: voltar"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Argumentos separados por espaços, vazio para nenhum:"
msgid "Could not save the setting."
msgstr "Não foi possível salvar a configuração."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Verificando os aplicativos instalados..."
msgid "No supported applications found."
msgstr "Nenhum aplicativo compatível encontrado."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"O que está marcado como \"indeterminado\" fica desligado até ser ligado aqui."
msgid "Autoscroll is off. This is what would be covered."
msgstr "O autoscroll está desligado. Isto é o que seria coberto."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Cima/Baixo: escolher, Espaço: ligar ou desligar, q: voltar"
msgid "Search:"
msgstr "Buscar:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Digite para buscar, Baixo: ir para a lista, Esc: voltar"
msgid "No matches."
msgstr "Nenhum resultado."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "O autoscroll está ligado para %d aplicativos."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr ""
"Aplicativos que já estão abertos passam a usar isso ao serem reiniciados."
msgid "Could not start the watcher for newly installed applications."
msgstr "Não foi possível iniciar o monitoramento de novos aplicativos."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Sem sessão de usuário do systemd, então novos aplicativos não serão "
"detectados sozinhos."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "O autoscroll está desligado e tudo o que foi alterado foi restaurado."
msgid "Autoscroll is off. Turn it on first."
msgstr "O autoscroll está desligado. Ligue-o primeiro."
msgid "Applied again to %d applications."
msgstr "Aplicado de novo a %d aplicativos."
msgid "Applied to %d applications."
msgstr "Aplicado a %d aplicativos."
msgid "Already applied to %d applications."
msgstr "Já aplicado a %d aplicativos."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"O Steam inicia sua interface de um jeito que esta versão não consegue mudar "
"sem que o Steam perceba; ele fica como está."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"A interface do Steam não pode receber o flag sem deixar um arquivo que o "
"Steam reinstalaria; a alteração foi desfeita."
msgid "Middle click stops pasting at the next login."
msgstr "O clique do meio para de colar no próximo login."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Uso: middleclick-autoscroll [comando]"
msgid "Commands:"
msgstr "Comandos:"
msgid "Turn autoscroll on"
msgstr "Ligar o autoscroll"
msgid "Turn it off and put everything back"
msgstr "Desligar e restaurar tudo"
msgid "Apply to anything installed since the last run"
msgstr "Aplicar a tudo o que foi instalado desde a última vez"
msgid "Take everything back and apply it again"
msgstr "Desfazer tudo e aplicar de novo"
msgid "Show what is covered"
msgstr "Mostrar o que está coberto"
msgid "List every application that was found"
msgstr "Listar todos os aplicativos encontrados"
msgid "Show this help"
msgstr "Mostrar esta ajuda"
msgid "Show the version"
msgstr "Mostrar a versão"
msgid "Without a command an interactive menu is shown."
msgstr "Sem comando, um menu interativo é exibido."
msgid "Unknown command: %s"
msgstr "Comando desconhecido: %s"
#. Time
msgid "never"
msgstr "nunca"
msgid "just now"
msgstr "agora mesmo"
msgid "1 minute ago"
msgstr "há 1 minuto"
msgid "%d minutes ago"
msgstr "há %d minutos"
msgid "1 hour ago"
msgstr "há 1 hora"
msgid "%d hours ago"
msgstr "há %d horas"
msgid "1 day ago"
msgstr "há 1 dia"
msgid "%d days ago"
msgstr "há %d dias"
+270
View File
@@ -0,0 +1,270 @@
# Russian translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "ВКЛ"
msgid "OFF"
msgstr "ВЫКЛ"
msgid "on"
msgstr "вкл"
msgid "off"
msgstr "выкл"
msgid "(none)"
msgstr "(нет)"
msgid "flag file"
msgstr "файл флагов"
msgid "launcher"
msgstr "ярлык"
msgid "profile"
msgstr "профиль"
msgid "cannot tell"
msgstr "неизвестно"
msgid "not patched yet"
msgstr "ещё не применено"
#. Status block
msgid "Autoscroll"
msgstr "Автопрокрутка"
msgid "Applications covered"
msgstr "Приложения с автопрокруткой"
msgid "%d of %d"
msgstr "%d из %d"
msgid "Not identified"
msgstr "Не распознано"
msgid "%d (see the applications list)"
msgstr "%d (см. список приложений)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Вставка средней кнопкой"
msgid "New applications"
msgstr "Новые приложения"
msgid "Last applied"
msgstr "Последнее применение"
msgid "Applications pick this up the next time they are started."
msgstr "Приложения подхватят это при следующем запуске."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Включить или выключить автопрокрутку"
msgid "Re-apply everything"
msgstr "Применить всё заново"
msgid "Applications"
msgstr "Приложения"
msgid "Settings"
msgstr "Настройки"
msgid "Quit"
msgstr "Выход"
msgid "Press any key to continue..."
msgstr "Нажмите любую клавишу..."
msgid "One moment..."
msgstr "Минутку..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Отключить вставку средней кнопкой"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"Средняя кнопка больше нигде не вставляет. Со следующего входа в систему."
msgid "Cover new applications"
msgstr "Охватывать новые приложения"
msgid "Applications installed later get autoscroll on their own."
msgstr "Приложения, установленные позже, получат автопрокрутку сами."
msgid "More Chromium arguments"
msgstr "Другие аргументы Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Передаются каждому приложению вместе с аргументом автопрокрутки."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Вверх/Вниз: выбор, Пробел: изменить, q: назад"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Аргументы через пробел (пусто, если не нужны):"
msgid "Could not save the setting."
msgstr "Не удалось сохранить настройку."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Просмотр установленных приложений..."
msgid "No supported applications found."
msgstr "Поддерживаемые приложения не найдены."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Помеченное как «неизвестно» остаётся выключенным, пока его здесь не включат."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Автопрокрутка выключена. Вот что было бы охвачено."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Вверх/Вниз: выбор, Пробел: включить или выключить, q: назад"
msgid "Search:"
msgstr "Поиск:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Печатайте для поиска, Вниз: к списку, Esc: назад"
msgid "No matches."
msgstr "Ничего не найдено."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Автопрокрутка включена. Приложений: %d."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Уже запущенные приложения подхватят это после перезапуска."
msgid "Could not start the watcher for newly installed applications."
msgstr "Не удалось запустить отслеживание новых приложений."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Нет пользовательского сеанса systemd, поэтому новые приложения не будут "
"подхвачены сами."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Автопрокрутка выключена, все изменения отменены."
msgid "Autoscroll is off. Turn it on first."
msgstr "Автопрокрутка выключена. Сначала включите её."
msgid "Applied again to %d applications."
msgstr "Применено заново. Приложений: %d."
msgid "Applied to %d applications."
msgstr "Применено. Приложений: %d."
msgid "Already applied to %d applications."
msgstr "Уже применено. Приложений: %d."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam запускает свой интерфейс так, что эта версия не может это изменить "
"незаметно для Steam; Steam остаётся без изменений."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"Интерфейсу Steam нельзя передать флаг, не оставив файл, который Steam "
"переустановил бы; изменение отменено."
msgid "Middle click stops pasting at the next login."
msgstr "Средняя кнопка перестанет вставлять со следующего входа в систему."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Использование: middleclick-autoscroll [команда]"
msgid "Commands:"
msgstr "Команды:"
msgid "Turn autoscroll on"
msgstr "Включить автопрокрутку"
msgid "Turn it off and put everything back"
msgstr "Выключить и всё вернуть"
msgid "Apply to anything installed since the last run"
msgstr "Применить ко всему, что установлено с прошлого запуска"
msgid "Take everything back and apply it again"
msgstr "Всё отменить и применить заново"
msgid "Show what is covered"
msgstr "Показать, что охвачено"
msgid "List every application that was found"
msgstr "Показать все найденные приложения"
msgid "Show this help"
msgstr "Показать эту справку"
msgid "Show the version"
msgstr "Показать версию"
msgid "Without a command an interactive menu is shown."
msgstr "Без команды показывается интерактивное меню."
msgid "Unknown command: %s"
msgstr "Неизвестная команда: %s"
#. Time
msgid "never"
msgstr "никогда"
msgid "just now"
msgstr "только что"
msgid "1 minute ago"
msgstr "1 минуту назад"
msgid "%d minutes ago"
msgstr "%d мин. назад"
msgid "1 hour ago"
msgstr "1 час назад"
msgid "%d hours ago"
msgstr "%d ч назад"
msgid "1 day ago"
msgstr "1 день назад"
msgid "%d days ago"
msgstr "%d дн. назад"
+269
View File
@@ -0,0 +1,269 @@
# Turkish translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Turkish\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "AÇIK"
msgid "OFF"
msgstr "KAPALI"
msgid "on"
msgstr "açık"
msgid "off"
msgstr "kapalı"
msgid "(none)"
msgstr "(yok)"
msgid "flag file"
msgstr "bayrak dosyası"
msgid "launcher"
msgstr "başlatıcı"
msgid "profile"
msgstr "profil"
msgid "cannot tell"
msgstr "belirsiz"
msgid "not patched yet"
msgstr "henüz uygulanmadı"
#. Status block
msgid "Autoscroll"
msgstr "Autoscroll"
msgid "Applications covered"
msgstr "Kapsanan uygulamalar"
msgid "%d of %d"
msgstr "%d / %d"
msgid "Not identified"
msgstr "Tanınmadı"
msgid "%d (see the applications list)"
msgstr "%d (uygulama listesine bakın)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Orta tıkla yapıştırma"
msgid "New applications"
msgstr "Yeni uygulamalar"
msgid "Last applied"
msgstr "Son uygulanma"
msgid "Applications pick this up the next time they are started."
msgstr "Uygulamalar bunu bir sonraki açılışta alır."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Autoscroll'u aç veya kapat"
msgid "Re-apply everything"
msgstr "Her şeyi yeniden uygula"
msgid "Applications"
msgstr "Uygulamalar"
msgid "Settings"
msgstr "Ayarlar"
msgid "Quit"
msgstr "Çık"
msgid "Press any key to continue..."
msgstr "Devam etmek için bir tuşa basın..."
msgid "One moment..."
msgstr "Bir dakika..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Orta tıkla yapıştırmayı kapat"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr ""
"Orta tık artık başka hiçbir yerde yapıştırmaz. Bir sonraki girişten itibaren."
msgid "Cover new applications"
msgstr "Yeni uygulamaları kapsa"
msgid "Applications installed later get autoscroll on their own."
msgstr "Sonradan kurulan uygulamalar Autoscroll'u kendiliğinden alır."
msgid "More Chromium arguments"
msgstr "Ek Chromium argümanları"
msgid "Passed to every application along with the autoscroll one."
msgstr "Autoscroll argümanıyla birlikte her uygulamaya verilir."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Yukarı/Aşağı: seç, Boşluk: değiştir, q: geri"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Boşlukla ayrılmış argümanlar, hiçbiri için boş bırakın:"
msgid "Could not save the setting."
msgstr "Ayar kaydedilemedi."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Kurulu uygulamalara bakılıyor..."
msgid "No supported applications found."
msgstr "Desteklenen uygulama bulunamadı."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr "\"belirsiz\" olarak işaretlenenler burada açılana kadar kapalı kalır."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Autoscroll kapalı. Kapsanacak olanlar bunlar."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Yukarı/Aşağı: seç, Boşluk: aç veya kapat, q: geri"
msgid "Search:"
msgstr "Ara:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Aramak için yazın, Aşağı: listeye git, Esc: geri"
msgid "No matches."
msgstr "Eşleşme yok."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Autoscroll %d uygulama için açık."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Zaten çalışan uygulamalar bunu yeniden başlatıldıklarında alır."
msgid "Could not start the watcher for newly installed applications."
msgstr "Yeni uygulamaları izleme başlatılamadı."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"systemd kullanıcı oturumu yok, bu yüzden yeni kurulan uygulamalar "
"kendiliğinden algılanmayacak."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Autoscroll kapalı ve değiştirilen her şey geri alındı."
msgid "Autoscroll is off. Turn it on first."
msgstr "Autoscroll kapalı. Önce açın."
msgid "Applied again to %d applications."
msgstr "%d uygulamaya yeniden uygulandı."
msgid "Applied to %d applications."
msgstr "%d uygulamaya uygulandı."
msgid "Already applied to %d applications."
msgstr "Zaten %d uygulamaya uygulanmış."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam arayüzünü, bu sürümün Steam fark etmeden değiştiremeyeceği bir şekilde "
"başlatıyor; olduğu gibi bırakılıyor."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"Steam arayüzüne, Steam'in yeniden kuracağı bir dosya bırakmadan bayrak "
"verilemiyor; değişiklik geri alındı."
msgid "Middle click stops pasting at the next login."
msgstr "Orta tık bir sonraki girişte yapıştırmayı bırakır."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Kullanım: middleclick-autoscroll [komut]"
msgid "Commands:"
msgstr "Komutlar:"
msgid "Turn autoscroll on"
msgstr "Autoscroll'u aç"
msgid "Turn it off and put everything back"
msgstr "Kapat ve her şeyi geri al"
msgid "Apply to anything installed since the last run"
msgstr "Son çalıştırmadan beri kurulan her şeye uygula"
msgid "Take everything back and apply it again"
msgstr "Her şeyi geri al ve yeniden uygula"
msgid "Show what is covered"
msgstr "Neyin kapsandığını göster"
msgid "List every application that was found"
msgstr "Bulunan tüm uygulamaları listele"
msgid "Show this help"
msgstr "Bu yardımı göster"
msgid "Show the version"
msgstr "Sürümü göster"
msgid "Without a command an interactive menu is shown."
msgstr "Komut verilmezse etkileşimli bir menü gösterilir."
msgid "Unknown command: %s"
msgstr "Bilinmeyen komut: %s"
#. Time
msgid "never"
msgstr "hiç"
msgid "just now"
msgstr "az önce"
msgid "1 minute ago"
msgstr "1 dakika önce"
msgid "%d minutes ago"
msgstr "%d dakika önce"
msgid "1 hour ago"
msgstr "1 saat önce"
msgid "%d hours ago"
msgstr "%d saat önce"
msgid "1 day ago"
msgstr "1 gün önce"
msgid "%d days ago"
msgstr "%d gün önce"
+269
View File
@@ -0,0 +1,269 @@
# Ukrainian translation for middleclick-autoscroll.
# Machine translated. Corrections are welcome.
# Copyright (C) 2026 Felitendo
# This file is distributed under the same license as middleclick-autoscroll.
#
msgid ""
msgstr ""
"Project-Id-Version: middleclick-autoscroll 1.6.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
"issues\n"
"POT-Creation-Date: 2026-09-24 00:00+0200\n"
"PO-Revision-Date: 2026-09-24 00:00+0200\n"
"Last-Translator: machine translation\n"
"Language-Team: Ukrainian\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. Status labels
msgid "ON"
msgstr "УВІМК"
msgid "OFF"
msgstr "ВИМК"
msgid "on"
msgstr "увімк"
msgid "off"
msgstr "вимк"
msgid "(none)"
msgstr "(немає)"
msgid "flag file"
msgstr "файл прапорців"
msgid "launcher"
msgstr "ярлик"
msgid "profile"
msgstr "профіль"
msgid "cannot tell"
msgstr "невідомо"
msgid "not patched yet"
msgstr "ще не застосовано"
#. Status block
msgid "Autoscroll"
msgstr "Автопрокручування"
msgid "Applications covered"
msgstr "Охоплені застосунки"
msgid "%d of %d"
msgstr "%d з %d"
msgid "Not identified"
msgstr "Не розпізнано"
msgid "%d (see the applications list)"
msgstr "%d (див. список застосунків)"
msgid "Steam"
msgstr "Steam"
msgid "Middle-click paste"
msgstr "Вставлення середньою кнопкою"
msgid "New applications"
msgstr "Нові застосунки"
msgid "Last applied"
msgstr "Востаннє застосовано"
msgid "Applications pick this up the next time they are started."
msgstr "Застосунки підхоплять це під час наступного запуску."
#. Menu
msgid "Turn autoscroll on or off"
msgstr "Увімкнути або вимкнути автопрокручування"
msgid "Re-apply everything"
msgstr "Застосувати все знову"
msgid "Applications"
msgstr "Застосунки"
msgid "Settings"
msgstr "Налаштування"
msgid "Quit"
msgstr "Вийти"
msgid "Press any key to continue..."
msgstr "Натисніть будь-яку клавішу..."
msgid "One moment..."
msgstr "Хвилинку..."
#. Settings
msgid "Turn off middle-click paste"
msgstr "Вимкнути вставлення середньою кнопкою"
msgid ""
"Stops middle click from pasting anywhere else. Starts with the next login."
msgstr "Середня кнопка більше ніде не вставляє. З наступного входу в систему."
msgid "Cover new applications"
msgstr "Охоплювати нові застосунки"
msgid "Applications installed later get autoscroll on their own."
msgstr "Застосунки, встановлені пізніше, отримають автопрокручування самі."
msgid "More Chromium arguments"
msgstr "Інші аргументи Chromium"
msgid "Passed to every application along with the autoscroll one."
msgstr "Передаються кожному застосунку разом з аргументом автопрокручування."
msgid "Up/Down: select, Space: change, q: back"
msgstr "Вгору/Вниз: вибір, Пробіл: змінити, q: назад"
msgid "Arguments separated by spaces, empty for none:"
msgstr "Аргументи через пробіл (порожньо, якщо не потрібні):"
msgid "Could not save the setting."
msgstr "Не вдалося зберегти налаштування."
#. Applications screen
msgid "Looking at the installed applications..."
msgstr "Перегляд встановлених застосунків..."
msgid "No supported applications found."
msgstr "Підтримуваних застосунків не знайдено."
msgid "Anything marked \"cannot tell\" stays off until it is turned on here."
msgstr ""
"Позначене як «невідомо» лишається вимкненим, доки його тут не увімкнуть."
msgid "Autoscroll is off. This is what would be covered."
msgstr "Автопрокручування вимкнено. Ось що було б охоплено."
msgid "Up/Down: select, Space: turn on or off, q: back"
msgstr "Вгору/Вниз: вибір, Пробіл: увімкнути чи вимкнути, q: назад"
msgid "Search:"
msgstr "Пошук:"
msgid "Type to search, Down: go to the list, Esc: back"
msgstr "Друкуйте для пошуку, Вниз: до списку, Esc: назад"
msgid "No matches."
msgstr "Нічого не знайдено."
#. Results
msgid "Autoscroll is on for %d applications."
msgstr "Автопрокручування увімкнено. Застосунків: %d."
msgid ""
"Applications that are already running pick this up when they are restarted."
msgstr "Уже запущені застосунки підхоплять це після перезапуску."
msgid "Could not start the watcher for newly installed applications."
msgstr "Не вдалося запустити стеження за новими застосунками."
msgid ""
"No systemd user session, so newly installed applications will not be picked "
"up on their own."
msgstr ""
"Немає користувацького сеансу systemd, тож нові застосунки не буде підхоплено "
"самостійно."
msgid "Autoscroll is off and everything that was changed has been put back."
msgstr "Автопрокручування вимкнено, усі зміни скасовано."
msgid "Autoscroll is off. Turn it on first."
msgstr "Автопрокручування вимкнено. Спершу увімкніть його."
msgid "Applied again to %d applications."
msgstr "Застосовано знову. Застосунків: %d."
msgid "Applied to %d applications."
msgstr "Застосовано. Застосунків: %d."
msgid "Already applied to %d applications."
msgstr "Уже застосовано. Застосунків: %d."
msgid ""
"Steam starts its interface in a way this version cannot change without Steam "
"noticing; leaving it alone."
msgstr ""
"Steam запускає свій інтерфейс так, що ця версія не може це змінити непомітно "
"для Steam; Steam лишається без змін."
msgid ""
"Steam's interface cannot be given the flag without leaving a file Steam "
"would reinstall; the change has been taken back."
msgstr ""
"Інтерфейсу Steam не можна передати прапорець, не залишивши файл, який Steam "
"перевстановив би; зміну скасовано."
msgid "Middle click stops pasting at the next login."
msgstr "Середня кнопка перестане вставляти з наступного входу в систему."
#. Help
msgid "Usage: middleclick-autoscroll [command]"
msgstr "Використання: middleclick-autoscroll [команда]"
msgid "Commands:"
msgstr "Команди:"
msgid "Turn autoscroll on"
msgstr "Увімкнути автопрокручування"
msgid "Turn it off and put everything back"
msgstr "Вимкнути й усе повернути"
msgid "Apply to anything installed since the last run"
msgstr "Застосувати до всього, що встановлено з минулого запуску"
msgid "Take everything back and apply it again"
msgstr "Усе скасувати й застосувати знову"
msgid "Show what is covered"
msgstr "Показати, що охоплено"
msgid "List every application that was found"
msgstr "Показати всі знайдені застосунки"
msgid "Show this help"
msgstr "Показати цю довідку"
msgid "Show the version"
msgstr "Показати версію"
msgid "Without a command an interactive menu is shown."
msgstr "Без команди показується інтерактивне меню."
msgid "Unknown command: %s"
msgstr "Невідома команда: %s"
#. Time
msgid "never"
msgstr "ніколи"
msgid "just now"
msgstr "щойно"
msgid "1 minute ago"
msgstr "1 хвилину тому"
msgid "%d minutes ago"
msgstr "%d хв тому"
msgid "1 hour ago"
msgstr "1 годину тому"
msgid "%d hours ago"
msgstr "%d год тому"
msgid "1 day ago"
msgstr "1 день тому"
msgid "%d days ago"
msgstr "%d дн. тому"
+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8.7 17.8 437.4 437.4" width="512" height="512">
<defs>
<linearGradient id="shell" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#dfe7f0"/></linearGradient>
<linearGradient id="wheel" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#5fd0ff"/><stop offset="1" stop-color="#0c62a0"/></linearGradient>
<radialGradient id="glow" cx=".5" cy=".5" r=".5"><stop offset="0" stop-color="#2aa7e8" stop-opacity=".55"/><stop offset="1" stop-color="#2aa7e8" stop-opacity="0"/></radialGradient>
<filter id="soft" x="-20%" y="-20%" width="140%" height="150%"><feDropShadow dx="0" dy="10" stdDeviation="12" flood-color="#04213a" flood-opacity=".35"/></filter>
<linearGradient id="disc" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#5fd0ff"/><stop offset="1" stop-color="#0c62a0"/></linearGradient>
</defs>
<g filter="url(#soft)">
<circle cx="200" cy="200" r="150" fill="url(#disc)"/>
<g fill="#ffffff">
<path d="M200 70 L236 118 L164 118 Z"/>
<path d="M200 330 L236 282 L164 282 Z"/>
<path d="M70 200 L118 164 L118 236 Z" opacity=".6"/>
<path d="M330 200 L282 164 L282 236 Z" opacity=".6"/>
<circle cx="200" cy="200" r="22"/>
</g>
</g>
<g transform="translate(150 150) scale(.62) rotate(-12 256 266)" filter="url(#soft)">
<rect x="166" y="96" width="180" height="340" rx="90" fill="url(#shell)" stroke="#c9d5e3" stroke-width="4"/>
<path d="M170 228 C 210 240, 302 240, 342 228" fill="none" stroke="#c9d5e3" stroke-width="5" stroke-linecap="round"/>
<path d="M256 100 L256 236" stroke="#c9d5e3" stroke-width="5"/>
<circle cx="256" cy="168" r="70" fill="url(#glow)"/>
<rect x="236" y="128" width="40" height="80" rx="20" fill="url(#wheel)"/>
<g stroke="#ffffff" stroke-opacity=".55" stroke-width="5" stroke-linecap="round"><line x1="246" y1="148" x2="266" y2="148"/><line x1="246" y1="168" x2="266" y2="168"/><line x1="246" y1="188" x2="266" y2="188"/></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

+12 -7
View File
@@ -1,11 +1,11 @@
[Unit]
Description=Watch for newly installed Chromium-based applications
Description=Watch for newly installed applications
Documentation=man:middleclick-autoscroll(1)
[Path]
# Every way an application can turn up: a package, a Flatpak, a snap, something
# installed by hand. Watching where the launchers land means there is no hook
# to write per package manager - pacman, apt, dnf, zypper, flatpak, snapd and
# to write per package manager: pacman, apt, dnf, zypper, flatpak, snapd and
# an AppImage manager all end up here.
#
# A directory that does not exist on this system costs nothing: systemd watches
@@ -23,19 +23,24 @@ PathModified=/run/current-system/sw/share/applications
PathModified=%h/.nix-profile/share/applications
PathModified=%h/.guix-profile/share/applications
# The desktop folder is watched too - Steam writes a shortcut there for every
# game somebody asks for one, and starting a game is a Steam start - but its
# name is translated and cannot be written down here. `enable` adds it in a
# drop-in, from the name this system uses.
# The desktop folder is watched too. Steam writes a shortcut there for every
# game somebody asks for one, and starting a game is a Steam start. But the
# folder's name is translated and cannot be written down here. `enable` adds it
# in a drop-in, from the name this system uses.
# Steam replaces the script that starts its interface on every client update,
# which takes the patch with it. Where that installation lives depends on how
# Steam was installed.
# Steam was installed. The steamrt3 client has a script of its own.
PathChanged=%h/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.steam/debian-installation/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/snap/steam/common/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.local/share/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/.steam/debian-installation/steamrt64/steamwebhelper.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/snap/steam/common/.local/share/Steam/steamrt64/steamwebhelper.sh
Unit=middleclick-autoscroll.service
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit]
Description=Apply middle-click autoscroll to Chromium-based applications
Description=Apply middle-click autoscroll
Documentation=man:middleclick-autoscroll(1)
[Service]
+81 -27
View File
@@ -4,7 +4,7 @@
#
# The routing lives in mca_route and is used twice: once by mca_apply to patch
# and once by the applications screen to show what would happen. Keeping it in
# one function is what makes that screen trustworthy - it is not a description
# one function is what makes that screen trustworthy: it is not a description
# of the behaviour, it is the behaviour.
MCA_CHANGES=0
@@ -15,6 +15,8 @@ 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
# gecko - a setting in each of the application's profiles
# unknown - cannot tell what engine this is (an AppImage), so nothing is done
# off - detected, but switched off
#
@@ -26,16 +28,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 || $kind == gecko ]]; 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
@@ -56,14 +58,15 @@ mca_route() {
# mca_apply
# Brings everything into line with the current settings. Safe to run as often
# as it likes to be - it writes only what differs, which is what keeps the
# 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=()
MCA_N_ON=0; MCA_N_OFF=0; MCA_N_UNKNOWN=0
MCA_GECKO_SEEN=(); MCA_GECKO_WATCH=()
mca_config_load
@@ -111,28 +114,36 @@ mca_apply() {
steam_done=1
fi
;;
spotify)
(( spotify_done )) || mca_spotify_apply
spotify_done=1
;;
gecko)
mca_gecko_apply "${MCA_PROFILES[i]}" "$packaging" "$prog"
;;
esac
done
# Steam is worth patching even when its desktop entry is missing - a user
# 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.
[[ $CFG_PASTE == yes ]] && mca_kde_paste_apply
mca_prune_orphans
@@ -172,7 +183,8 @@ MCA_UNIT_SERVICE="middleclick-autoscroll.service"
# The desktop folder is watched like every other directory an entry can turn up
# in, but its name is translated and the unit that ships with the package
# cannot know it. So that one path is written here, into a drop-in, from the
# name this system actually uses.
# name this system actually uses. The same goes for where Firefox and its forks
# keep their profiles, which depends on what is installed.
MCA_UNIT_DROPIN="${MCA_XDG_CONFIG}/systemd/user/${MCA_UNIT_PATH}.d"
mca_watch_available() {
@@ -186,27 +198,51 @@ mca_watch_enabled() {
# _mca_watch_dropin_write
# Returns 0 when the drop-in changed and systemd has to be told, 1 when it was
# already right - the same contract as mca_write_if_changed, and for the same
# reason: the watcher runs after every desktop entry that appears anywhere on
# already right. That is the same contract as mca_write_if_changed, for the
# same reason: the watcher runs after every desktop entry that appears anywhere on
# the system, and a daemon-reload each time would be absurd.
_mca_watch_dropin_write() {
local dir file="$MCA_UNIT_DROPIN/desktop.conf" content
dir="$(mca_desktop_folder)"
[[ -d $dir ]] || { _mca_watch_dropin_remove; return $?; }
local dir path content='' changed=1
# A per cent sign starts a specifier in a unit file and has to be doubled
# to mean itself. Rare in a folder name, fatal when it happens.
content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n'
dir="$(mca_desktop_folder)"
[[ -d $dir ]] && content="[Path]"$'\n'"PathModified=${dir//%/%%}"$'\n'
_mca_watch_conf desktop.conf "$content" && changed=0
mca_write_if_changed "$file" "$content"
# Filled by the apply that ran just before. A new profile is added there.
content=''
for path in "${MCA_GECKO_WATCH[@]}"; do
content+="PathChanged=${path//%/%%}"$'\n'
done
[[ -n $content ]] && content="[Path]"$'\n'"$content"
_mca_watch_conf profiles.conf "$content" && changed=0
return "$changed"
}
# _mca_watch_conf <name> <content>
# Writes one drop-in, or removes it when the content is empty. Returns 0 when
# it changed.
_mca_watch_conf() {
local file="$MCA_UNIT_DROPIN/$1"
if [[ -n $2 ]]; then
mca_write_if_changed "$file" "$2"
return
fi
[[ -e $file ]] || return 1
rm -f -- "$file"
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true
return 0
}
_mca_watch_dropin_remove() {
[[ -e "$MCA_UNIT_DROPIN/desktop.conf" ]] || return 1
rm -f -- "$MCA_UNIT_DROPIN/desktop.conf"
rmdir "$MCA_UNIT_DROPIN" 2>/dev/null || true
return 0
local changed=1
_mca_watch_conf desktop.conf '' && changed=0
_mca_watch_conf profiles.conf '' && changed=0
return "$changed"
}
mca_watch_enable() {
@@ -220,7 +256,8 @@ mca_watch_enable() {
# mca_watch_refresh
# Keeps a running watcher in step with a desktop folder that has been renamed,
# which is what a change of session language does to it.
# which is what a change of session language does to it, and with the Firefox
# profiles there are now.
mca_watch_refresh() {
mca_watch_available || return 0
_mca_watch_dropin_write || return 0
@@ -267,12 +304,29 @@ 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 <route>
mca_route_label() {
case "$1" in
flags) mca_msg "flag file" ;;
desktop) mca_msg "launcher" ;;
steam) mca_msg "Steam" ;;
spotify) printf 'spotify-launcher' ;;
gecko) mca_msg "profile" ;;
unknown) mca_msg "cannot tell" ;;
*) mca_msg "off" ;;
esac
+50 -26
View File
@@ -3,7 +3,7 @@
# Paths, translations, output helpers and the change ledger.
#
# Everything here runs unprivileged. This tool only ever writes inside the
# user's own home - flag files, desktop entries, Steam's own scripts - so there
# user's own home (flag files, desktop entries, Steam's own scripts), so there
# is no system state to guard and nothing that needs root.
MCA_VERSION="@VERSION@"
@@ -19,7 +19,7 @@ MCA_PRETTY="Middle-Click Autoscroll"
#
# --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
# 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.
@@ -27,7 +27,7 @@ MCA_PRETTY="Middle-Click Autoscroll"
# --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
# 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
@@ -35,15 +35,19 @@ MCA_PRETTY="Middle-Click Autoscroll"
# the one that always works.
MCA_FLAG="--enable-blink-features=MiddleClickAutoscroll"
# 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"
# Two browsers do not answer to the Chromium name. Helium ships the feature
# under its own name and sets the runtime flag from that alone. Brave takes
# the generated feature away and switches the flag on from one of its own,
# spelled MiddelButtonClickAutoscroll. The typo is in Brave's source, and the
# correct spelling does nothing. So browsers are asked for all three. 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,MiddelButtonClickAutoscroll"
# 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"
MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll,MiddelButtonClickAutoscroll"
# Bumped when the flags above change, and when what gets written where does.
# An installation that was set up by a version with a different answer is taken
@@ -52,7 +56,10 @@ MCA_BROWSER_FEATURES="MiddleClickAutoscroll,HeliumMiddleClickAutoscroll"
#
# 3 is what takes -noverifyfiles back out of the Steam entries an older version
# put it in.
MCA_FLAG_SCHEME=3
#
# 4 adds Brave's name to the browser list, which an entry edited in place would
# otherwise never get.
MCA_FLAG_SCHEME=4
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
MCA_LOCALEDIR="${MCA_LOCALEDIR:-@LOCALEDIR@}"
@@ -80,7 +87,7 @@ MCA_BACKUPDIR="${MCA_STATEDIR}/backup"
MCA_LEDGER="${MCA_STATEDIR}/ledger"
# The folder the desktop itself shows. Nothing in the XDG search path looks at
# it, so an entry that lives only there is invisible to a scan - and a game
# it, so an entry that lives only there is invisible to a scan. A game
# shortcut dragged onto the desktop is exactly that.
#
# Its name is translated: Schreibtisch, Bureau, Escritorio. The name in use is
@@ -208,12 +215,6 @@ else
MCA_C_GREEN='' MCA_C_YELLOW='' MCA_C_RED=''
fi
# Set by mca_bad and mca_note. The menu redraws straight after an action, which
# would wipe the screen; this marks that something was printed the user still
# has to read. A plain success needs no acknowledgement - the status block at
# the top of the menu already shows the new state.
MCA_UI_NEEDS_ACK=''
# --quiet silences progress chatter; errors still go to stderr. Used by the
# systemd unit, which has no terminal and logs to the journal anyway.
MCA_QUIET=''
@@ -221,8 +222,8 @@ MCA_QUIET=''
mca_say() { [[ -n $MCA_QUIET ]] || printf '%s\n' "$*"; }
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_bad() { printf '%s✘%s %s\n' "$MCA_C_RED" "$MCA_C_RESET" "$*" >&2; }
mca_note() { [[ -n $MCA_QUIET ]] || printf '%s•%s %s\n' "$MCA_C_DIM" "$MCA_C_RESET" "$*"; }
mca_have() { command -v "$1" > /dev/null 2>&1; }
@@ -237,10 +238,13 @@ mca_time_ago() {
delta=$(( now - ts ))
(( delta < 0 )) && delta=0
if (( delta < 60 )); then mca_msg "just now"
elif (( delta < 3600 )); then mca_msg "%d minutes ago" "$(( delta / 60 ))"
elif (( delta < 86400 )); then mca_msg "%d hours ago" "$(( delta / 3600 ))"
else mca_msg "%d days ago" "$(( delta / 86400 ))"
if (( delta < 60 )); then mca_msg "just now"
elif (( delta < 120 )); then mca_msg "1 minute ago"
elif (( delta < 3600 )); then mca_msg "%d minutes ago" "$(( delta / 60 ))"
elif (( delta < 7200 )); then mca_msg "1 hour ago"
elif (( delta < 86400 )); then mca_msg "%d hours ago" "$(( delta / 3600 ))"
elif (( delta < 172800 )); then mca_msg "1 day ago"
else mca_msg "%d days ago" "$(( delta / 86400 ))"
fi
printf '\n'
}
@@ -268,7 +272,7 @@ mca_state_write() {
# ---------------------------------------------------------------------------
# The change ledger
# ---------------------------------------------------------------------------
# Reverting by pattern - "delete every desktop entry that mentions the flag" -
# Reverting by pattern ("delete every desktop entry that mentions the flag")
# would also delete entries the user wrote by hand. So every change is recorded
# instead, and `revert` replays the ledger backwards.
#
@@ -278,9 +282,14 @@ 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
# gecko <user.js in a profile> created | block
#
# `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. A user.js
# loses its block, and prefs.js the line Firefox copied out of it.
mca_ledger_add() {
local kind="$1" path="$2" detail="${3:-}"
@@ -303,6 +312,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
@@ -327,12 +345,18 @@ mca_backup_name() {
# copy was stored under. Existing backups are never overwritten: the first copy
# is the pristine one, and a second apply must not replace it with an already
# patched version.
#
# A symlink is kept as a symlink, so undo can put the link back. It is taken
# again every time: this program never writes a link, so one is always pristine.
mca_backup() {
local file="$1" name
name="$(mca_backup_name "$file")"
mkdir -p "$MCA_BACKUPDIR" 2>/dev/null || return 1
if [[ ! -e "$MCA_BACKUPDIR/$name" ]]; then
if [[ -L $file ]]; then
rm -f -- "$MCA_BACKUPDIR/$name"
cp -P -p -- "$file" "$MCA_BACKUPDIR/$name" 2>/dev/null || return 1
elif [[ ! -e "$MCA_BACKUPDIR/$name" && ! -L "$MCA_BACKUPDIR/$name" ]]; then
cp -p -- "$file" "$MCA_BACKUPDIR/$name" 2>/dev/null || return 1
fi
printf '%s\n' "$name"
+49 -7
View File
@@ -101,6 +101,20 @@ mca_config_set() {
MCA_CONFIG_CACHED=0
}
# mca_config_del <Key>
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 <Key> <item>
# The per-application overrides are stored as space separated lists.
mca_config_list_has() {
@@ -136,14 +150,8 @@ 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
CFG_EXTRA_FLAGS="$(mca_config_get ExtraFlags '')"
@@ -154,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.
+497 -93
View File
@@ -4,7 +4,7 @@
#
# The detection is deliberately conservative. A wrong "yes" appends an unknown
# argument to something that is not Chromium, and plenty of programs treat an
# unrecognised argument as a file name to open - so every rule here is a
# unrecognised argument as a file name to open. So every rule here is a
# positive one, and anything that cannot be identified is reported as unknown
# rather than guessed at.
@@ -13,7 +13,7 @@
# swiftshader libraries) and plain CEF (libcef).
#
# Deliberately not in here: libEGL.so and libffmpeg.so. Chromium ships both, but
# so does the system - /usr/lib/libEGL.so exists on any machine with Mesa - and
# so does the system (/usr/lib/libEGL.so exists on any machine with Mesa), and
# a marker that can be somebody else's file is not a marker.
MCA_MARKERS=(
chrome_crashpad_handler chrome-sandbox chrome_100_percent.pak
@@ -21,6 +21,22 @@ MCA_MARKERS=(
libvk_swiftshader.so LICENSES.chromium.html libcef.so
)
# The markers that still mean something when the whole of a tree is searched
# rather than just the directory the binary sits in. That is the case inside a
# Flatpak, a snap or an AppImage, where there is no "next to the binary".
#
# Three of the names above are missing here on purpose. icudtl.dat is ICU's
# data file and not Chromium's: a Flutter application ships one in data/, and
# a whole-tree search would find it. snapshot_blob.bin and resources.pak are
# the same kind of shared name. Next to a binary they are still evidence,
# because nothing but Chromium unpacks its payload there. A few directories
# deeper they are not.
MCA_MARKERS_STRICT=(
chrome_crashpad_handler chrome-sandbox chrome_100_percent.pak
v8_context_snapshot.bin libvk_swiftshader.so LICENSES.chromium.html
libcef.so app.asar
)
# Shared directories, where a marker belongs to the system rather than to the
# program that happens to live there. An application ships its payload in a
# directory of its own; nothing unpacks Chromium straight into /usr/lib.
@@ -58,14 +74,21 @@ MCA_SNAP_DIRS=(/snap /var/lib/snapd/snap)
# CHROMIUM_FLAGS and CHROME_WRAPPER earn their place: they are the variables
# the Debian, Fedora and openSUSE Chromium wrappers and Google's own Chrome
# wrapper build their command line out of, and nothing else sets them.
MCA_SCRIPT_HINTS='ELECTRON_|app\.asar|chrome-sandbox|libcef|enable-blink-features|ozone-platform-hint|CHROMIUM_FLAGS|CHROME_WRAPPER|CHROME_VERSION_EXTRA'
#
# updater_bootstrap is Discord's, and it is the only thing left that names it.
# Discord no longer ships its application: /usr/bin/discord is a few lines of
# shell that run that bootstrap, which downloads Electron into the user's
# config directory on first start and execs it from there. So there is nothing
# next to the launcher to find, and the path it hands over to is built out of
# $HOME and the version it just downloaded.
MCA_SCRIPT_HINTS='ELECTRON_|app\.asar|chrome-sandbox|libcef|enable-blink-features|ozone-platform-hint|CHROMIUM_FLAGS|CHROME_WRAPPER|CHROME_VERSION_EXTRA|updater_bootstrap'
# ---------------------------------------------------------------------------
# Desktop entries
# ---------------------------------------------------------------------------
# Where Flatpak and snapd put the launchers they export. Both add these to
# XDG_DATA_DIRS themselves, through a file in /etc/profile.d - but only for a
# XDG_DATA_DIRS themselves, through a file in /etc/profile.d. But only for a
# session that was started after they were installed, and only for a session
# manager that reads it at all. They are appended, after everything XDG names,
# so a directory that is already in the search path keeps its own position and
@@ -77,7 +100,7 @@ mca_extra_desktop_dirs() {
/var/lib/snapd/desktop/applications
}
# The directories a desktop entry can come from, most specific first - which is
# The directories a desktop entry can come from, most specific first. This is
# also XDG lookup order, so the first file found for an id is the one that is
# actually used.
mca_desktop_dirs() {
@@ -139,7 +162,7 @@ _mca_desktop_read() {
# mca_exec_program <exec line>
# The program a desktop entry actually starts: the first token that is not an
# environment prefix, resolved to an absolute path. The result is left in
# MCA_PROG rather than printed - the scan calls this for every desktop entry on
# MCA_PROG rather than printed. The scan calls this for every desktop entry on
# the system, and a command substitution each time is a fork each time.
#
# Fails for entries this tool has no safe way to rewrite: anything routed
@@ -215,7 +238,7 @@ mca_exec_flatpak_id() {
# Whether an entry starts something inside Steam rather than starting Steam
# itself: it carries a steam:// address of its own. Steam writes one of those
# for every game somebody asks for a shortcut to, and the client's own entry
# never has one - it takes an address from the outside, through %U.
# never has one. It takes an address from the outside, through %U.
mca_exec_is_steam_link() {
local line="$1" prog="$2"
@@ -235,7 +258,7 @@ _mca_prog_is_steam_name() {
# _mca_prog_is_steam_wrapper <program>
# Whether a script in front of the client is a way of starting Steam. People
# put one there to add a switch of their own, and an entry pointing at it is a
# Steam start like any other - but the script is not named after Steam, so
# Steam start like any other. But the script is not named after Steam, so
# nothing above recognises it.
#
# Getting this wrong is worse than it sounds: such a script tends to mention
@@ -266,8 +289,8 @@ _mca_prog_is_steam_wrapper() {
# mca_prog_is_steam <program>
# Whether running this program starts the Steam client. Every packaging is in
# here and every name Valve and the distributions give the launcher, because
# the answer decides whether an entry gets Steam's own switch - and an entry
# that starts Steam without it undoes the web helper patch on the way up.
# the answer decides whether an entry gets Steam's own switch. An entry that
# starts Steam without it undoes the web helper patch on the way up.
#
# The program is what a scan leaves behind: an absolute path for a native
# install, flatpak:<id> or snap:<name> for the other two.
@@ -309,14 +332,34 @@ _mca_stat_batch() {
return 0
}
# The first line of the cache file, and the reason it is there: the verdicts
# below it are keyed on size and mtime, so an entry for a file that has not
# changed is never looked at again. A cache written when a verdict meant
# something else (before an AppImage could come out as anything but "no")
# would therefore keep answering the old way forever.
#
# So the version is part of it, and every update drops the cache. The rules
# change far more often than anyone remembers to bump a number by hand: v1.5.3
# taught the hint scan about Discord's launcher, and Discord stayed out of the
# list anyway, because /usr/bin/discord had not changed and its old "no" was
# still in here.
#
# 3 adds the gecko verdict.
MCA_CACHE_FORMAT="# middleclick-autoscroll detect 3 $MCA_VERSION"
_mca_cache_load() {
local path stamp verdict
local path stamp verdict first=1
(( MCA_CACHE_LOADED )) && return 0
MCA_CACHE_LOADED=1
[[ -r "$MCA_CACHEDIR/detect" ]] || return 0
while IFS=$'\t' read -r path stamp verdict; do
if (( first )); then
first=0
[[ $path == "$MCA_CACHE_FORMAT" ]] || return 0
continue
fi
[[ -n $path && -n $stamp ]] || continue
MCA_DETECT_CACHE["$path"]="$stamp"$'\t'"$verdict"
done < "$MCA_CACHEDIR/detect"
@@ -331,6 +374,7 @@ mca_cache_flush() {
mkdir -p "$MCA_CACHEDIR" 2>/dev/null || return 0
tmp="$(mktemp "$MCA_CACHEDIR/detect.XXXXXX")" || return 0
printf '%s\n' "$MCA_CACHE_FORMAT" > "$tmp"
for path in "${!MCA_DETECT_CACHE[@]}"; do
entry="${MCA_DETECT_CACHE[$path]}"
printf '%s\t%s\n' "$path" "$entry" >> "$tmp"
@@ -350,7 +394,7 @@ _mca_has_markers() {
[[ $dir == "$s" ]] && return 1
done
for s in "${MCA_SYSTEM_DIR_GLOBS[@]}"; do
# Unquoted on purpose - these are patterns, not names.
# Unquoted on purpose: these are patterns, not names.
# shellcheck disable=SC2053
[[ $dir == $s ]] && return 1
done
@@ -362,6 +406,26 @@ _mca_has_markers() {
return 1
}
# _mca_find_markers <directory> <depth>
# The same question for a whole tree, which is the shape a Flatpak, a snap and
# an unpacked AppImage come in: everything the application ships is somewhere
# under one root and there is no single directory that is "next to the binary".
# Hence the narrower list, see MCA_MARKERS_STRICT.
_mca_find_markers() {
local root="$1" depth="$2" m
local -a names=()
[[ -d $root ]] || return 1
for m in "${MCA_MARKERS_STRICT[@]}"; do
(( ${#names[@]} )) && names+=(-o)
names+=(-name "$m")
done
[[ -n "$(find "$root" -maxdepth "$depth" \
\( "${names[@]}" \) -print -quit 2>/dev/null)" ]]
}
# The plain assignments the script made before it handed over, for
# _mca_script_subst to read. A variable of its own rather than something passed
# around: every caller of _mca_script_target reads it through a command
@@ -373,9 +437,9 @@ declare -A MCA_SCRIPT_VARS=()
# The text with $NAME and ${NAME} replaced by what the script assigned to them,
# left in MCA_SUBST.
#
# Fails as soon as something turns up that only a running shell could work out
# - a positional parameter, a name the script never set, a default value. That
# is the point: an unresolvable path has to come out as no path at all, never
# Fails as soon as something turns up that only a running shell could work out,
# like a positional parameter, a name the script never set or a default value.
# That is the point: an unresolvable path has to come out as no path at all, never
# as a wrong one.
MCA_SUBST=''
@@ -415,8 +479,8 @@ _mca_script_subst() {
# openSUSE all set the directory and the program name into variables at the top
# of the script and end on `exec -a "$APPNAME" "$LIBDIR/$APPNAME"`. Without
# resolving those there is nothing to follow, and the answer would have to come
# from the hint scan - a weaker kind of evidence than finding the binary and
# its markers.
# from the hint scan, which is weaker evidence than finding the binary and its
# markers.
_mca_script_target() {
local script="$1" line tok name val skip=0
local -a tokens
@@ -424,6 +488,11 @@ _mca_script_target() {
MCA_SCRIPT_VARS=()
# Only assignments and exec lines matter, and the shell is slow at reading
# a big script line by line. So grep picks them out, and a script with no
# exec at all (winetricks has 20000 lines of that) is not read here at all.
grep -qaE '^[[:space:]]*exec[[:space:]]' -- "$script" 2>/dev/null || return 1
while IFS= read -r line; do
if [[ $line =~ $re_assign ]]; then
name="${BASH_REMATCH[2]}"
@@ -481,16 +550,17 @@ _mca_script_target() {
fi
return $?
done
done < "$script"
done < <(grep -aE '^[[:space:]]*((export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=|exec[[:space:]])' \
-- "$script" 2>/dev/null)
return 1
}
# mca_flags_candidates <program>
# The flag files a launcher reads, in the order it reads them, taken from the
# launcher itself rather than assumed from the distribution. Where the wrappers
# follow that convention - Arch's Electron and Chromium packages do, and take
# extra arguments from $XDG_CONFIG_HOME/<name>-flags.conf - it is a far better
# place to inject than a desktop entry: it survives package updates and it
# launcher itself rather than assumed from the distribution. Arch's Electron
# and Chromium packages follow that convention and take extra arguments from
# $XDG_CONFIG_HOME/<name>-flags.conf. Where the wrappers do that, it is a far
# better place to inject than a desktop entry: it survives package updates and it
# applies to a launch from the terminal too. Where they do not, this finds
# nothing and the caller falls back to the desktop entry.
mca_flags_candidates() {
@@ -503,7 +573,7 @@ mca_flags_candidates() {
# A wrapper that only execs another wrapper (heroic -> electron43) inherits
# that one's flag files, plus the specific name it builds from a variable
# at runtime and therefore never writes down - which is why that one is
# at runtime and therefore never writes down. That is why that one is
# derived from the target's own name rather than found.
#
# It is derived only once the target has shown that it reads a flag file at
@@ -521,18 +591,297 @@ mca_flags_candidates() {
fi
}
# mca_is_chromium <program>
# Succeeds when the program is a Chromium, CEF or Electron process.
mca_is_chromium() {
local prog="$1" verdict real stamp cached
# ---------------------------------------------------------------------------
# Looking inside an AppImage
# ---------------------------------------------------------------------------
#
# An AppImage is the AppImage runtime (an ordinary ELF executable) with a
# squashfs image appended to it. The payload is compressed, so nothing the
# application ships is on disk where the marker check could find it, and for a
# long time that made every AppImage a "cannot tell".
#
# It does not have to be. squashfs keeps the names of everything it holds in
# one table of its own, the table is a few kilobytes, and the names are the
# whole of what this question needs. So that table is read and inflated here -
# rather than unpacking a hundred megabytes, and rather than running the image
# to ask it, which is the one thing a scan started by a path unit must never
# do.
#
# Everything below fails closed. The moment a field is not where it should be,
# or the compressor is one there is no tool for, the answer goes back to
# "cannot tell" and the application is left to the applications screen.
[[ -n $prog && -e $prog ]] || return 1
# _mca_bytes <file> <offset> <count>
# Count bytes from an offset, as numbers, in MCA_BYTES. od rather than a shell
# read because a variable cannot hold a NUL byte and these headers are full of
# them. And a whole header at a time rather than a field at a time, because
# the first question below is asked about every program on the system and one
# od per field would be four forks per program.
MCA_BYTES=()
_mca_bytes() {
local file="$1" off="$2" count="$3"
MCA_BYTES=()
# read stops on end of input rather than on the delimiter it was given, so
# it reports failure for input that is perfectly complete. The count is
# the check.
read -r -d '' -a MCA_BYTES \
< <(od -An -tu1 -j"$off" -N"$count" -v -- "$file" 2>/dev/null)
(( ${#MCA_BYTES[@]} == count ))
}
# _mca_slice <file> <offset> <length>
# Length bytes from an offset, on stdout.
_mca_slice() {
dd if="$1" bs=65536 iflag=skip_bytes,count_bytes \
skip="$2" count="$3" status=none 2>/dev/null
}
# _mca_word <index>
# One 64-bit little-endian field out of MCA_BYTES, in MCA_WORD. Assigns rather
# than prints for the same reason _mca_bytes reads a whole header at a time:
# printing means a command substitution, and that is a fork per field.
#
# Fails for the all-ones value squashfs writes down for a table that is not in
# the image, which is a case the callers below have to tell from a real offset
# and bash arithmetic (signed, 64-bit) cannot represent.
MCA_WORD=0
_mca_word() {
local at="$1"
(( ${#MCA_BYTES[@]} >= at + 8 )) || return 1
(( MCA_BYTES[at+7] == 255 && MCA_BYTES[at+6] == 255 )) && return 1
MCA_WORD=$(( (MCA_BYTES[at] | MCA_BYTES[at+1] << 8 \
| MCA_BYTES[at+2] << 16 | MCA_BYTES[at+3] << 24) \
| ((MCA_BYTES[at+4] | MCA_BYTES[at+5] << 8 \
| MCA_BYTES[at+6] << 16 | MCA_BYTES[at+7] << 24) << 32) ))
return 0
}
# _mca_appimage_offset <file>
# Where the appended image starts, in MCA_APPIMAGE_AT: directly after the ELF,
# which is where its section header table ends. This is how the runtime works
# it out for --appimage-offset, and it is exact.
#
# Searching for the squashfs magic instead would not be: the runtime carries a
# copy of it in its own code. Neither is the AppImage magic at byte 8 of the
# ELF header a way in. It is there to be zeroed, and a build pipeline that
# does not want its image picked up by a desktop integration daemon does
# exactly that. So the offset is computed for any ELF at all and the caller
# finds out whether there is an image at it.
MCA_APPIMAGE_AT=0
_mca_appimage_offset() {
local shoff shentsize shnum
MCA_APPIMAGE_AT=0
_mca_bytes "$1" 0 64 || return 1
# \x7fELF, and then the class byte: 2 for the 64-bit header, 1 for the
# 32-bit one, which puts every field after it somewhere else.
(( MCA_BYTES[0] == 127 && MCA_BYTES[1] == 69 \
&& MCA_BYTES[2] == 76 && MCA_BYTES[3] == 70 )) || return 1
if (( MCA_BYTES[4] == 2 )); then
_mca_word 40 || return 1
shoff="$MCA_WORD"
shentsize=$(( MCA_BYTES[58] | MCA_BYTES[59] << 8 ))
shnum=$(( MCA_BYTES[60] | MCA_BYTES[61] << 8 ))
elif (( MCA_BYTES[4] == 1 )); then
shoff=$(( MCA_BYTES[32] | MCA_BYTES[33] << 8 \
| MCA_BYTES[34] << 16 | MCA_BYTES[35] << 24 ))
shentsize=$(( MCA_BYTES[46] | MCA_BYTES[47] << 8 ))
shnum=$(( MCA_BYTES[48] | MCA_BYTES[49] << 8 ))
else
return 1
fi
(( shoff > 0 && shentsize > 0 && shnum > 0 )) || return 1
MCA_APPIMAGE_AT=$(( shoff + shentsize * shnum ))
return 0
}
# _mca_inflate <compressor> <file> <offset> <length>
# One squashfs metadata block, decompressed onto stdout.
#
# lzo and lz4 are missing because squashfs stores them as bare blocks and
# neither lzop nor the lz4 tool will read one without the framing their own
# file format puts around it. An image compressed with either comes out as
# "cannot tell", which is where it started.
_mca_inflate() {
local comp="$1" file="$2" off="$3" len="$4"
case "$comp" in
1)
# squashfs stores a zlib stream and gzip only reads its own
# container, but underneath both are the same deflate data with a
# different wrapper around it. So the wrapper is swapped: zlib's
# two header bytes are dropped and a minimal gzip header put in
# front. gzip then writes every byte of the block and complains
# about the trailer it did not get, which is why its status is
# thrown away here and the check is on the output instead.
(( len > 2 )) || return 1
{
printf '\037\213\010\000\000\000\000\000\000\003'
_mca_slice "$file" $(( off + 2 )) $(( len - 2 ))
} | { gzip -dc 2>/dev/null || true; }
;;
2)
mca_have xz || return 1
_mca_slice "$file" "$off" "$len" \
| { xz -dc --format=lzma 2>/dev/null || true; }
;;
4)
mca_have xz || return 1
_mca_slice "$file" "$off" "$len" | { xz -dc 2>/dev/null || true; }
;;
6)
mca_have zstd || return 1
_mca_slice "$file" "$off" "$len" | { zstd -dc 2>/dev/null || true; }
;;
*) return 1 ;;
esac
}
# _mca_squashfs_names <file> <offset>
# The directory table of the image at that offset, inflated onto stdout. It is
# not parsed: the names sit in it as plain text between the records that
# describe them, and a name is all the caller is looking for.
#
# Fails unless the walk lands exactly on the end of the table. That is the
# integrity check: the block sizes adding up to the table's own length is what
# says the fields were read from a real superblock, and that the output is the
# whole of the names rather than some of them. It is also why the caller has
# to collect this before trusting it, never pipe it.
_mca_squashfs_names() {
local file="$1" base="$2"
local comp dir_start end field header size pos
_mca_bytes "$file" "$base" 96 || return 1
comp=$(( MCA_BYTES[20] | MCA_BYTES[21] << 8 ))
_mca_word 72 || return 1
dir_start="$MCA_WORD"
(( dir_start > 0 )) || return 1
# Where the names stop: the first table squashfs writes after them. Which
# one that is depends on the image. There is no fragment table when
# nothing was packed into a fragment, and no export table unless it was
# asked for. So they are tried in the order they are written and the
# first one that is actually there wins. bytes_used closes the list for an
# image that has none of them.
end=0
for field in 80 88 48 40; do
_mca_word "$field" || continue
(( MCA_WORD > dir_start )) || continue
end="$MCA_WORD"
break
done
(( end > dir_start )) || return 1
# Names for a hundred thousand files would still fit in a fraction of
# this. A table that claims more than it is a table that was misread.
(( end - dir_start > 8388608 )) && return 1
pos=$(( base + dir_start ))
end=$(( base + end ))
while (( pos < end )); do
_mca_bytes "$file" "$pos" 2 || return 1
header=$(( MCA_BYTES[0] | MCA_BYTES[1] << 8 ))
size=$(( header & 0x7fff ))
(( size > 0 && pos + 2 + size <= end )) || return 1
# The top bit says the block was stored as it is, which squashfs does
# for the ones compression made no smaller.
if (( header & 0x8000 )); then
_mca_slice "$file" $(( pos + 2 )) "$size" || return 1
else
_mca_inflate "$comp" "$file" $(( pos + 2 )) "$size" || return 1
fi
pos=$(( pos + 2 + size ))
done
(( pos == end ))
}
# _mca_appimage_verdict <file>
# What the image appended to this file contains, left in MCA_APPIMAGE:
# yes - Chromium, CEF or Electron
# no - the names of everything inside were read and none of them is
# unknown - there is an image, but its contents could not be read
# none - not an AppImage; there is nothing appended to this ELF
MCA_APPIMAGE=none
_mca_appimage_verdict() {
local file="$1" base names m
local -a args=()
MCA_APPIMAGE=none
_mca_appimage_offset "$file" || return 0
base="$MCA_APPIMAGE_AT"
# hsqs, the squashfs magic. Nothing there means nothing was appended, so
# this is an ordinary executable and not an AppImage at all.
_mca_bytes "$file" "$base" 4 || return 0
(( MCA_BYTES[0] == 104 && MCA_BYTES[1] == 115 \
&& MCA_BYTES[2] == 113 && MCA_BYTES[3] == 115 )) || return 0
# From here on there is an image, so the worst this can end on is "cannot
# tell". Never "no", which would be an answer about contents that were
# never read.
MCA_APPIMAGE=unknown
names="$(mktemp "${TMPDIR:-/tmp}/mca-names.XXXXXX")" || return 0
# Nothing at all in the table means every block failed to inflate (a
# compressor whose tool is not installed). That is a "cannot tell" too.
if _mca_squashfs_names "$file" "$base" > "$names" 2>/dev/null \
&& [[ -s $names ]]
then
for m in "${MCA_MARKERS_STRICT[@]}"; do args+=(-e "$m"); done
if grep -qaF "${args[@]}" -- "$names"; then
MCA_APPIMAGE=yes
else
MCA_APPIMAGE=no
fi
fi
rm -f -- "$names"
return 0
}
# mca_detect_verdict <program>
# What running this program starts, left in MCA_VERDICT:
# yes - Chromium, CEF or Electron
# no - something else
# unknown - an image whose payload could not be read, so neither answer has
# been earned and the applications screen offers it as a choice
# gecko:<rel> - Firefox or another Gecko application, which keeps its
# profiles in <rel>. See gecko.sh.
#
# Assigns rather than returns three states through an exit code, and is the one
# place the memo and the on-disk cache are consulted.
MCA_VERDICT=''
mca_detect_verdict() {
local prog="$1" real stamp cached
MCA_VERDICT=no
[[ -n $prog && -e $prog ]] || return 0
# Memoized under the path as given, so the same launcher named twice in a
# scan costs nothing at all the second time.
if [[ -n ${MCA_DETECT_MEMO[$prog]+set} ]]; then
[[ ${MCA_DETECT_MEMO[$prog]} == yes ]]
return $?
MCA_VERDICT="${MCA_DETECT_MEMO[$prog]}"
return 0
fi
_mca_cache_load
@@ -546,26 +895,52 @@ mca_is_chromium() {
if [[ -n $stamp && -n ${MCA_DETECT_CACHE[$prog]+set} ]]; then
cached="${MCA_DETECT_CACHE[$prog]}"
if [[ "${cached%%$'\t'*}" == "$stamp" ]]; then
verdict="${cached#*$'\t'}"
MCA_DETECT_MEMO[$prog]="$verdict"
[[ $verdict == yes ]]
return $?
MCA_VERDICT="${cached#*$'\t'}"
MCA_DETECT_MEMO[$prog]="$MCA_VERDICT"
return 0
fi
fi
real="$(readlink -f -- "$prog" 2>/dev/null)" || real="$prog"
verdict=no
if _mca_detect_uncached "$real"; then verdict=yes; fi
MCA_DETECT_UNSURE=0
MCA_DETECT_GECKO=''
if _mca_detect_uncached "$real"; then
MCA_VERDICT=yes
elif [[ -n $MCA_DETECT_GECKO ]]; then
MCA_VERDICT="gecko:$MCA_DETECT_GECKO"
elif (( MCA_DETECT_UNSURE )); then
MCA_VERDICT=unknown
else
MCA_VERDICT=no
fi
MCA_DETECT_MEMO[$prog]="$verdict"
MCA_DETECT_MEMO[$prog]="$MCA_VERDICT"
if [[ -n $stamp ]]; then
MCA_DETECT_CACHE["$prog"]="$stamp"$'\t'"$verdict"
MCA_DETECT_CACHE["$prog"]="$stamp"$'\t'"$MCA_VERDICT"
MCA_CACHE_DIRTY=1
fi
[[ $verdict == yes ]]
return 0
}
# mca_is_chromium <program>
# Succeeds when the program is a Chromium, CEF or Electron process. "Cannot
# tell" is not that, so it fails here. Anything that has to treat the two
# differently asks mca_detect_verdict instead.
mca_is_chromium() {
mca_detect_verdict "$1"
[[ $MCA_VERDICT == yes ]]
}
# Set by _mca_detect_uncached when it reaches the end without finding anything
# and the reason is that something could not be read, rather than that there
# was nothing there. Only mca_detect_verdict reads it, straight after the call.
MCA_DETECT_UNSURE=0
# Set by _mca_detect_uncached to where the profiles are, when the program turned
# out to be Gecko rather than Chromium.
MCA_DETECT_GECKO=''
_mca_detect_uncached() {
local real="$1" depth="${2:-0}" dir target
@@ -579,26 +954,48 @@ _mca_detect_uncached() {
if target="$(_mca_script_target "$real")" && [[ -n $target ]]; then
_mca_detect_uncached "$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")" \
$(( depth + 1 )) && return 0
[[ -n $MCA_DETECT_GECKO ]] && return 1
fi
grep -qE "$MCA_SCRIPT_HINTS" -- "$real" 2>/dev/null && return 0
return 1
fi
# A binary. Everything Chromium ships is unpacked next to it, either in the
# same directory or - for /opt/thing/bin/Thing layouts - one level up.
# same directory or (for /opt/thing/bin/Thing layouts) one level up.
dir="$(dirname -- "$real")"
_mca_has_markers "$dir" && return 0
[[ ${dir##*/} == bin ]] && _mca_has_markers "${dir%/*}" && return 0
# Not Chromium but Gecko, which has autoscroll of its own.
if _mca_gecko_rel "$dir" \
|| { [[ ${dir##*/} == bin ]] && _mca_gecko_rel "${dir%/*}"; }
then
MCA_DETECT_GECKO="$MCA_GECKO_REL"
return 1
fi
# An AppImage keeps all of that inside a filesystem appended to itself, so
# there is nothing next to the binary to find. But the names of everything
# in there can be read, and that settles it either way.
_mca_appimage_verdict "$real"
case "$MCA_APPIMAGE" in
yes) return 0 ;;
no) return 1 ;;
esac
# Last resort: Chromium's own argument table is in the binary. -m1 stops at
# the first hit, so this reads far less than the file size suggests.
#
# AppImages are the one thing this cannot see through - their payload is a
# compressed filesystem - which is why they come out as unknown and are
# left to the applications screen.
grep -qaFm1 -- 'enable-blink-features' "$real" 2>/dev/null && return 0
grep -qaFm1 -- 'CHROME_VERSION_EXTRA' "$real" 2>/dev/null && return 0
# What is left is an image that could not be read (an unsupported
# compressor) or the older AppImage layout, which is an ISO9660 filesystem
# and has no name table of this shape at all. Either way the answer is not
# "no", it is "nobody looked".
if [[ $MCA_APPIMAGE == unknown || $real == *.AppImage || $real == *.appimage ]]; then
MCA_DETECT_UNSURE=1
fi
return 1
}
@@ -612,8 +1009,9 @@ 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 | gecko | unknown | no
MCA_PACKAGING=() # native | flatpak | snap
MCA_PROFILES=() # where a Gecko application keeps its profiles, see gecko.sh
# A scan reads every desktop entry on the system, so the menu does it once and
# then redraws from what it found. Applying rescans on its own, so nothing else
@@ -626,15 +1024,15 @@ mca_scan_once() {
}
mca_scan() {
local dir file id name exec_line prog kind packaging i
local dir file id name exec_line prog kind packaging profiles i
local -A seen=()
local -a c_ids=() c_files=() c_names=() c_progs=() c_browser=() c_stat=()
MCA_IDS=(); MCA_FILES=(); MCA_NAMES=(); MCA_PROGS=(); MCA_KINDS=()
MCA_PACKAGING=()
MCA_PACKAGING=(); MCA_PROFILES=()
# Pass one: read the entries and work out what each of them starts. No
# detection yet - that needs a stat per program, and those are collected so
# detection yet: that needs a stat per program, and those are collected so
# they can be asked for all at once.
while IFS= read -r dir; do
[[ -d $dir ]] || continue
@@ -672,8 +1070,8 @@ mca_scan() {
fi
# The shortcuts Steam writes for single games are not
# applications of their own - a game is whatever engine it was
# built with, and none of those reads a Chromium argument - and
# applications of their own. A game is whatever engine it was
# built with, and none of those reads a Chromium argument. And
# starting the client through one needs nothing on its command
# line either.
mca_exec_is_steam_link "$exec_line" "$prog" && continue
@@ -688,14 +1086,15 @@ mca_scan() {
MCA_STAT=()
_mca_stat_batch "${c_stat[@]}"
# Pass two: decide what each one is. What it does - an application or a
# browser - and how it was packaged are two separate questions: a Chromium
# Pass two: decide what each one is. What it does (an application or a
# browser) and how it was packaged are two separate questions: a Chromium
# installed as a snap is still a browser, and somebody who has turned
# browsers off means that one too.
for i in "${!c_ids[@]}"; do
prog="${c_progs[i]}"
kind=no
packaging=native
profiles=''
if [[ $prog == flatpak:* ]]; then
packaging=flatpak
@@ -703,6 +1102,8 @@ mca_scan() {
kind=steam
elif mca_flatpak_is_chromium "${prog#flatpak:}"; then
(( c_browser[i] )) && kind=browser || kind=app
elif mca_flatpak_gecko "${prog#flatpak:}"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi
prog="${prog#flatpak:}"
elif [[ $prog == snap:* ]]; then
@@ -712,15 +1113,24 @@ mca_scan() {
kind=steam
elif mca_snap_is_chromium "$prog"; then
(( c_browser[i] )) && kind=browser || kind=app
elif mca_snap_gecko "$prog"; then
kind=gecko profiles="$MCA_GECKO_REL"
fi
elif mca_prog_is_steam "$prog"; then
# Steam is Chromium inside, but nothing about it can be changed
# from a command line argument; it has its own module.
kind=steam
elif mca_is_chromium "$prog"; then
(( c_browser[i] )) && kind=browser || kind=app
elif [[ $prog == *.AppImage || $prog == *.appimage ]]; then
kind=unknown
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
yes) (( c_browser[i] )) && kind=browser || kind=app ;;
unknown) kind=unknown ;;
gecko:*) kind=gecko profiles="${MCA_VERDICT#gecko:}" ;;
esac
fi
[[ $kind == no ]] && continue
@@ -731,10 +1141,12 @@ mca_scan() {
MCA_PROGS+=("$prog")
MCA_KINDS+=("$kind")
MCA_PACKAGING+=("$packaging")
MCA_PROFILES+=("$profiles")
done
mca_cache_flush
MCA_SCANNED=1
mca_config_migrate
}
# mca_has_flags_file <program>
@@ -770,8 +1182,8 @@ mca_desktop_is_browser() {
}
# mca_snap_name <program>
# The snap an executable belongs to, left in MCA_PROG - assigned rather than
# printed for the same reason mca_exec_program is: the scan asks this about
# The snap an executable belongs to, left in MCA_PROG. It is assigned rather
# than printed for the same reason mca_exec_program is: the scan asks this about
# every desktop entry on the system, and a command substitution per entry is a
# fork per entry.
#
@@ -813,13 +1225,7 @@ mca_snap_is_chromium() {
for d in "${MCA_SNAP_DIRS[@]}"; do
root="$d/$name/current"
[[ -d $root ]] || continue
if [[ -n "$(find "$root" -maxdepth 5 \
\( -name 'chrome_crashpad_handler' -o -name 'app.asar' \
-o -name 'libcef.so' -o -name 'v8_context_snapshot.bin' \
-o -name 'chrome-sandbox' \) \
-print -quit 2>/dev/null)" ]]
then
if _mca_find_markers "$root" 5; then
MCA_DETECT_MEMO[snap:$name]=yes
return 0
fi
@@ -833,21 +1239,14 @@ mca_snap_is_chromium() {
# Flatpak keeps every application in its own tree, so the marker check works the
# same way once that tree has been located.
mca_flatpak_is_chromium() {
local id="$1" loc
mca_have flatpak || return 1
local id="$1"
if [[ -n ${MCA_DETECT_MEMO[flatpak:$id]+set} ]]; then
[[ ${MCA_DETECT_MEMO[flatpak:$id]} == yes ]]
return $?
fi
loc="$(flatpak info --show-location "$id" 2>/dev/null)"
if [[ -n $loc && -d "$loc/files" ]] \
&& [[ -n "$(find "$loc/files" -maxdepth 4 \
\( -name 'chrome_crashpad_handler' -o -name 'app.asar' \
-o -name 'libcef.so' -o -name 'v8_context_snapshot.bin' \) \
-print -quit 2>/dev/null)" ]]
then
if mca_flatpak_location "$id" && _mca_find_markers "$MCA_FLATPAK_AT/files" 4; then
MCA_DETECT_MEMO[flatpak:$id]=yes
return 0
fi
@@ -856,28 +1255,33 @@ mca_flatpak_is_chromium() {
return 1
}
# mca_kind_wanted <kind> <id> [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_flatpak_location <app id>
# Where Flatpak keeps the application, in MCA_FLATPAK_AT. Assigned rather than
# printed so the memo survives: both the Chromium and the Gecko check ask.
declare -A MCA_FLATPAK_LOC=()
MCA_FLATPAK_AT=''
mca_flatpak_location() {
local id="$1"
if [[ -z ${MCA_FLATPAK_LOC[$id]+set} ]]; then
MCA_FLATPAK_LOC[$id]=''
mca_have flatpak \
&& MCA_FLATPAK_LOC[$id]="$(flatpak info --show-location "$id" 2>/dev/null)"
fi
MCA_FLATPAK_AT="${MCA_FLATPAK_LOC[$id]}"
[[ -n $MCA_FLATPAK_AT ]]
}
# mca_kind_wanted <kind> <id>
# 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 ]]
}
+254
View File
@@ -0,0 +1,254 @@
# shellcheck shell=bash
#
# Firefox, and everything else built on Gecko: LibreWolf, Zen, Floorp,
# Waterfox, Thunderbird.
#
# Gecko has autoscroll of its own, off on Linux. There is no command line flag
# for it, only a setting: general.autoScroll. Settings live in each profile, so
# the line goes into the profile's user.js, which Gecko reads at every start.
#
# Gecko copies what user.js sets into prefs.js. So undoing takes the line out
# of prefs.js as well. A browser that is still open would write it back when it
# quits, so for that profile user.js says false instead, and the record stays
# until the next undo can finish the job.
MCA_GECKO_BEGIN='// >>> middleclick-autoscroll'
MCA_GECKO_END='// <<< middleclick-autoscroll'
MCA_GECKO_ON='user_pref("general.autoScroll", true);'
MCA_GECKO_OFF='user_pref("general.autoScroll", false);'
# The profile folders one apply has done already, and the profiles.ini files
# the watcher should look at. Both are filled by mca_gecko_apply.
declare -A MCA_GECKO_SEEN=()
MCA_GECKO_WATCH=()
# Set by mca_gecko_revert when a profile is still open and has to be undone
# again later.
MCA_GECKO_PENDING=0
# _mca_gecko_rel <directory>
# Whether a Gecko application is installed there. If so, where it keeps its
# profiles, relative to the home or config folder, in MCA_GECKO_REL. Read from
# application.ini the way Gecko does it: [App] Profile= when it is set, else
# Vendor/Name, in lowercase.
MCA_GECKO_REL=''
_mca_gecko_rel() {
local dir="$1" line group='' vendor='' name='' profile='' rel=''
MCA_GECKO_REL=''
[[ -e $dir/libxul.so && -r $dir/application.ini ]] || return 1
while IFS= read -r line || [[ -n $line ]]; do
line="${line%$'\r'}"
case "$line" in
'['*) group="$line" ;;
Vendor=*) [[ $group == '[App]' ]] && vendor="${line#*=}" ;;
Name=*) [[ $group == '[App]' ]] && name="${line#*=}" ;;
Profile=*) [[ $group == '[App]' ]] && profile="${line#*=}" ;;
esac
done < "$dir/application.ini"
if [[ -n $profile ]]; then
rel="${profile//\\//}"
while [[ $rel == /* ]]; do rel="${rel#/}"; done
rel="${rel#.}"
elif [[ -n $name ]]; then
rel="${vendor:+$vendor/}$name"
fi
# Gecko lowercases in ASCII. A Turkish locale turns I into a dotless ı.
rel="${rel,,}"
rel="${rel//ı/i}"
[[ -n $rel && /$rel/ != */../* ]] || return 1
MCA_GECKO_REL="$rel"
}
# _mca_gecko_tree <root> <depth>
# The same for a Flatpak or a snap, where libxul.so is somewhere in the tree.
_mca_gecko_tree() {
local lib
MCA_GECKO_REL=''
[[ -d $1 ]] || return 1
lib="$(find "$1" -maxdepth "$2" -name libxul.so -print -quit 2>/dev/null)"
[[ -n $lib ]] && _mca_gecko_rel "${lib%/*}"
}
# mca_flatpak_gecko <app id> / mca_snap_gecko <snap name>
# Whether it is Gecko, with the folder in MCA_GECKO_REL. Memoized like the
# Chromium check, and an empty answer means no.
declare -A MCA_GECKO_MEMO=()
mca_flatpak_gecko() {
local key="flatpak:$1"
if [[ -z ${MCA_GECKO_MEMO[$key]+set} ]]; then
MCA_GECKO_REL=''
mca_flatpak_location "$1" && _mca_gecko_tree "$MCA_FLATPAK_AT/files" 4
MCA_GECKO_MEMO[$key]="$MCA_GECKO_REL"
fi
MCA_GECKO_REL="${MCA_GECKO_MEMO[$key]}"
[[ -n $MCA_GECKO_REL ]]
}
mca_snap_gecko() {
local key="snap:$1" d
if [[ -z ${MCA_GECKO_MEMO[$key]+set} ]]; then
MCA_GECKO_REL=''
for d in "${MCA_SNAP_DIRS[@]}"; do
_mca_gecko_tree "$d/$1/current" 5 && break
done
MCA_GECKO_MEMO[$key]="$MCA_GECKO_REL"
fi
MCA_GECKO_REL="${MCA_GECKO_MEMO[$key]}"
[[ -n $MCA_GECKO_REL ]]
}
# mca_gecko_roots <rel> <packaging> <name>
# Every folder that can hold the application's profiles.ini. An older install
# uses ~/.<rel>. A new one (Firefox 147 and later) uses $XDG_CONFIG_HOME/<rel>,
# or ~/<rel> for a fork with a profile name of its own. A Flatpak or a snap has
# the same inside its own folder.
mca_gecko_roots() {
local rel="$1" packaging="$2" name="$3" d
case "$packaging" in
flatpak)
printf '%s\n' "$HOME/.var/app/$name/.$rel" "$HOME/.var/app/$name/config/$rel"
;;
snap)
for d in common current; do
printf '%s\n' "$HOME/snap/$name/$d/.$rel" "$HOME/snap/$name/$d/.config/$rel"
done
;;
*)
printf '%s\n' "$HOME/.$rel" "$MCA_XDG_CONFIG/$rel"
[[ $rel == */* ]] || printf '%s\n' "$HOME/$rel"
;;
esac
}
# mca_gecko_profiles <root>
# Every profile folder that profiles.ini there lists.
mca_gecko_profiles() {
local root="$1" line path='' relative=1 inprofile=0
[[ -r $root/profiles.ini ]] || return 0
while IFS= read -r line || [[ -n $line ]]; do
line="${line%$'\r'}"
case "$line" in
'['*)
_mca_gecko_profile_emit
[[ $line == '[Profile'* ]] && inprofile=1 || inprofile=0
path='' relative=1
;;
Path=*) path="${line#Path=}" ;;
IsRelative=*) relative="${line#IsRelative=}" ;;
esac
done < "$root/profiles.ini"
_mca_gecko_profile_emit
}
_mca_gecko_profile_emit() {
(( inprofile )) && [[ -n $path ]] || return 0
[[ $relative == 1 ]] && path="$root/$path"
[[ -d $path ]] && printf '%s\n' "$path"
return 0
}
# _mca_gecko_write <user.js> <line>
# Puts the line into user.js as a marked block, replacing an older block.
# Returns like mca_write_if_changed.
_mca_gecko_write() {
local file="$1" line="$2" content=''
[[ -f $file ]] && content="$(_mca_flags_drop_block "$file" "$MCA_GECKO_BEGIN" "$MCA_GECKO_END")"
content="${content:+$content$'\n'}$MCA_GECKO_BEGIN"$'\n'"$line"$'\n'"$MCA_GECKO_END"
mca_write_if_changed "$file" "$content"$'\n'
}
# mca_gecko_apply <rel> <packaging> <name>
# Turns autoscroll on in every profile of the application.
mca_gecko_apply() {
local root profile
while IFS= read -r root; do
[[ -n ${MCA_GECKO_SEEN[$root]+set} ]] && continue
MCA_GECKO_SEEN[$root]=1
# A new profile is written into profiles.ini, so the watcher can pick
# it up.
MCA_GECKO_WATCH+=("$root/profiles.ini")
while IFS= read -r profile; do
_mca_gecko_profile_apply "$profile"
done < <(mca_gecko_profiles "$root")
done < <(mca_gecko_roots "$@")
}
_mca_gecko_profile_apply() {
local dir="$1" file="$1/user.js" detail=created
# A user.js that links somewhere else is part of a setup of its own.
[[ -L $file ]] && return 0
[[ -f $file ]] && detail=block
if ! grep -qxF -- "$MCA_GECKO_BEGIN" "$file" 2>/dev/null; then
# Set by the user already, in user.js or in the browser's own
# settings. Their choice: nothing is written and nothing recorded.
grep -qE '^[[:space:]]*user_pref\([[:space:]]*"general\.autoScroll"' \
-- "$file" 2>/dev/null && return 0
grep -qxF -- "$MCA_GECKO_ON" "$dir/prefs.js" 2>/dev/null && return 0
fi
_mca_gecko_write "$file" "$MCA_GECKO_ON"
case $? in
0) MCA_CHANGES=$(( MCA_CHANGES + 1 )) ;;
2) return 0 ;;
esac
# A file this program created stays one it created.
mca_ledger_has "$file" && detail="$(mca_ledger_detail "$file")"
mca_ledger_add gecko "$file" "$detail"
}
# mca_gecko_revert <user.js> <created|block>
# Returns 0 when something changed. Sets MCA_GECKO_PENDING when the profile is
# open and the record has to stay.
mca_gecko_revert() {
local file="$1" detail="$2" dir="${1%/*}" prefs content tmp rc=0
MCA_GECKO_PENDING=0
[[ -f $file && ! -L $file ]] || return 1
grep -qxF -- "$MCA_GECKO_BEGIN" "$file" 2>/dev/null || return 1
# The lock link only exists while the profile is open.
if [[ -L $dir/lock ]]; then
MCA_GECKO_PENDING=1
_mca_gecko_write "$file" "$MCA_GECKO_OFF"
return
fi
content="$(_mca_flags_drop_block "$file" "$MCA_GECKO_BEGIN" "$MCA_GECKO_END")"
if [[ $detail == created && -z ${content//[[:space:]]/} ]]; then
rm -f -- "$file"
else
printf '%s\n' "$content" > "$file"
fi
prefs="$dir/prefs.js"
if [[ -f $prefs && ! -L $prefs ]] && grep -qxF -- "$MCA_GECKO_ON" "$prefs"; then
tmp="$(mktemp "$prefs.XXXXXX")" || return 0
chmod --reference="$prefs" -- "$tmp" 2>/dev/null || chmod 600 -- "$tmp"
# grep says 1 when no line is left, which is fine here.
grep -vxF -- "$MCA_GECKO_ON" "$prefs" > "$tmp" || rc=$?
if (( rc <= 1 )); then mv -f -- "$tmp" "$prefs"; else rm -f -- "$tmp"; fi
fi
return 0
}
+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
}
+186 -67
View File
@@ -31,14 +31,25 @@ mca_ui_term_restore() {
MCA_TERM_SAVED=''
}
# Runs an action with the terminal handed back to normal line mode, so anything
# it prints - or prompts for - behaves the way a program expects.
mca_ui_cooked() {
mca_ui_term_restore
"$@"
local rc=$?
mca_ui_term_raw
return $rc
# What the last action printed. The menu shows it under the status until the
# next key, so nothing has to wait for a key on a screen of its own.
MCA_UI_MSG=''
# Set while the menu runs. Its status block already says that running
# applications need a restart, so the actions leave that line out there.
MCA_IN_MENU=''
# mca_ui_run <command...>
# Runs an action in this shell (so what it scans and loads stays) with its
# output kept for the menu. None of them asks anything.
mca_ui_run() {
local tmp
printf ' %s%s%s\n' "$MCA_C_DIM" "$(mca_msg "One moment...")" "$MCA_C_RESET"
tmp="$(mktemp)" || { "$@"; return; }
"$@" > "$tmp" 2>&1
MCA_UI_MSG="$(< "$tmp")"
rm -f -- "$tmp"
}
# mca_read_key
@@ -126,8 +137,8 @@ mca_pause() {
# _mca_row <label> <value>
# printf's %-28s pads by bytes, so a label containing "ü" comes out one column
# short. ${#s} counts characters in a UTF-8 locale, so the padding is computed
# here instead - and applied inline, because command substitution would eat the
# trailing spaces again.
# here instead. It is applied inline, because command substitution would eat
# the trailing spaces again.
_mca_row() {
local label="$1" value="$2" pad
pad=$(( 30 - ${#label} ))
@@ -172,13 +183,13 @@ mca_ui_status() {
if (( MCA_N_UNKNOWN )); then
_mca_row "$(mca_msg "Not identified")" \
"${MCA_C_DIM}$(mca_msg "%d - see the applications list" "$MCA_N_UNKNOWN")${MCA_C_RESET}"
"${MCA_C_DIM}$(mca_msg "%d (see the applications list)" "$MCA_N_UNKNOWN")${MCA_C_RESET}"
fi
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
@@ -194,6 +205,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
@@ -206,18 +229,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"
"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() {
@@ -262,10 +283,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"
@@ -273,15 +293,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 changes - q goes back"
mca_msg_into "$locale" "Up/Down: select, Space: change, q: back"
hint="$MCA_MSG_RESULT"
local clearseq
@@ -301,7 +326,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' \
@@ -309,6 +334,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"
@@ -365,32 +391,42 @@ mca_ui_edit_text() {
# turned on, and where a single application gets left out without having to
# switch off its whole category.
#
# The screen opens on a search field above the list, so typing filters it
# right away. The field is row -1: Down leaves it, Up from the top returns.
#
# Returns 0 when something was changed.
mca_ui_apps() {
local count key frame row pad i cursor=0 dirty=1 touched=0 locale
local -a labels=() states=()
local count key frame row pad i idx cursor=-1 dirty=1 touched=0 locale query='' needle
local footer flines size rows height top=0
local -a labels=() lower=() states=() matches=()
locale="$(mca_ui_locale)"
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"
mca_msg_into "$locale" "Up/Down: select, Space: turn on or off, q: back"
hint="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Anything not identified is left alone until it is turned on here."
local l_search hint_search nomatch
mca_msg_into "$locale" "Search:"; l_search="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Type to search, Down: go to the list, Esc: back"
hint_search="$MCA_MSG_RESULT"
mca_msg_into "$locale" "No matches."; nomatch="$MCA_MSG_RESULT"
mca_msg_into "$locale" 'Anything marked "cannot tell" stays off until it is turned on here.'
legend="$MCA_MSG_RESULT"
mca_msg_into "$locale" "Autoscroll is off - this is what would be covered."
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
local l_off l_cannot l_on l_steam l_flagfile l_launcher l_profile
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"
mca_msg_into "$locale" "profile"; l_profile="$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}"
@@ -398,6 +434,8 @@ 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 s_gecko="${s_on} ${MCA_C_DIM}(${l_profile})${MCA_C_RESET}"
local clearseq
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
@@ -408,8 +446,7 @@ mca_ui_apps() {
count=${#MCA_IDS[@]}
if (( count == 0 )); then
printf '\n %s\n' "$(mca_msg "No Chromium-based applications found.")"
mca_pause
MCA_UI_MSG="$(mca_msg "No supported applications found.")"
return 1
fi
@@ -417,56 +454,133 @@ mca_ui_apps() {
if (( dirty )); then
mca_config_load
mca_count_routes
labels=(); states=()
labels=(); lower=(); states=()
for i in "${!MCA_IDS[@]}"; do
labels+=("${MCA_NAMES[i]}")
row="${MCA_NAMES[i],,}"
lower+=("${row//ı/i}")
states+=("${MCA_ROUTES[i]}")
done
dirty=0
fi
# In a Turkish locale I lowercases to a dotless ı, so "office" would
# not find ONLYOFFICE. Both sides fold it back to i.
needle="${query,,}"
needle="${needle//ı/i}"
matches=()
for i in "${!labels[@]}"; do
[[ ${lower[i]} == *"$needle"* ]] && matches+=("$i")
done
count=${#matches[@]}
(( cursor >= count )) && cursor=$(( count - 1 ))
# The footer is built first: the list gets the rows it leaves over.
footer='' flines=1
# 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
footer+=" ${MCA_C_YELLOW}${warn}${MCA_C_RESET}"$'\n'
flines=$(( flines + 1 ))
fi
# Only needed while there is something it explains.
if (( MCA_N_UNKNOWN )); then
footer+=" ${MCA_C_DIM}${legend}${MCA_C_RESET}"$'\n'
flines=$(( flines + 1 ))
fi
if (( cursor < 0 )); then
footer+=" ${MCA_C_DIM}${hint_search}${MCA_C_RESET}"$'\n'
# Puts the terminal cursor at the end of the query (row 4).
printf -v row '\033[4;%dH' $(( 6 + ${#l_search} + ${#query} ))
footer+="$row"
else
footer+=" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n'
fi
# A list longer than the terminal would scroll the search field off
# the top. So only a window of it is drawn, and it follows the cursor.
# Five rows above the list, one below it, the footer, and the row the
# terminal cursor ends up on.
# A terminal that does not know its size says 0; then all of it is drawn.
size="$(stty size 2>/dev/null)"
rows="${size%% *}"
[[ $rows =~ ^[1-9][0-9]*$ ]] || rows=10000
height=$(( rows - 7 - flines ))
(( height < 1 )) && height=1
(( top > count - height )) && top=$(( count - height ))
(( top < 0 )) && top=0
if (( cursor >= 0 )); then
(( cursor < top )) && top=$cursor
(( cursor >= top + height )) && top=$(( cursor - height + 1 ))
fi
frame="$clearseq"$'\n'"${MCA_C_BOLD}${MCA_C_BLUE} ${title}${MCA_C_RESET}"$'\n\n'
local marker selected="${MCA_C_BLUE}▸${MCA_C_RESET} " shown
for i in "${!labels[@]}"; do
case "${states[i]}" in
if (( cursor < 0 )); then marker="$selected"; else marker=' '; fi
frame+=" ${marker}${l_search} ${query}"$'\n\n'
for (( i = top; i < count && i < top + height; i++ )); do
idx=${matches[i]}
case "${states[idx]}" in
off) shown="$s_off" ;;
unknown) shown="$s_cannot" ;;
steam) shown="$s_steam" ;;
flags) shown="$s_flags" ;;
spotify) shown="$s_spotify" ;;
gecko) shown="$s_gecko" ;;
*) shown="$s_desktop" ;;
esac
pad=$(( 34 - ${#labels[i]} ))
pad=$(( 34 - ${#labels[idx]} ))
(( pad < 0 )) && pad=0
if (( i == cursor )); then marker="$selected"; else marker=' '; fi
printf -v row ' %s%s%*s %s' "$marker" "${labels[i]}" "$pad" '' "$shown"
printf -v row ' %s%s%*s %s' "$marker" "${labels[idx]}" "$pad" '' "$shown"
frame+="$row"$'\n'
done
(( count )) || frame+=" ${MCA_C_DIM}${nomatch}${MCA_C_RESET}"$'\n'
frame+=$'\n'
# 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}${warn}${MCA_C_RESET}"$'\n'
# Where the window is, in the row under it, when there is more.
if (( count > height )); then
frame+=" ${MCA_C_DIM}$(( top + 1 ))–$(( i )) / ${count}${MCA_C_RESET}"$'\n'
else
frame+=$'\n'
fi
frame+=" ${MCA_C_DIM}${legend}${MCA_C_RESET}"$'\n'
frame+=" ${MCA_C_DIM}${hint}${MCA_C_RESET}"$'\n'
frame+="$footer"
printf '%s' "$frame"
key="$(mca_read_key)" || return "$(( ! touched ))"
# In the search field every key that can be typed goes into the query,
# q and the vi keys too.
if (( cursor < 0 )); then
case "$key" in
down|enter) (( count )) && cursor=0 ;;
up) cursor=$(( count - 1 )) ;;
backspace) query="${query%?}" ;;
space) query+=' ' ;;
escape)
[[ -z $query ]] && return "$(( ! touched ))"
query=''
;;
left|right) ;;
*) [[ $key == [[:print:]] ]] && query+="$key" ;;
esac
continue
fi
idx=${matches[cursor]}
case "$key" in
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
down|j) cursor=$(( (cursor + 1) % count )) ;;
up|k) cursor=$(( cursor - 1 )) ;;
down|j) cursor=$(( cursor + 1 < count ? cursor + 1 : -1 )) ;;
space|enter|right|left|l|h)
case "${states[cursor]}" in
case "${states[idx]}" in
off|unknown)
mca_config_list_del Skip "${MCA_IDS[cursor]}"
mca_config_list_add Include "${MCA_IDS[cursor]}"
mca_config_list_del Skip "${MCA_IDS[idx]}"
mca_config_list_add Include "${MCA_IDS[idx]}"
;;
*)
mca_config_list_del Include "${MCA_IDS[cursor]}"
mca_config_list_add Skip "${MCA_IDS[cursor]}"
mca_config_list_del Include "${MCA_IDS[idx]}"
mca_config_list_add Skip "${MCA_IDS[idx]}"
;;
esac
touched=1
@@ -483,8 +597,9 @@ mca_ui_apps() {
# ---------------------------------------------------------------------------
mca_ui_menu() {
local choice
local choice line
MCA_IN_MENU=1
mca_ui_term_raw
trap 'mca_ui_term_restore' EXIT INT TERM
@@ -495,6 +610,13 @@ mca_ui_menu() {
mca_head " $MCA_PRETTY"
mca_ui_status
printf '\n'
if [[ -n $MCA_UI_MSG ]]; then
while IFS= read -r line; do
printf ' %s\n' "$line"
done <<< "$MCA_UI_MSG"
printf '\n'
MCA_UI_MSG=''
fi
printf ' [1] %s\n' "$(mca_msg "Turn autoscroll on or off")"
printf ' [2] %s\n' "$(mca_msg "Re-apply everything")"
printf ' [3] %s\n' "$(mca_msg "Applications")"
@@ -512,13 +634,11 @@ mca_ui_menu() {
case "$choice" in
1)
MCA_UI_NEEDS_ACK=''
if [[ $CFG_ENABLED == yes ]]; then
mca_ui_cooked mca_do_disable
mca_ui_run mca_do_disable
else
mca_ui_cooked mca_do_enable
mca_ui_run mca_do_enable
fi
[[ -n $MCA_UI_NEEDS_ACK ]] && mca_pause
;;
# Not a plain apply: with the watcher running there is never
# anything left for one to do, and a menu entry that answers
@@ -527,21 +647,20 @@ mca_ui_menu() {
# what fixes an application that drifted, a flag file somebody
# edited, or Steam after it restored its own script.
2)
mca_ui_cooked mca_do_apply --rebuild
mca_pause
mca_ui_run mca_do_apply --rebuild
;;
3)
if mca_ui_apps; then
[[ $CFG_ENABLED == yes ]] && mca_ui_cooked mca_do_apply --rebuild
[[ $CFG_ENABLED == yes ]] && mca_ui_run mca_do_apply --rebuild
fi
;;
4)
if mca_ui_settings; then
[[ $CFG_ENABLED == yes ]] && mca_ui_cooked mca_do_apply --rebuild
[[ $CFG_ENABLED == yes ]] && mca_ui_run mca_do_apply --rebuild
fi
;;
q|Q) mca_ui_term_restore; trap - EXIT INT TERM; return 0 ;;
# Anything else - Enter, arrow keys, stray characters - just
# Anything else (Enter, arrow keys, stray characters) just
# redraws. Escape is deliberately not a quit key, so a mistyped
# arrow key cannot close the menu.
*) ;;
+77 -40
View File
@@ -4,19 +4,19 @@
#
# There are two ways in, and the order matters:
#
# 1. A flag file. Where the distribution wraps Electron and Chromium in a
# 1. A flag file. Some distributions wrap Electron and Chromium in a
# launcher script that reads extra arguments from
# $XDG_CONFIG_HOME/<name>-flags.conf - Arch and its derivatives do, and it
# is the convention their packages follow - this is the good one: it is
# the supported way to pass arguments, it survives package upgrades
# $XDG_CONFIG_HOME/<name>-flags.conf. Arch and its derivatives do, and
# their packages follow that convention. Where it exists, this is the good
# way in: it is the supported way to pass arguments, it survives package upgrades
# untouched, and it applies however the program is started, including from
# a terminal.
#
# Nothing is assumed about which distribution this is. The launcher itself
# is read, and the route is taken only for a launcher that really does
# name such a file. Elsewhere - Debian, Ubuntu, Fedora, openSUSE, where
# the equivalent file lives under /etc and is the system's to write -
# there is no flag file to use and route 2 answers for everything.
# name such a file. On Debian, Ubuntu, Fedora and openSUSE the
# equivalent file lives under /etc and is the system's to write. There is
# no flag file to use there, and route 2 answers for everything.
#
# 2. A desktop entry. For applications that ship their own binary with no
# wrapper, and for everything a Flatpak or a snap contains, there is
@@ -71,6 +71,11 @@ MCA_FEATURE_OPTS='--enable-blink-features|--enable-features'
# Where an argument has to go: after the program and its own arguments, but
# before the first field code (%U and friends) or Flatpak's @@ markers, which
# are placeholders the launcher expands rather than arguments.
#
# A bare "--" stops it too. That is where Chromium stops reading switches and
# starts treating everything as an address, so a flag put after it is read as a
# page to open and the feature never comes on. Discord's entry is exactly that
# shape: `discord --url -- %u`.
_mca_exec_insert_at() {
local rest="$1" tok idx=0
@@ -79,7 +84,7 @@ _mca_exec_insert_at() {
[[ -n $rest ]] || break
tok="${rest%% *}"
case "$tok" in
%[a-zA-Z]|@@|@@u) printf '%s\n' "$idx"; return 0 ;;
%[a-zA-Z]|@@|@@u|--) printf '%s\n' "$idx"; return 0 ;;
esac
rest="${rest:${#tok}}"
idx=$(( idx + ${#tok} ))
@@ -176,8 +181,8 @@ mca_flags_apply() {
done
[[ -n $existing_target ]] && return 0
# None exists yet. The last candidate is the wrapper's own fallback - the
# generic electron-flags.conf rather than electron43-flags.conf - so
# None exists yet. The last candidate is the wrapper's own fallback (the
# generic electron-flags.conf rather than electron43-flags.conf). So
# creating that one also covers every other application using the same
# shared Electron.
target="${candidates[-1]}"
@@ -255,8 +260,9 @@ _mca_flags_write() {
return 0
}
# _mca_flags_drop_block <file> [begin marker] [end marker]
_mca_flags_drop_block() {
awk -v b="$MCA_MARK_BEGIN" -v e="$MCA_MARK_END" '
awk -v b="${2:-$MCA_MARK_BEGIN}" -v e="${3:-$MCA_MARK_END}" '
$0 == b { skip = 1; next }
$0 == e { skip = 0; next }
!skip
@@ -319,7 +325,7 @@ 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
# user who starts Steam from "Library" expects the same behaviour there.
_mca_desktop_transform() {
@@ -360,8 +366,8 @@ mca_desktop_apply() {
local target="$MCA_APPDIR/$id.desktop" content backup
if [[ "$src" == "$target" ]]; then
# 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
# The user's own entry, like an AppImage, a web app shortcut or
# something installed by hand. There is nowhere to shadow it from, so it is
# edited directly and the original is kept.
grep -q "^$MCA_MARK_INPLACE=" "$src" 2>/dev/null && return 0
@@ -370,7 +376,7 @@ mca_desktop_apply() {
[[ "$content" == "$(< "$src")" ]] && return 0
backup="$(mca_backup "$src")" || return 1
if mca_write_if_changed "$src" "$content"$'\n'; then
if _mca_entry_write "$src" "$content"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
mca_ledger_add inplace "$src" "$backup"
@@ -383,7 +389,7 @@ mca_desktop_apply() {
mkdir -p "$MCA_APPDIR" 2>/dev/null || return 1
# 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
# 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
@@ -407,22 +413,42 @@ mca_desktop_apply() {
# entry in the menu completely, which is why Discord launched at login used
# to behave differently from Discord launched from the menu.
#
# The desktop folder, where a shortcut somebody dragged out of the menu -
# or asked Steam for - lives and nowhere else.
# The desktop folder, where a shortcut somebody dragged out of the menu
# (or asked Steam for) lives and nowhere else.
#
# Neither can be shadowed from anywhere, so both are edited where they stand,
# with the original kept.
# _mca_entry_patch_inplace <file> <gate: autostart|apps>
# _mca_entry_write <file> <content>
# Writes an entry edited in place, like mca_write_if_changed. A symlink is
# replaced by the edited file instead of written through: it usually points at
# a package's entry in /usr, which is not ours to change. Undo puts the link
# back from the backup.
_mca_entry_write() {
local file="$1" content="$2" tmp
[[ -L $file ]] || { mca_write_if_changed "$file" "$content"; return; }
tmp="$(mktemp "$file.XXXXXX")" || return 2
chmod --reference="$file" -- "$tmp" 2>/dev/null || chmod 644 -- "$tmp"
# A link into /usr/share/applications is trusted for where it points. A
# copy on the desktop is only trusted by KDE when it can be run.
chmod u+x -- "$tmp" 2>/dev/null || true
if printf '%s' "$content" > "$tmp" && mv -f -- "$tmp" "$file"; then
return 0
fi
rm -f -- "$tmp"
return 2
}
# _mca_entry_patch_inplace <file>
# 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
@@ -471,12 +497,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")")"
@@ -485,7 +507,7 @@ _mca_entry_patch_inplace() {
[[ "$content" == "$(< "$file")" ]] && return 0
backup="$(mca_backup "$file")" || return 0
if mca_write_if_changed "$file" "$content"$'\n'; then
if _mca_entry_write "$file" "$content"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
mca_ledger_add inplace "$file" "$backup"
@@ -498,7 +520,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
}
@@ -510,7 +532,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
}
@@ -568,7 +590,7 @@ mca_spotify_apply() {
# Uninstalling something deletes its entry from /usr/share/applications, but the
# copy shadowing it is in the user's home and pacman knows nothing about it. It
# would sit in the menu forever, offering to start a program that is no longer
# installed - and the watcher would not notice, because a plain apply only ever
# installed. And the watcher would not notice, because a plain apply only ever
# looks at what is there now. A file that was edited where it stood and has
# since been deleted is the same problem from the other side: its record would
# have `disable` put the file back.
@@ -610,7 +632,8 @@ mca_prune_orphans() {
# ---------------------------------------------------------------------------
mca_revert_all() {
local kind path detail
local kind path detail backup
local -a keep=()
[[ -f $MCA_LEDGER ]] || return 0
@@ -626,12 +649,15 @@ mca_revert_all() {
fi
;;
inplace)
if [[ -n $detail && -f "$MCA_BACKUPDIR/$detail" ]]; then
backup="$MCA_BACKUPDIR/$detail"
if [[ -n $detail ]] && [[ -f $backup || -L $backup ]]; then
if [[ -e $path ]] || [[ -d "$(dirname -- "$path")" ]]; then
cp -p -- "$MCA_BACKUPDIR/$detail" "$path" 2>/dev/null \
# A link goes back as a link, not written through.
[[ -L $backup ]] && rm -f -- "$path"
cp -P -p -- "$backup" "$path" 2>/dev/null \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
rm -f -- "$MCA_BACKUPDIR/$detail"
rm -f -- "$backup"
elif [[ -f $path ]]; then
# No backup: the file did not exist before we wrote it.
rm -f -- "$path"
@@ -646,10 +672,21 @@ 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 ))
;;
gecko)
mca_gecko_revert "$path" "$detail" \
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
# Still open, so not done yet. See gecko.sh.
(( MCA_GECKO_PENDING )) && keep+=("$kind"$'\t'"$path"$'\t'"$detail")
;;
esac
done < <(tac "$MCA_LEDGER" 2>/dev/null)
rm -f -- "$MCA_LEDGER"
(( ${#keep[@]} )) && printf '%s\n' "${keep[@]}" > "$MCA_LEDGER"
rmdir "$MCA_BACKUPDIR" 2>/dev/null || true
return 0
}
+161 -68
View File
@@ -2,25 +2,29 @@
#
# Steam.
#
# Steam's interface is CEF, so the feature is there - but Steam builds the
# Steam's interface is CEF, so the feature is there. But Steam builds the
# command line for its web helper itself and offers no way to add to it. The
# only place to get an argument in is the shell script that starts the helper,
# which lives inside Steam's own installation:
#
# ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
#
# Steam compares the installed files against its manifest at every start - by
# size and timestamp rather than by content - and restores whatever differs.
# The steamrt3 client, a beta for now, has a script of its own:
#
# ~/.local/share/Steam/steamrt64/steamwebhelper.sh
#
# Steam compares the installed files against its manifest at every start (by
# size and timestamp, not by content) and restores whatever differs.
# So the patch is written to look untouched: the bytes the flag costs are taken
# back out of the script's comments and the timestamp is put back afterwards,
# which leaves a file that is byte-for-byte the size Steam recorded and as old
# as Steam left it. A client that verifies its files finds nothing to repair
# and the flag survives however Steam was started - from the menu, from a game
# shortcut, from a launcher like Heroic, from a terminal.
# and the flag survives however Steam was started: from the menu, from a game
# shortcut, from a launcher like Heroic, or from a terminal.
#
# A patch that changes the length is not an option, and no switch covers for
# one. Steam checks its files at a start it was given arguments for, but also
# at the shutdown it runs itself - and that one has no -noverifyfiles on its
# at the shutdown it runs itself. That one has no -noverifyfiles on its
# command line, whatever the session was started with. What a single wrong
# length costs is the whole client package downloaded, extracted and installed
# again, and a client that quits at the end of it instead of coming up. So the
@@ -28,6 +32,10 @@
# all: no autoscroll in the interface is a smaller thing than a client that
# reinstalls itself.
#
# The steamrt3 script has no comments to give back. So it is copied aside, as
# it is, and replaced by a stub of the same size that runs the copy with the
# flag. See _mca_steam_stub.
#
# The file comes back on every client update, and the watcher re-applies the
# patch when that happens.
@@ -60,24 +68,28 @@ mca_steam_roots() {
done
}
# The script to patch inside a Steam installation. Newer clients start the
# helper through a wrapper inside the container runtime; older ones exec it
# from steamwebhelper.sh directly, and that one forwards its arguments too.
mca_steam_script() {
local root="$1" f
# The scripts to patch inside a Steam installation, one per client. The default
# client starts the helper through a wrapper inside the container runtime;
# older ones exec it from steamwebhelper.sh directly, and that one forwards its
# arguments too. The steamrt3 client has its own. Both clients are patched
# where both are there, so switching between them keeps autoscroll.
mca_steam_scripts() {
local root="$1" f found=1
for f in \
"$root/ubuntu12_64/steamwebhelper_sniper_wrap.sh" \
"$root/ubuntu12_64/steamwebhelper.sh"
do
[[ -f $f && -w $f ]] && { printf '%s\n' "$f"; return 0; }
[[ -f $f && -w $f ]] && { printf '%s\n' "$f"; found=0; break; }
done
return 1
f="$root/steamrt64/steamwebhelper.sh"
[[ -f $f && -w $f ]] && { printf '%s\n' "$f"; found=0; }
return "$found"
}
mca_steam_installed() {
local root
while IFS= read -r root; do
mca_steam_script "$root" > /dev/null && return 0
mca_steam_scripts "$root" > /dev/null && return 0
done < <(mca_steam_roots)
return 1
}
@@ -87,13 +99,17 @@ mca_steam_script_patched() {
grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null
}
# Whether every script there is patched. One that is not is a client that
# starts without autoscroll.
mca_steam_patched() {
local root script
local root script found=0
while IFS= read -r root; do
script="$(mca_steam_script "$root")" || continue
mca_steam_script_patched "$script" && return 0
while IFS= read -r script; do
mca_steam_script_patched "$script" || return 1
found=1
done < <(mca_steam_scripts "$root")
done < <(mca_steam_roots)
return 1
(( found ))
}
# _mca_steam_exec_line <script>
@@ -114,8 +130,8 @@ _mca_steam_exec_line() {
# The patched script on stdout: the flags appended to the line that starts the
# web helper, and the same number of bytes taken back out of the script's
# comments, so that the file Steam finds is the length Steam wrote down.
# Everything else - setting up the container runtime, deciding whether the
# sandbox can be used - is left alone.
# Everything else is left alone, like setting up the container runtime or
# deciding whether the sandbox can be used.
#
# Comments are eaten from the bottom up, so the header that says what the
# script is for is the last thing to lose anything, and the '#' itself always
@@ -163,6 +179,81 @@ _mca_steam_same_size() {
(( $(wc -c < "$script") == $(printf '%s\n' "$content" | wc -c) ))
}
# Where a stub keeps Steam's own script, and the line that runs it from there.
MCA_STEAM_ORIG=.orig
MCA_STEAM_STUB="exec \"\$0$MCA_STEAM_ORIG\" \"\$@\""
# _mca_steam_stub <script> <flags>
# The stub on stdout, for a script with no comments to pay for the flags. It
# is exactly as long as the script and runs Steam's own script, kept next to
# it, with the flags added. That script hands its arguments on to the helper.
# Steam only checks the files in its manifest, and the copy is not one of them.
# Fails when the script does not hand its arguments on, has no #! line, or is
# too short to hold the stub.
_mca_steam_stub() {
local script="$1" flags="$2" head body pad
[[ -n "$(_mca_steam_exec_line "$script")" ]] || return 1
head="$(head -n 1 -- "$script")"
[[ $head == '#!'* ]] || return 1
body="$MCA_STEAM_STUB $flags"
pad=$(( $(wc -c < "$script") - $(printf '%s\n%s\n' "$head" "$body" | wc -c) ))
(( pad >= 0 )) || return 1
# The bytes left over go into a comment line.
printf '%s\n' "$head"
if (( pad == 1 )); then
printf '\n'
elif (( pad > 1 )); then
printf '#%*s\n' $(( pad - 2 )) ''
fi
printf '%s' "$body"
}
# _mca_steam_is_stub <script>
_mca_steam_is_stub() {
grep -qF -- "$MCA_STEAM_STUB" "$1" 2>/dev/null
}
# _mca_steam_text <script> <flags>
# The patched script on stdout: from the comments if they can pay for the
# flags, as a stub if not. Fails when neither keeps the size.
_mca_steam_text() {
local script="$1" flags="$2" out
if ! { out="$(_mca_steam_build "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out"; }
then
out="$(_mca_steam_stub "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out" || return 1
fi
printf '%s' "$out"
}
# _mca_steam_write <script> <backup> <content>
# Writes the patched script. <backup> is the untouched copy kept for undoing.
_mca_steam_write() {
local script="$1" backup="$2" content="$3"
# A stub needs Steam's script next to it before it can start anything.
if [[ $content == *"$MCA_STEAM_STUB"* ]]; then
cp -p -- "$backup" "$script$MCA_STEAM_ORIG" 2>/dev/null || return 1
chmod +x -- "$script$MCA_STEAM_ORIG" 2>/dev/null || true
fi
if mca_write_if_changed "$script" "$content"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
chmod +x -- "$script" 2>/dev/null || true
# Steam looks at when the file was last written as well as at how big
# it is, and the copy taken before the edit still carries the original
# timestamp. Putting it back costs nothing and removes the other half
# of what the client would notice.
touch -r "$backup" -- "$script" 2>/dev/null || true
}
# _mca_steam_refresh_backup <script>
# A client update brings a new version of the script, and the copy kept from
# before it is no longer what putting it back means. Only ever done while the
@@ -180,8 +271,8 @@ _mca_steam_refresh_backup() {
# The script is already patched. Whether that is a patch Steam can live with is
# a question of length: an earlier version of this program appended the flag
# where the comments could not pay for it, which leaves the script longer than
# the file Steam wrote down - and a client that checks its files reinstalls
# itself over that, then quits instead of starting.
# the file Steam wrote down. A client that checks its files reinstalls itself
# over that, then quits instead of starting.
#
# Rebuilt from the untouched original kept when the script was first patched,
# never from the patched file, the flag either fits this time or the original
@@ -195,15 +286,18 @@ _mca_steam_settle() {
# Nothing to compare against, so nothing can be said about the length.
[[ -f $copy ]] || return 0
# Already the length Steam expects: the patch is a good one.
(( $(wc -c < "$script") == $(wc -c < "$copy") )) && return 0
if out="$(_mca_steam_build "$copy" "$flags")" && _mca_steam_same_size "$copy" "$out"; then
if mca_write_if_changed "$script" "$out"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
# Already the length Steam expects: the patch is a good one. Unless it is
# a stub that lost Steam's script next to it, and would start nothing.
if (( $(wc -c < "$script") == $(wc -c < "$copy") )); then
if _mca_steam_is_stub "$script" && [[ ! -f $script$MCA_STEAM_ORIG ]]; then
cp -p -- "$copy" "$script$MCA_STEAM_ORIG" 2>/dev/null || true
fi
chmod +x -- "$script" 2>/dev/null || true
touch -r "$copy" -- "$script" 2>/dev/null || true
return 0
fi
if out="$(_mca_steam_text "$copy" "$flags")" \
&& _mca_steam_write "$script" "$copy" "$out"
then
mca_ledger_add steam "$script" "$name"
return 0
fi
@@ -224,41 +318,31 @@ mca_steam_apply() {
flags="$(mca_flags)"
while IFS= read -r root; do
script="$(mca_steam_script "$root")" || continue
found=1
while IFS= read -r script; do
found=1
if mca_steam_script_patched "$script"; then
_mca_steam_settle "$script" "$flags"
continue
fi
if mca_steam_script_patched "$script"; then
_mca_steam_settle "$script" "$flags"
continue
fi
_mca_steam_refresh_backup "$script"
_mca_steam_refresh_backup "$script"
# Either the comments can pay for the flag or nothing is written.
# A longer file is one Steam reinstalls itself over; a client update
# that changed how the helper is started leaves no line to patch at
# all. Both end here, and this script is what starts Steam's entire
# interface, so leaving it alone is the only safe answer to either.
if ! { out="$(_mca_steam_build "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out"; }
then
mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")"
continue
fi
# Either the file keeps its length or nothing is written. A
# longer file is one Steam reinstalls itself over; a client
# update that changed how the helper is started leaves no line
# to patch at all. Both end here, and this script is what starts
# Steam's entire interface, so leaving it alone is the only safe
# answer to either.
if ! out="$(_mca_steam_text "$script" "$flags")"; then
mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")"
continue
fi
backup="$(mca_backup "$script")" || continue
if mca_write_if_changed "$script" "$out"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
chmod +x -- "$script" 2>/dev/null || true
# Steam looks at when the file was last written as well as at how big
# it is, and the copy taken before the edit still carries the original
# timestamp. Putting it back costs nothing and removes the other half
# of what the client would notice.
touch -r "$MCA_BACKUPDIR/$backup" -- "$script" 2>/dev/null || true
mca_ledger_add steam "$script" "$backup"
backup="$(mca_backup "$script")" || continue
_mca_steam_write "$script" "$MCA_BACKUPDIR/$backup" "$out" || continue
mca_ledger_add steam "$script" "$backup"
done < <(mca_steam_scripts "$root")
done < <(mca_steam_roots)
(( found ))
@@ -266,7 +350,9 @@ mca_steam_apply() {
# mca_steam_revert <script> <backup name>
mca_steam_revert() {
local script="$1" backup="$2"
local script="$1" backup="$2" orig="$1$MCA_STEAM_ORIG" stub=1 tmp
_mca_steam_is_stub "$script" || stub=0
if [[ -n $backup && -f "$MCA_BACKUPDIR/$backup" ]]; then
if [[ -e $script ]]; then
@@ -274,14 +360,21 @@ mca_steam_revert() {
chmod +x -- "$script" 2>/dev/null || true
fi
rm -f -- "$MCA_BACKUPDIR/$backup"
elif (( stub )) && [[ -f $orig ]]; then
# No backup, but a stub keeps Steam's own script right next to it.
cp -p -- "$orig" "$script" 2>/dev/null || return 1
else
# No backup to fall back on, so take the flags back out of the exec line.
[[ -f $script ]] || return 1
tmp="$(mktemp "${script}.XXXXXX")" || return 1
sed "s| $MCA_FLAG||g" "$script" > "$tmp" && mv -f "$tmp" "$script" || { rm -f "$tmp"; return 1; }
chmod +x -- "$script" 2>/dev/null || true
return 0
fi
# No backup to fall back on - take the flags back out of the exec line.
[[ -f $script ]] || return 1
local tmp
tmp="$(mktemp "${script}.XXXXXX")" || return 1
sed "s| $MCA_FLAG||g" "$script" > "$tmp" && mv -f "$tmp" "$script" || { rm -f "$tmp"; return 1; }
chmod +x -- "$script" 2>/dev/null || true
# The copy goes once the stub that ran it is gone.
if (( stub )) && ! _mca_steam_is_stub "$script"; then
rm -f -- "$orig"
fi
return 0
}
+13 -10
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
#
# middleclick-autoscroll - middle-click autoscroll for Chromium-based
# applications on Linux
# middleclick-autoscroll: middle-click autoscroll for Chromium-based
# applications and Firefox on Linux
#
# Blink has had Windows-style autoscroll for years, behind a runtime flag that
# is not turned on for Linux because middle click is taken by primary-selection
# paste there. Turning it on is a matter of one command line argument - the
# paste there. Turning it on is a matter of one command line argument. The
# work is finding every application that needs it, putting the argument
# somewhere it survives an update, and doing the same for the two programs that
# do not take arguments at all.
# do not take arguments at all. Firefox has the same feature behind a setting.
#
# Copyright (C) 2026 Felitendo
# SPDX-License-Identifier: GPL-3.0-or-later
@@ -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 gecko 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
@@ -50,13 +50,14 @@ mca_do_enable() {
if mca_watch_available; then
mca_watch_enable || mca_note "$(mca_msg "Could not start the watcher for newly installed applications.")"
else
mca_note "$(mca_msg "No systemd user session - newly installed applications will not be picked up on their own.")"
mca_note "$(mca_msg "No systemd user session, so newly installed applications will not be picked up on their own.")"
fi
fi
mca_ok "$(mca_msg "Autoscroll is on for %d applications." "$MCA_N_ON")"
mca_note "$(mca_msg "Applications that are already running pick this up when they are restarted.")"
[[ -n $MCA_IN_MENU ]] \
|| mca_note "$(mca_msg "Applications that are already running pick this up when they are restarted.")"
}
mca_do_disable() {
@@ -107,10 +108,12 @@ mca_do_apply() {
if (( rebuild )); then
mca_ok "$(mca_msg "Applied again to %d applications." "$MCA_N_ON")"
mca_say " $(mca_msg "Applications that are already running pick this up when they are restarted.")"
[[ -n $MCA_IN_MENU ]] \
|| mca_say " $(mca_msg "Applications that are already running pick this up when they are restarted.")"
elif (( MCA_CHANGES )); then
mca_ok "$(mca_msg "Applied to %d applications." "$MCA_N_ON")"
mca_say " $(mca_msg "Applications that are already running pick this up when they are restarted.")"
[[ -n $MCA_IN_MENU ]] \
|| mca_say " $(mca_msg "Applications that are already running pick this up when they are restarted.")"
else
mca_ok "$(mca_msg "Already applied to %d applications." "$MCA_N_ON")"
fi
@@ -141,7 +144,7 @@ mca_do_list() {
mca_count_routes
if (( ${#MCA_IDS[@]} == 0 )); then
mca_note "$(mca_msg "No Chromium-based applications found.")"
mca_note "$(mca_msg "No supported applications found.")"
return 0
fi