From c33e4e16001b4b992c8de3ff34c8786db7d24c7b Mon Sep 17 00:00:00 2001 From: Rasmus Wejlgaard Date: Sun, 16 Aug 2026 20:48:55 +0100 Subject: [PATCH] Share the decisions behind the gnome/kde/xfce sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three desktop sets say the same seven things to three unrelated backends — gsettings, kwriteconfig, xfconf-query. There's no code worth sharing between them, which is why they were left as three separate scripts, but it also meant nothing at all connected them: adding a setting to one and forgetting the other two was completely invisible, and the keyboard repeat rate was hardcoded three times in two different unit systems. config/desktop-common.sh now holds both halves of what they actually share: - the values (KEY_REPEAT_RATE, KEY_REPEAT_DELAY_MS), so changing the repeat rate is one edit instead of three. GNOME wants the gap between repeats in milliseconds rather than a rate, so it inverts the shared value. - the list of settings all three are expected to cover, as DESKTOP_INTENTS. Each apply.sh marks where it handles one with a "# intent: " comment, and tests/check-desktop-intents.sh cross-references the two in CI. It fails if a desktop is missing an intent, and also if one marks an intent that isn't declared, which catches typos in the markers. Settings that only exist on one desktop (Dolphin's menu bar, XFCE's panel layout) aren't intents. The macos set is deliberately out of scope — not a desktop environment in the same sense, and the few settings that overlap don't convert cleanly. --- .github/workflows/pr-test.yml | 7 ++- README.md | 43 +++++++++++++++++ config/desktop-common.sh | 43 +++++++++++++++++ config/sets/gnome/apply.sh | 39 ++++++++++----- config/sets/kde/apply.sh | 40 ++++++++++------ config/sets/xfce/apply.sh | 33 +++++++++---- tests/check-desktop-intents.sh | 86 ++++++++++++++++++++++++++++++++++ 7 files changed, 253 insertions(+), 38 deletions(-) create mode 100644 config/desktop-common.sh create mode 100755 tests/check-desktop-intents.sh diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 2fd78da..b3106ba 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -14,7 +14,12 @@ jobs: - name: ShellCheck run: | - shellcheck -x install-scripts/*.sh install-scripts/lib/*.sh config/sets/*/apply.sh config/sets/basic/tmux/scripts/*.sh scripts/* + shellcheck -x install-scripts/*.sh install-scripts/lib/*.sh config/desktop-common.sh config/sets/*/apply.sh config/sets/basic/tmux/scripts/*.sh scripts/* tests/*.sh + + # The gnome/kde/xfce sets apply the same decisions through three + # unrelated backends, so nothing else would notice one falling behind. + - name: Desktop intent coverage + run: bash tests/check-desktop-intents.sh - name: Fish syntax check run: | diff --git a/README.md b/README.md index 59bc8b2..76d75cb 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,13 @@ config/ os # "Linux" — hidden from `make picky` elsewhere apply.sh # further sets live alongside these, following the same layout + desktop-common.sh # What gnome/kde/xfce all agree to apply (see below) install-scripts/ # Numbered setup scripts run by the Makefile lib/sets.sh # Shared helpers for discovering/enabling sets pick-sets.sh # the dialog checklist; takes set names to skip it scripts/ # Misc helper scripts (e.g. Gentoo kernel upgrade) tests/ # Dockerfiles used by CI to test installs per distro + check-desktop-intents.sh # Fails CI if a desktop set falls behind the others ``` ## Config Sets & `make picky` @@ -179,6 +181,45 @@ full-install` runs too, so you only need to pick once per machine. If you've never picked, everything defaults to just `basic`, matching this repo's historical behavior. +### Desktop intents + +`gnome`, `kde` and `xfce` apply the same handful of decisions — key repeat +rate, Caps Lock as Escape, no screen lock, no idle display blanking, no +automount, an editable path bar, a dark theme — through three completely +unrelated backends (`gsettings`, `kwriteconfig`, `xfconf-query`). There's no +code worth sharing between them, so what gets shared instead is the decision. + +`config/desktop-common.sh` holds both halves of that: + +- the **values** all three should end up applying (`KEY_REPEAT_RATE`, + `KEY_REPEAT_DELAY_MS`), so changing the repeat rate is one edit rather than + three in three different unit systems — GNOME wants the gap between + repeats in milliseconds, the other two want a rate +- the **list** of settings all three are expected to cover, as + `DESKTOP_INTENTS` + +Each `apply.sh` marks where it handles an intent with a `# intent: ` +comment above the relevant section, and `tests/check-desktop-intents.sh` +cross-references the two: + +``` +$ bash tests/check-desktop-intents.sh + intent gnome kde xfce + keyboard-repeat yes yes yes + caps-as-escape yes yes yes + ... +``` + +It runs in CI and fails if a desktop is missing an intent, or marks one that +isn't declared (which catches typos in the markers). So adding a setting to +one desktop and forgetting the other two now breaks the build instead of +going unnoticed. Settings that genuinely only exist on one desktop — Dolphin's +menu bar, XFCE's panel layout — aren't intents and need no marker. + +The `macos` set is deliberately outside this: it isn't a desktop environment +in the same sense, most of its settings have no Linux equivalent, and the few +that overlap use units that don't convert cleanly. + ### Adding a new set 1. Create `config/sets//`. @@ -403,6 +444,8 @@ When you submit a pull request, the following automated checks run: - **Build Verification**: The GitHub Action (`pr-test.yml`) verifies that the dotfiles can be successfully built on each supported platform, and that the install actually landed (symlinks in place, fish functions loaded) — not just that the install command exited 0 +- **Lint & Consistency**: `shellcheck` over every shell script in the repo, a `fish --no-execute` parse of every fish script, and `tests/check-desktop-intents.sh` to catch the `gnome`/`kde`/`xfce` sets drifting apart (see [Desktop intents](#desktop-intents)) + ### Workflow Details The PR testing workflow: diff --git a/config/desktop-common.sh b/config/desktop-common.sh new file mode 100644 index 0000000..bcd5902 --- /dev/null +++ b/config/desktop-common.sh @@ -0,0 +1,43 @@ +# shellcheck shell=bash +# Shared ground truth for the desktop sets — gnome, kde and xfce. +# +# Those three sets say the same things to three completely different +# backends (gsettings, kwriteconfig, xfconf-query), so there's no code worth +# sharing between them. What *is* worth sharing is the decision behind the +# code: which settings this repo has an opinion about, and what that opinion +# is. Without that written down once, adding a setting to one desktop and +# forgetting the other two is invisible. +# +# So: every setting all three desktops are expected to cover gets an entry in +# DESKTOP_INTENTS below, and each apply.sh marks where it handles it with a +# +# # intent: +# +# comment. tests/check-desktop-intents.sh cross-references the two and fails +# if a desktop is missing one, or claims an id that doesn't exist here. +# Settings that only make sense on one desktop (Dolphin's menu bar, XFCE's +# panel layout) aren't intents and need no annotation. +# +# The macos set is deliberately out of scope: it isn't a desktop environment +# in this sense, its settings mostly have no Linux equivalent, and the few +# that overlap use units that don't convert cleanly. +# +# Sourced by config/sets/{gnome,kde,xfce}/apply.sh. + +# Values every desktop should end up applying, in their canonical units. +# Each backend converts as needed — GNOME, for one, wants the repeat +# *interval* in milliseconds rather than a rate. +KEY_REPEAT_RATE=50 # repeats per second, once repeating starts +KEY_REPEAT_DELAY_MS=250 # milliseconds held before repeating starts + +# ":", one per setting all three desktops should cover. +# shellcheck disable=SC2034 # read by tests/check-desktop-intents.sh, which sources this +DESKTOP_INTENTS=( + "keyboard-repeat:Key repeat at ${KEY_REPEAT_RATE}/s after a ${KEY_REPEAT_DELAY_MS}ms delay" + "caps-as-escape:Caps Lock acts as Escape" + "no-screen-lock:Screen locking disabled entirely" + "no-idle-display:Display never dims or blanks when idle, on AC or battery" + "no-automount:Removable devices are not automounted" + "editable-path-bar:File dialogs offer an editable path entry, not breadcrumbs" + "dark-theme:A dark colour scheme" +) diff --git a/config/sets/gnome/apply.sh b/config/sets/gnome/apply.sh index 56bad2e..d25c63a 100755 --- a/config/sets/gnome/apply.sh +++ b/config/sets/gnome/apply.sh @@ -4,6 +4,14 @@ set -euo pipefail # GNOME settings that diverge from stock defaults on this machine, mirroring # config/sets/kde/apply.sh's choices for the GNOME desktop. # Each section below is independent and safe to re-run. +# +# The "# intent:" markers below tie each section to an entry in +# config/desktop-common.sh, which is the one place the kde/gnome/xfce sets +# agree on what they're all supposed to cover. + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +# shellcheck source=config/desktop-common.sh +source "$REPO/config/desktop-common.sh" if ! command -v gsettings >/dev/null 2>&1; then echo "Warning: gsettings not found (not a GNOME system?); skipping GNOME settings." >&2 @@ -21,20 +29,23 @@ gset() { fi } -# Keyboard repeat rate: 50 repeats/sec (20ms interval) with a 250ms initial -# delay (Settings > Keyboard). -gset org.gnome.desktop.peripherals.keyboard delay 250 -gset org.gnome.desktop.peripherals.keyboard repeat-interval 20 +# intent: keyboard-repeat +# GNOME takes the gap *between* repeats in milliseconds rather than a rate, +# so the shared rate is inverted here (Settings > Keyboard). +gset org.gnome.desktop.peripherals.keyboard delay "$KEY_REPEAT_DELAY_MS" +gset org.gnome.desktop.peripherals.keyboard repeat-interval "$(( 1000 / KEY_REPEAT_RATE ))" -# Remap Caps Lock to Escape (gnome-tweaks > Keyboard & Mouse > Additional -# Layout Options > Caps Lock Behavior). +# intent: caps-as-escape +# gnome-tweaks > Keyboard & Mouse > Additional Layout Options > Caps Lock +# Behavior. gset org.gnome.desktop.input-sources xkb-options "['caps:escape']" -# Disable screen locking entirely (Settings > Privacy & Security > Screen -# Lock). +# intent: no-screen-lock +# Settings > Privacy & Security > Screen Lock. gset org.gnome.desktop.screensaver lock-enabled false gset org.gnome.desktop.screensaver idle-activation-enabled false +# intent: no-idle-display # Never dim or blank the display when idle, on AC or battery (Settings > # Power). gset org.gnome.desktop.session idle-delay 0 @@ -42,15 +53,17 @@ gset org.gnome.settings-daemon.plugins.power idle-dim false gset org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type nothing gset org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type nothing -# Disable automounting of removable devices (Settings > Removable Media). +# intent: no-automount +# Settings > Removable Media. gset org.gnome.desktop.media-handling automount false gset org.gnome.desktop.media-handling automount-open false -# GTK file open/save dialogs: use an editable path bar instead of -# breadcrumbs, matching Dolphin's setting. +# intent: editable-path-bar +# GTK file open/save dialogs: an editable path bar instead of breadcrumbs, +# matching Dolphin's setting. gset org.gtk.Settings.FileChooser location-mode filename-entry gset org.gtk.gtk4.Settings.FileChooser location-mode filename-entry -# Prefer a dark color scheme, GNOME's equivalent of Breeze Dark (Settings > -# Appearance). +# intent: dark-theme +# GNOME's equivalent of Breeze Dark (Settings > Appearance). gset org.gnome.desktop.interface color-scheme prefer-dark diff --git a/config/sets/kde/apply.sh b/config/sets/kde/apply.sh index ffc4ea3..4fa910b 100755 --- a/config/sets/kde/apply.sh +++ b/config/sets/kde/apply.sh @@ -3,6 +3,14 @@ set -euo pipefail # KDE Plasma settings that diverge from stock defaults on this machine. # Each section below is independent and safe to re-run. +# +# The "# intent:" markers below tie each section to an entry in +# config/desktop-common.sh, which is the one place the kde/gnome/xfce sets +# agree on what they're all supposed to cover. + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +# shellcheck source=config/desktop-common.sh +source "$REPO/config/desktop-common.sh" kwriteconfig="" for candidate in kwriteconfig6 kwriteconfig5; do @@ -17,25 +25,27 @@ if [ -z "$kwriteconfig" ]; then exit 0 fi -# Keyboard repeat rate: 50 repeats/sec with a 250ms initial delay -# (System Settings > Keyboard > Advanced), via kcminputrc. -"$kwriteconfig" --file kcminputrc --group Keyboard --key RepeatDelay 250 -"$kwriteconfig" --file kcminputrc --group Keyboard --key RepeatRate 50 +# intent: keyboard-repeat +# System Settings > Keyboard > Advanced, via kcminputrc. +"$kwriteconfig" --file kcminputrc --group Keyboard --key RepeatDelay "$KEY_REPEAT_DELAY_MS" +"$kwriteconfig" --file kcminputrc --group Keyboard --key RepeatRate "$KEY_REPEAT_RATE" # Apply immediately in the current X11 session; a fresh Plasma login also # re-applies it from kcminputrc either way. if [ -n "${DISPLAY:-}" ] && command -v xset >/dev/null 2>&1; then - xset r rate 250 50 || true + xset r rate "$KEY_REPEAT_DELAY_MS" "$KEY_REPEAT_RATE" || true fi -# Remap Caps Lock to Escape (System Settings > Keyboard > Advanced > Caps -# Lock behavior). +# intent: caps-as-escape +# System Settings > Keyboard > Advanced > Caps Lock behavior. "$kwriteconfig" --file kxkbrc --group Layout --key Options "caps:escape" -# Disable screen locking entirely (System Settings > Screen Locking). +# intent: no-screen-lock +# System Settings > Screen Locking. "$kwriteconfig" --file kscreenlockerrc --group Daemon --key Autolock false "$kwriteconfig" --file kscreenlockerrc --group Daemon --key Timeout 0 +# intent: no-idle-display # Never dim or turn off the display when idle, on AC or battery # (System Settings > Power Management > Energy Saving). for profile in AC Battery; do @@ -43,17 +53,19 @@ for profile in AC Battery; do "$kwriteconfig" --file powerdevilrc --group "$profile" --group Display --key TurnOffDisplayWhenIdle false done -# Disable automounting of removable devices (System Settings > Removable -# Storage). +# intent: no-automount +# System Settings > Removable Storage. "$kwriteconfig" --file kded5rc --group Module-device_automounter --key autoload false -# Dolphin: hide the menu bar, and use an editable path bar instead of -# breadcrumbs in file open/save dialogs. +# Dolphin: hide the menu bar. KDE-only, so not a shared intent. "$kwriteconfig" --file dolphinrc --group MainWindow --key MenuBar Disabled + +# intent: editable-path-bar +# An editable path bar instead of breadcrumbs in file open/save dialogs. "$kwriteconfig" --file kdeglobals --group "KFileDialog Settings" --key "Breadcrumb Navigation" false -# Use the Breeze Dark global theme (System Settings > Appearance > Global -# Theme). +# intent: dark-theme +# The Breeze Dark global theme (System Settings > Appearance > Global Theme). if command -v plasma-apply-lookandfeel >/dev/null 2>&1; then plasma-apply-lookandfeel -a org.kde.breezedark.desktop || true else diff --git a/config/sets/xfce/apply.sh b/config/sets/xfce/apply.sh index 5d3097f..fa9d18a 100755 --- a/config/sets/xfce/apply.sh +++ b/config/sets/xfce/apply.sh @@ -5,6 +5,14 @@ set -euo pipefail # config/sets/kde/apply.sh and config/sets/gnome/apply.sh's choices for the # XFCE desktop. # Each section below is independent and safe to re-run. +# +# The "# intent:" markers below tie each section to an entry in +# config/desktop-common.sh, which is the one place the kde/gnome/xfce sets +# agree on what they're all supposed to cover. + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +# shellcheck source=config/desktop-common.sh +source "$REPO/config/desktop-common.sh" if ! command -v xfconf-query >/dev/null 2>&1; then echo "Warning: xfconf-query not found (not an XFCE system?); skipping XFCE settings." >&2 @@ -23,19 +31,20 @@ xset_prop() { # --- Keyboard --------------------------------------------------------- -# Keyboard repeat rate: 50 repeats/sec with a 250ms initial delay -# (Settings > Keyboard > Behaviour). +# intent: keyboard-repeat +# Settings > Keyboard > Behaviour. xset_prop keyboards /Default/KeyRepeat bool true -xset_prop keyboards /Default/KeyRepeat/Rate int 50 -xset_prop keyboards /Default/KeyRepeat/Delay int 250 +xset_prop keyboards /Default/KeyRepeat/Rate int "$KEY_REPEAT_RATE" +xset_prop keyboards /Default/KeyRepeat/Delay int "$KEY_REPEAT_DELAY_MS" # Apply immediately in the current X11 session; a fresh login also # re-applies it from the "keyboards" channel either way. if [ -n "${DISPLAY:-}" ] && command -v xset >/dev/null 2>&1; then - xset r rate 250 50 || true + xset r rate "$KEY_REPEAT_DELAY_MS" "$KEY_REPEAT_RATE" || true fi -# Remap Caps Lock to Escape. Unlike the layout/group/compose-key options, +# intent: caps-as-escape +# Unlike the layout/group/compose-key options, # xfsettingsd has no xfconf-backed setting for this XKB option group, so it # can't be stored declaratively the way the kde/gnome sets do it — it's # applied directly via setxkbmap instead, both now and on every future @@ -57,7 +66,8 @@ fi # --- Screen locking & power -------------------------------------------- -# Disable screen locking entirely (Settings > Screensaver). xfce4-screensaver +# intent: no-screen-lock +# Settings > Screensaver. xfce4-screensaver # is a separate, optional package (older XFCE releases use light-locker # instead, which has no xfconf-backed settings of its own). if command -v xfce4-screensaver >/dev/null 2>&1; then @@ -67,6 +77,7 @@ else echo "Warning: xfce4-screensaver not found; skipping screen lock settings." >&2 fi +# intent: no-idle-display # Never dim or turn off the display when idle, on AC or battery, and don't # lock on suspend/hibernate (Settings > Power Manager). if command -v xfce4-power-manager >/dev/null 2>&1; then @@ -80,9 +91,9 @@ fi # --- Device automount ---------------------------------------------------- -# Disable automounting of removable devices (Settings > Removable Drives -# and Media). thunar-volman is a separate, optional package from Thunar -# itself. +# intent: no-automount +# Settings > Removable Drives and Media. thunar-volman is a separate, +# optional package from Thunar itself. if command -v thunar-volman >/dev/null 2>&1; then xset_prop thunar-volman /autobrowse/enabled bool false xset_prop thunar-volman /automount-drives/enabled bool false @@ -94,6 +105,7 @@ fi # --- File manager / dialogs ---------------------------------------------- +# intent: editable-path-bar # Thunar: use an editable path bar instead of breadcrumbs. if command -v thunar >/dev/null 2>&1; then xset_prop thunar /last-location-bar string ThunarLocationEntry @@ -118,6 +130,7 @@ fi # --- Appearance ------------------------------------------------------------ +# intent: dark-theme # Prefer a dark GTK + window manager theme, whichever of these common dark # themes is actually installed (Settings > Appearance / Window Manager). pick_dark_theme() { diff --git a/tests/check-desktop-intents.sh b/tests/check-desktop-intents.sh new file mode 100755 index 0000000..1763066 --- /dev/null +++ b/tests/check-desktop-intents.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -euo pipefail + +# Checks that every desktop set accounts for every intent declared in +# config/desktop-common.sh. +# +# The gnome, kde and xfce sets apply the same decisions through three +# unrelated backends, so nothing stops one of them quietly falling behind the +# others. This is what stops it: add an intent to DESKTOP_INTENTS and this +# fails until all three sets mark where they handle it with a +# +# # intent: +# +# comment. It also fails on an intent id that isn't declared, which catches +# typos in those markers. +# +# Run by the lint job in .github/workflows/pr-test.yml, and by hand any time: +# bash tests/check-desktop-intents.sh + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=config/desktop-common.sh +source "$REPO/config/desktop-common.sh" + +DESKTOP_SETS=(gnome kde xfce) + +# Intent ids, in declared order. +intent_ids=() +for entry in "${DESKTOP_INTENTS[@]}"; do + intent_ids+=("${entry%%:*}") +done + +# Every "# intent: " marker found in a set's apply.sh, one per line. +markers_in() { + grep -hoE '^# intent: [a-z0-9-]+' "$1" | sed 's/^# intent: //' || true +} + +failures=() + +# --- Coverage: every set implements every declared intent ------------------ + +printf 'Desktop intent coverage (config/desktop-common.sh):\n\n' +printf ' %-20s' 'intent' +printf '%-9s' "${DESKTOP_SETS[@]}" +printf '\n' + +for id in "${intent_ids[@]}"; do + printf ' %-20s' "$id" + for set in "${DESKTOP_SETS[@]}"; do + apply="$REPO/config/sets/$set/apply.sh" + if [ ! -f "$apply" ]; then + printf '%-9s' 'NO SET' + failures+=("config/sets/$set/apply.sh does not exist") + elif markers_in "$apply" | grep -qxF "$id"; then + printf '%-9s' 'yes' + else + printf '%-9s' 'MISSING' + failures+=("$set does not handle intent '$id' (add a '# intent: $id' marker to config/sets/$set/apply.sh, or drop the intent)") + fi + done + printf '\n' +done +printf '\n' + +# --- The other direction: no set claims an intent that isn't declared ------ + +for set in "${DESKTOP_SETS[@]}"; do + apply="$REPO/config/sets/$set/apply.sh" + [ -f "$apply" ] || continue + while IFS= read -r marker; do + [ -z "$marker" ] && continue + printf '%s\n' "${intent_ids[@]}" | grep -qxF "$marker" && continue + failures+=("$set marks intent '$marker', which isn't declared in config/desktop-common.sh (typo?)") + done < <(markers_in "$apply") +done + +# --- Verdict --------------------------------------------------------------- + +if [ "${#failures[@]}" -gt 0 ]; then + echo "Desktop intents are out of sync:" >&2 + for failure in "${failures[@]}"; do + echo " - $failure" >&2 + done + exit 1 +fi + +echo "All ${#intent_ids[@]} intents covered by all ${#DESKTOP_SETS[@]} desktop sets."