Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5d2481d6d | ||
|
|
fc9dae95f9 | ||
|
|
dd0671440d | ||
|
|
0b1b33ea84 | ||
|
|
e3278ecf27 | ||
|
|
2401855b70 | ||
|
|
da291cefa5 | ||
|
|
e2e05748f7 | ||
|
|
c20ea98de4 | ||
|
|
d0d977ff56 | ||
|
|
cf48fc47df | ||
|
|
335d4cc9ec | ||
|
|
c7222d22bd | ||
|
|
4fffba005d | ||
|
|
b93d0f5f1e | ||
|
|
d39e669743 | ||
|
|
f9826edc24 | ||
|
|
5e645acbd0 | ||
|
|
1042639a08 | ||
|
|
ab8c6477cb | ||
|
|
0fbe8bdb39 | ||
|
|
a992efb0cb | ||
|
|
b97ccf0379 | ||
|
|
7a446364dc | ||
|
|
449a20ce9b | ||
|
|
ae4e8b9294 | ||
|
|
b4fa0edf28 | ||
|
|
e09b145f3a | ||
|
|
004094ef78 | ||
|
|
0c4e7645d0 |
No files matched your search
@@ -0,0 +1 @@
|
||||
buy_me_a_coffee: felitendo
|
||||
Executable
+136
@@ -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://buymeacoffee.com/felitendo"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="48"></a>
|
||||
|
||||
----
|
||||
|
||||
EOF
|
||||
else
|
||||
printf '%s\n\n' "$entry"
|
||||
fi
|
||||
printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# 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.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
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 the result once and push. No commits for attempts that did not work. 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>)"`.
|
||||
@@ -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.5.4
|
||||
|
||||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
@@ -19,17 +19,21 @@ 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
|
||||
@@ -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)"; \
|
||||
|
||||
@@ -1,166 +1,162 @@
|
||||
# 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">
|
||||
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://buymeacoffee.com/felitendo"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" 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. Space turns one on or off.
|
||||
|
||||
<p align="center">
|
||||
<img src="res/screenshots/applications.png" alt="The applications list in Konsole, each with how it 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.
|
||||
**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.
|
||||
|
||||
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.
|
||||
</details>
|
||||
|
||||
## Steam
|
||||
<details>
|
||||
<summary>Commands</summary>
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
@@ -21,15 +21,16 @@ 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.
|
||||
|
||||
@@ -54,7 +55,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
|
||||
@@ -84,7 +85,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 +97,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
|
||||
@@ -115,7 +116,7 @@ entry entirely. Those are patched in place as well.
|
||||
|
||||
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.
|
||||
@@ -133,14 +134,14 @@ _~/.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
|
||||
@@ -172,11 +173,66 @@ and has a configuration file with a slot for extra arguments; that slot is
|
||||
where the flag goes. Installed as a plain package, a Flatpak or a snap, it is
|
||||
an ordinary desktop entry and needs nothing special.
|
||||
|
||||
# WHAT CANNOT BE DETECTED
|
||||
# MIDDLE-CLICK PASTE
|
||||
|
||||
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.
|
||||
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 +246,17 @@ _~/.local/state/middleclick-autoscroll/ledger_
|
||||
_~/.local/state/middleclick-autoscroll/backup/_
|
||||
Copies of the files that are edited in place rather than shadowed.
|
||||
|
||||
_~/.config/kwinrc_
|
||||
KDE's own configuration. One key in it, *EnablePrimarySelection*, is
|
||||
written when the desktop's middle-click paste is turned off, and put
|
||||
back by *disable*.
|
||||
|
||||
_~/.cache/middleclick-autoscroll/detect_
|
||||
Which programs were found to be Chromium, keyed by size and modification
|
||||
time. Safe to delete.
|
||||
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 +269,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
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: middleclick-autoscroll 1.0.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2026-08-16 00:00+0200\n"
|
||||
"PO-Revision-Date: 2026-08-16 00:00+0200\n"
|
||||
"Last-Translator: Felitendo\n"
|
||||
@@ -43,7 +44,6 @@ msgstr "nicht erkennbar"
|
||||
msgid "not patched yet"
|
||||
msgstr "noch nicht gepatcht"
|
||||
|
||||
|
||||
#. Status block
|
||||
msgid "Autoscroll"
|
||||
msgstr "Autoscroll"
|
||||
@@ -57,12 +57,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 +75,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,7 +94,6 @@ msgstr "Beenden"
|
||||
msgid "Press any key to continue..."
|
||||
msgstr "Beliebige Taste drücken …"
|
||||
|
||||
|
||||
#. Settings
|
||||
msgid "Electron and CEF applications"
|
||||
msgstr "Electron- und CEF-Anwendungen"
|
||||
@@ -112,14 +113,17 @@ msgstr "Programme, die sich beim Anmelden selbst starten"
|
||||
msgid "Spotify"
|
||||
msgstr "Spotify"
|
||||
|
||||
msgid "Turn off KDE's middle-click paste"
|
||||
msgstr "KDEs Mittelklick-Einfügen abschalten"
|
||||
|
||||
msgid "Apply to newly installed applications"
|
||||
msgstr "Auf neu installierte Anwendungen anwenden"
|
||||
|
||||
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 or Right: change, q: back"
|
||||
msgstr "Hoch/Runter: wählen, Leertaste oder Rechts: ändern, q: zurück"
|
||||
|
||||
msgid "Arguments separated by spaces, empty for none:"
|
||||
msgstr "Argumente mit Leerzeichen getrennt, leer für keine:"
|
||||
@@ -127,7 +131,6 @@ 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 …"
|
||||
@@ -136,27 +139,34 @@ msgid "No Chromium-based applications found."
|
||||
msgstr "Keine Chromium-basierten 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."
|
||||
msgstr ""
|
||||
"Nicht erkannte Anwendungen bleiben unangetastet, bis sie hier eingeschaltet "
|
||||
"werden."
|
||||
|
||||
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 "Autoscroll is off. This is what would be covered."
|
||||
msgstr "Autoscroll ist aus. Das hier wäre abgedeckt."
|
||||
|
||||
msgid "Up/Down: select, Space: turn on or off, q: back"
|
||||
msgstr "Hoch/Runter: wählen, Leertaste: ein- oder ausschalten, q: zurück"
|
||||
|
||||
#. 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 +183,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 +238,6 @@ msgstr "Ohne Befehl wird ein interaktives Menü angezeigt."
|
||||
msgid "Unknown command: %s"
|
||||
msgstr "Unbekannter Befehl: %s"
|
||||
|
||||
|
||||
#. Time
|
||||
msgid "never"
|
||||
msgstr "nie"
|
||||
@@ -225,11 +245,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"
|
||||
@@ -5,7 +5,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: middleclick-autoscroll 1.0.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/Felitendo/middleclick-autoscroll/issues\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/middleclick-autoscroll/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2026-08-16 00:00+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
@@ -43,7 +44,6 @@ msgstr ""
|
||||
msgid "not patched yet"
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Status block
|
||||
msgid "Autoscroll"
|
||||
msgstr ""
|
||||
@@ -57,12 +57,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 +75,6 @@ msgstr ""
|
||||
msgid "Applications pick this up the next time they are started."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Menu
|
||||
msgid "Turn autoscroll on or off"
|
||||
msgstr ""
|
||||
@@ -92,7 +94,6 @@ msgstr ""
|
||||
msgid "Press any key to continue..."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Settings
|
||||
msgid "Electron and CEF applications"
|
||||
msgstr ""
|
||||
@@ -112,13 +113,16 @@ msgstr ""
|
||||
msgid "Spotify"
|
||||
msgstr ""
|
||||
|
||||
msgid "Turn off KDE's middle-click paste"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply to newly installed applications"
|
||||
msgstr ""
|
||||
|
||||
msgid "Additional Chromium arguments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Up/Down select - Space or Right changes - q goes back"
|
||||
msgid "Up/Down: select, Space or Right: change, q: back"
|
||||
msgstr ""
|
||||
|
||||
msgid "Arguments separated by spaces, empty for none:"
|
||||
@@ -127,7 +131,6 @@ msgstr ""
|
||||
msgid "Could not save the setting."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Applications screen
|
||||
msgid "Looking at the installed applications..."
|
||||
msgstr ""
|
||||
@@ -138,24 +141,26 @@ msgstr ""
|
||||
msgid "Anything not identified is left alone 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 ""
|
||||
|
||||
|
||||
#. 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 +178,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 +228,6 @@ msgstr ""
|
||||
msgid "Unknown command: %s"
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Time
|
||||
msgid "never"
|
||||
msgstr ""
|
||||
@@ -225,11 +235,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 ""
|
||||
@@ -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: 133 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -5,7 +5,7 @@ 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,10 +23,10 @@ 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
|
||||
|
||||
+10
-5
@@ -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
|
||||
@@ -56,7 +56,7 @@ 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
|
||||
@@ -114,7 +114,7 @@ mca_apply() {
|
||||
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
|
||||
mca_steam_apply
|
||||
@@ -134,6 +134,11 @@ mca_apply() {
|
||||
|
||||
[[ $CFG_SPOTIFY == yes ]] && mca_spotify_apply
|
||||
|
||||
# Not about any one application: middle click goes on pasting everywhere
|
||||
# else on the desktop, and KDE is the one desktop that can be told not to.
|
||||
# See kde.sh.
|
||||
[[ $CFG_PASTE == yes ]] && mca_kde_paste_apply
|
||||
|
||||
mca_prune_orphans
|
||||
|
||||
if (( MCA_CHANGES )) && mca_have update-desktop-database; then
|
||||
@@ -186,8 +191,8 @@ 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
|
||||
|
||||
+41
-19
@@ -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
|
||||
@@ -210,8 +217,8 @@ 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.
|
||||
# has to read. A plain success needs no acknowledgement, because 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
|
||||
@@ -237,10 +244,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 +278,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 +288,12 @@ mca_state_write() {
|
||||
# shadow <generated desktop entry> <source entry it was generated from>
|
||||
# inplace <edited file> <basename of its backup copy>
|
||||
# flags <flag file> created | appended | merged
|
||||
# kwin <kwinrc> what EnablePrimarySelection said before
|
||||
#
|
||||
# `inplace` and `flags` differ in how they are undone: a backup is restored
|
||||
# wholesale, a flag file only loses the one line that was added to it.
|
||||
# The kinds differ in how they are undone: a backup is restored wholesale, a
|
||||
# flag file only loses the one line that was added to it, and kwinrc gets one
|
||||
# key written back. It is a file KDE writes to itself, and putting a whole
|
||||
# copy of it back would take everything else settled since with it.
|
||||
|
||||
mca_ledger_add() {
|
||||
local kind="$1" path="$2" detail="${3:-}"
|
||||
@@ -303,6 +316,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
|
||||
|
||||
@@ -144,6 +144,7 @@ mca_config_load() {
|
||||
CFG_STEAM=no; mca_config_bool PatchSteam yes && CFG_STEAM=yes
|
||||
CFG_SPOTIFY=no; mca_config_bool PatchSpotify yes && CFG_SPOTIFY=yes
|
||||
CFG_WATCH=no; mca_config_bool WatchNewApps yes && CFG_WATCH=yes
|
||||
CFG_PASTE=no; mca_config_bool DisablePaste yes && CFG_PASTE=yes
|
||||
|
||||
CFG_EXTRA_FLAGS="$(mca_config_get ExtraFlags '')"
|
||||
|
||||
|
||||
+429
-67
@@ -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,32 @@ _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.
|
||||
MCA_CACHE_FORMAT="# middleclick-autoscroll detect 2 $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 +372,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 +392,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 +404,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 +435,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 +477,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
|
||||
@@ -487,10 +549,10 @@ _mca_script_target() {
|
||||
|
||||
# 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 +565,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 +583,295 @@ 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
|
||||
#
|
||||
# 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 +885,45 @@ 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
|
||||
if _mca_detect_uncached "$real"; then
|
||||
MCA_VERDICT=yes
|
||||
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
|
||||
|
||||
_mca_detect_uncached() {
|
||||
local real="$1" depth="${2:-0}" dir target
|
||||
|
||||
@@ -585,20 +943,33 @@ _mca_detect_uncached() {
|
||||
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
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
@@ -634,7 +1005,7 @@ mca_scan() {
|
||||
MCA_PACKAGING=()
|
||||
|
||||
# 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 +1043,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,8 +1059,8 @@ 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
|
||||
@@ -717,10 +1088,12 @@ mca_scan() {
|
||||
# Steam is Chromium inside, but nothing about it can be changed
|
||||
# from a command line argument; it has its own module.
|
||||
kind=steam
|
||||
elif mca_is_chromium "$prog"; then
|
||||
(( c_browser[i] )) && kind=browser || kind=app
|
||||
elif [[ $prog == *.AppImage || $prog == *.appimage ]]; then
|
||||
kind=unknown
|
||||
else
|
||||
mca_detect_verdict "$prog"
|
||||
case "$MCA_VERDICT" in
|
||||
yes) (( c_browser[i] )) && kind=browser || kind=app ;;
|
||||
unknown) kind=unknown ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
[[ $kind == no ]] && continue
|
||||
@@ -770,8 +1143,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 +1186,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
|
||||
@@ -842,12 +1209,7 @@ mca_flatpak_is_chromium() {
|
||||
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 [[ -n $loc ]] && _mca_find_markers "$loc/files" 4; then
|
||||
MCA_DETECT_MEMO[flatpak:$id]=yes
|
||||
return 0
|
||||
fi
|
||||
|
||||
+167
@@ -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
|
||||
}
|
||||
+21
-8
@@ -32,7 +32,7 @@ mca_ui_term_restore() {
|
||||
}
|
||||
|
||||
# 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.
|
||||
# it prints or prompts for behaves the way a program expects.
|
||||
mca_ui_cooked() {
|
||||
mca_ui_term_restore
|
||||
"$@"
|
||||
@@ -126,8 +126,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,7 +172,7 @@ 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
|
||||
@@ -194,6 +194,18 @@ mca_ui_status() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Not an ON/OFF like the rows around it: what is being reported is the state
|
||||
# of the paste, not of a switch, and off is the state this program is after.
|
||||
if mca_kde_available; then
|
||||
if [[ "$(mca_kde_read)" == false ]]; then
|
||||
_mca_row "$(mca_msg "Middle-click paste")" \
|
||||
"${MCA_C_GREEN}$(mca_msg "off")${MCA_C_RESET}"
|
||||
else
|
||||
_mca_row "$(mca_msg "Middle-click paste")" \
|
||||
"${MCA_C_DIM}$(mca_msg "on")${MCA_C_RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
_mca_row "$(mca_msg "Last applied")" "$(mca_time_ago "$last")"
|
||||
|
||||
if [[ $CFG_ENABLED == yes ]]; then
|
||||
@@ -216,6 +228,7 @@ MCA_SETTINGS=(
|
||||
"PatchAutostart|bool|yes|Programs that start themselves at login"
|
||||
"PatchSteam|bool|yes|Steam"
|
||||
"PatchSpotify|bool|yes|Spotify"
|
||||
"DisablePaste|bool|yes|Turn off KDE's middle-click paste"
|
||||
"WatchNewApps|bool|yes|Apply to newly installed applications"
|
||||
"ExtraFlags|text||Additional Chromium arguments"
|
||||
)
|
||||
@@ -281,7 +294,7 @@ mca_ui_settings() {
|
||||
|
||||
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 or Right: change, q: back"
|
||||
hint="$MCA_MSG_RESULT"
|
||||
|
||||
local clearseq
|
||||
@@ -374,11 +387,11 @@ mca_ui_apps() {
|
||||
|
||||
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."
|
||||
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
|
||||
@@ -541,7 +554,7 @@ mca_ui_menu() {
|
||||
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.
|
||||
*) ;;
|
||||
|
||||
+26
-17
@@ -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]}"
|
||||
@@ -319,7 +324,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 +365,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
|
||||
|
||||
@@ -383,7 +388,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,8 +412,8 @@ 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.
|
||||
@@ -568,7 +573,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.
|
||||
@@ -646,6 +651,10 @@ mca_revert_all() {
|
||||
mca_steam_revert "$path" "$detail" \
|
||||
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
;;
|
||||
kwin)
|
||||
mca_kde_paste_revert "$path" "$detail" \
|
||||
&& MCA_CHANGES=$(( MCA_CHANGES + 1 ))
|
||||
;;
|
||||
esac
|
||||
done < <(tac "$MCA_LEDGER" 2>/dev/null)
|
||||
|
||||
|
||||
+11
-11
@@ -2,25 +2,25 @@
|
||||
#
|
||||
# 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.
|
||||
# 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
|
||||
@@ -114,8 +114,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
|
||||
@@ -180,8 +180,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
|
||||
@@ -277,7 +277,7 @@ mca_steam_revert() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# No backup to fall back on - take the flags back out of the exec line.
|
||||
# No backup to fall back on, so take the flags back out of the exec line.
|
||||
[[ -f $script ]] || return 1
|
||||
local tmp
|
||||
tmp="$(mktemp "${script}.XXXXXX")" || return 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# middleclick-autoscroll - middle-click autoscroll for Chromium-based
|
||||
# middleclick-autoscroll: middle-click autoscroll for Chromium-based
|
||||
# applications 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.
|
||||
@@ -17,7 +17,7 @@ set -uo pipefail
|
||||
|
||||
MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"
|
||||
|
||||
for _mod in common config detect patch steam apply menu; do
|
||||
for _mod in common config detect patch steam kde apply menu; do
|
||||
# shellcheck source=/dev/null
|
||||
if ! source "$MCA_LIBDIR/$_mod.sh"; then
|
||||
printf 'middleclick-autoscroll: cannot load %s/%s.sh\n' "$MCA_LIBDIR" "$_mod" >&2
|
||||
@@ -50,7 +50,7 @@ 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
|
||||
|
||||
|
||||
Reference in new issue
Block a user