Add configuration for GNOME

This commit is contained in:
Alexander Hess 2026-08-03 14:30:55 +02:00
commit 7804080953
Signed by: alexander
GPG key ID: B46EDB4DFC26805D
47 changed files with 6899 additions and 0 deletions

1121
.local/bin/color-palette Executable file

File diff suppressed because it is too large Load diff

52
.local/bin/dark-scheme-toggle Executable file
View file

@ -0,0 +1,52 @@
#!/bin/sh
# Triggered by <Super><Alt>d
# as defined in [org/gnome/settings-daemon/plugins/media-keys]
# in ~/.config/gnome-settings/key-bindings.ini
set -eu
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
Forge=org.gnome.shell.extensions.forge
Interface=org.gnome.desktop.interface
if [ "$(gsettings get $Interface color-scheme)" = "'prefer-dark'" ]; then
new_theme="light"
else
new_theme="dark"
fi
# Render and apply the palette for the target scheme before flipping,
# so that the visible switch happens in one step
"$HOME/.local/bin/color-palette" --write --scheme "$new_theme"
dconf load / < "$XDG_CONFIG_HOME/gnome-settings/local/palette.ini"
# Set the "color-scheme" explicitly here to be on the safe side
# (although it is also contained in the palette.ini file)
#
# See comments for "color-scheme" under [org/gnome/desktop/interface]
# in ~/.config/gnome-settings/misc.ini
gsettings set $Interface color-scheme "prefer-$new_theme"
# `forge` only re-reads its stylesheet on a restart;
# however, it takes about one second to do so
# gnome-extensions disable forge@jmmaranan.com
# gnome-extensions enable forge@jmmaranan.com
# As a hack, we make `forge` re-read its stylesheet by poking a watched setting
if gnome-extensions list --enabled 2>/dev/null | grep -qxF "forge@jmmaranan.com"; then
_gap=$(gsettings get $Forge window-gap-size | awk '{print $2}')
gsettings set $Forge window-gap-size "uint32 $((_gap + 1))"
gsettings set $Forge window-gap-size "uint32 $_gap"
fi
echo "Switched the color theme to $new_theme"

84
.local/bin/dconf-load Executable file
View file

@ -0,0 +1,84 @@
#!/bin/sh
# Load the ~/.config/gnome-settings[/local]/*.ini files into `dconf`,
# or just the single <file> given as an argument
#
# Unlike ~/.local/bin/gnome-restore this neither backs up nor resets
# anything, so keys removed from the *.ini files stay active
# => Use this for a quick iteration on the *.ini files,
# and `gnome-restore` for a deterministic state
set -eu
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
GNOME_CFG_DIR="$XDG_CONFIG_HOME/gnome-settings"
_usage() {
echo "Usage: $(basename "$0") [<file>]" >&2
exit 1
}
_load() {
# The *.ini files are machine independent and use a placeholder /home/user
echo " $(basename "$1")"
sed "s|/home/user|$HOME|g" "$1" | dconf load /
}
if ! command -v dconf >/dev/null 2>&1; then
echo "dconf is not installed" >&2
exit 1
fi
[ "$#" -le 1 ] || _usage
# A single file, e.g., while iterating on it
if [ "$#" -eq 1 ]; then
if [ ! -f "$1" ]; then
echo "No such file: $1" >&2
exit 1
fi
echo "Loading a single GNOME settings file:"
_load "$1"
exit 0
fi
# Load all *.ini files in ~/.config/gnome-settings[/local]/
if [ ! -d "$GNOME_CFG_DIR" ]; then
echo "No such directory: $GNOME_CFG_DIR" >&2
exit 1
fi
echo "Loading GNOME settings from: $GNOME_CFG_DIR"
for file in "$GNOME_CFG_DIR"/*.ini; do
[ -e "$file" ] || continue # because the glob does not expand if empty
_load "$file"
done
for file in "$GNOME_CFG_DIR"/local/*.ini; do
[ -e "$file" ] || continue # because the glob does not expand if empty
_load "$file"
done

43
.local/bin/dconf-verify Executable file
View file

@ -0,0 +1,43 @@
#!/bin/sh
# List GNOME `dconf` keys that are live but not described by ~/.config/gnome-settings/*.ini
set -eu
# Keep in sync with the `RESET_PATHS` in ~/.local/bin/gnome-restore
# (Note: The `$FILTER` below is a bit more generic than the `RESET_PATHS`)
FILTER='^(org/gnome/|org/gtk/|org/freedesktop/(ibus|tracker)/|ca/desrt/|system/)'
GNOME_CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gnome-settings"
flat() {
awk '/^\[/ { s = substr($0, 2, length($0) - 2); next }
/^[A-Za-z0-9][A-Za-z0-9_-]*=/ { i = index($0, "="); print s "/" substr($0, 1, i - 1) }'
}
live=$(mktemp)
managed=$(mktemp)
trap 'rm -f "$live" "$managed"' EXIT INT TERM
dconf dump / | flat | grep -E "$FILTER" | sort -u > "$live"
find "$GNOME_CFG_DIR" -maxdepth 2 -type f -name '*.ini' -exec cat {} + \
| sed "s|/home/user|$HOME|g" \
| flat | grep -E "$FILTER" | sort -u > "$managed"
echo ""
echo "== Live keys NOT in the ~/.config/gnome-settings/*.ini files =="
comm -23 "$live" "$managed"
echo ""
echo ""
echo "== Keys in the ~/.config/gnome-settings/*.ini files but NOT live =="
comm -13 "$live" "$managed"
echo ""

View file

@ -151,6 +151,31 @@ sh "$XDG_BIN_HOME/generate-shell-completions" --quieter \
|| echo "WARN: generating shell completions failed" >&2
if command -v python3 >/dev/null 2>&1; then
python3 -m \
pip install \
--user \
--upgrade \
--break-system-packages \
coloraide \
rofimoji \
|| echo "WARN: pip install failed" >&2
python3 "$XDG_BIN_HOME/color-palette" --write \
|| echo "WARN: color-palette failed" >&2
fi
if systemctl --user daemon-reload 2>/dev/null; then
systemctl --user enable gnome-restore.service
systemctl --user enable night-light-reset.timer
else
echo "WARN: no systemd user session, units not enabled" >&2
fi
# Keep only the most recent backups
_keep=10
if [ -d "$BACKUPS_DIR" ]; then

View file

@ -0,0 +1,30 @@
#!/bin/sh
# Raise Evolution and switch to its workspace,
# or switch to the target workspace first and launch it there
#
# Switching before the launch avoids the flicker that
# `auto-move-windows` causes by relocating an already mapped window
#
# Note that `auto-move-windows` must not also manage Evolution,
# or both mechanisms fight over the placement
set -eu
_CLASS=evolution
_COMMAND=/usr/bin/evolution
# This is the last of the 10 named but 0-based workspaces
# in "num-workspaces" under [org/gnome/desktop/wm/preferences]
_WORKSPACE=9
# "wmctrl -x -a" already switches to the window's workspace
wmctrl -x -a "$_CLASS" && exit 0
wmctrl -s "$_WORKSPACE"
exec "$_COMMAND"

152
.local/bin/gnome-restore Executable file
View file

@ -0,0 +1,152 @@
#!/bin/sh
# Restore the GNOME settings from ~/.config/gnome-settings/*.ini
#
# `dconf load` merges into the existing state, implying
# that keys removed from the *.ini files would still be active
# => Reset the subtrees we fully manage first,
# which makes the resulting state deterministic
set -eu
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
BACKUP_DIR="$XDG_STATE_HOME/gnome-settings-backups"
GNOME_CFG_DIR="$XDG_CONFIG_HOME/gnome-settings"
# Subtrees either
# - fully described by the *.ini files
# - or reset to their defaults
#
# Notes:
# - Keep in sync with the `grep`s in ~/.local/bin/dconf-verify
# - "/org/freedesktop/tracker/" is exempted as we think resetting it
# makes `localsearch3` re-index everything from scratch
RESET_PATHS="
/ca/desrt/dconf-editor/
/desktop/ibus/
/org/gnome/baobab/
/org/gnome/calculator/
/org/gnome/calendar/
/org/gnome/Characters/
/org/gnome/clocks/
/org/gnome/Connections/
/org/gnome/Contacts/
/org/gnome/control-center/
/org/gnome/desktop/
/org/gnome/Disks/
/org/gnome/evince/
/org/gnome/Extensions/
/org/gnome/file-roller/
/org/gnome/gedit/
/org/gnome/gnome-system-monitor/
/org/gnome/Loupe/
/org/gnome/maps/
/org/gnome/mutter/
/org/gnome/nautilus/
/org/gnome/nm-applet/
/org/gnome/portal/
/org/gnome/settings-daemon/
/org/gnome/shell/
/org/gnome/simple-scan/
/org/gnome/software/
/org/gnome/system/location/
/org/gnome/terminal/
/org/gnome/TextEditor/
/org/gnome/Totem/
/org/gnome/tweaks/
/org/gnome/Weather/
/org/gnome/GWeather4/
/org/gtk/
/system/
"
if ! command -v dconf >/dev/null 2>&1; then
echo "dconf is not installed => Nothing to do" >&2
exit 0
fi
if [ ! -d "$GNOME_CFG_DIR" ]; then
echo "No settings found at: $GNOME_CFG_DIR" >&2
exit 1
fi
echo "Backing up current GNOME settings to: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
dconf dump / > "$BACKUP_DIR/$(date +%Y%m%d-%H%M%S).ini"
# Keep the ten most recent backups
# shellcheck disable=SC2012 # File names are timestamps => `ls` is safe
ls -1t "$BACKUP_DIR"/*.ini | tail -n +11 | xargs -r rm --
# Remove temporary background image files
# created by ~/.local/bin/randomize-background
if [ "${FRESH_SESSION:-}" = "1" ]; then
echo "Removing session-scoped background image files"
rm -f "$XDG_CONFIG_HOME/gnome-settings/local/temporary_background.ini"
find "$XDG_DATA_HOME/backgrounds" -name '*-tmp-*.jpg' -delete 2>/dev/null || true
fi
echo "Resetting managed subtrees in dconf"
for path in $RESET_PATHS; do
echo " $path"
dconf reset -f "$path"
done
# forge's `patchCss()` writes its stylesheet backup to a path built from a
# property that no longer exists, yielding "~/undefined.bak"
# => Patch the path in the installed extension
THEME_JS="$XDG_DATA_HOME/gnome-shell/extensions/forge@jmmaranan.com/lib/shared/theme.js"
if [ -w "$THEME_JS" ]; then
sed -i 's|this\.configMgr\.stylesheetFileName + "\.bak"|configCss.get_path() + ".bak"|' "$THEME_JS"
fi
echo "Import GNOME extensions' schemas"
SCHEMAS="$HOME/.local/bin/gnome-shell-extensions-schemas-import"
if [ -x "$SCHEMAS" ]; then
"$SCHEMAS" || echo "WARN: importing GNOME extension schemas failed" >&2
fi
LOAD="$HOME/.local/bin/dconf-load"
if [ -x "$LOAD" ]; then
"$LOAD"
else
echo "ERROR: $LOAD is missing" >&2
exit 1
fi
echo "Patching forge"
# Draw a whitish border on unfocused windows as well
PATCH="$HOME/.local/bin/gnome-shell-extension-forge-patch"
if [ -x "$PATCH" ]; then
"$PATCH" || echo "WARN: forge border patch failed" >&2
fi
echo "Restored GNOME to the defaults defined in ~/.config/gnome-settings"

View file

@ -0,0 +1,49 @@
#!/bin/sh
# Install `forge` from source into the same place that
# https://extensions.gnome.org/extension/4481/forge/ would use
#
# The released version may break "window-toggle-float"
# (see https://github.com/forge-ext/forge/issues/492)
# and is not maintained at the moment
#
# A re-login is required afterwards,
# because `gnome-shell` only scans the extensions directory on startup
set -eu
_UUID=forge@jmmaranan.com
_REPO=https://github.com/forge-ext/forge.git
_TARGET="${XDG_DATA_HOME:-$HOME/.local/share}/gnome-shell/extensions/$_UUID"
for _tool in git make glib-compile-schemas msgfmt xgettext; do
command -v "$_tool" >/dev/null || {
echo "ERROR: $_tool is missing (try: apt install git make libglib2.0-bin gettext)" >&2
exit 1
}
done
_workdir=$(mktemp -d)
trap 'rm -rf "$_workdir"' EXIT
git clone --depth 1 "$_REPO" "$_workdir"
_commit=$(git -C "$_workdir" rev-parse --short HEAD)
make -C "$_workdir" build
rm -rf "$_TARGET"
make -C "$_workdir" install
echo "Installed $_UUID from $_commit into $_TARGET"
echo "Log out and back in, then: gnome-extensions enable $_UUID"

View file

@ -0,0 +1,73 @@
#!/bin/sh
# Patch `forge` to draw a border on all tiled and unfocused windows
#
# Idempotent: Re-running on a patched file changes nothing
set -eu
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
WINDOW_JS="$XDG_DATA_HOME/gnome-shell/extensions/forge@jmmaranan.com/lib/extension/window.js"
[ -w "$WINDOW_JS" ] || { echo "Not writable or missing: $WINDOW_JS" >&2; exit 1; }
grep -q "showUnfocusedBorders" "$WINDOW_JS" && { echo "Already patched"; exit 0; }
# Call our method after the focused border is drawn
sed -i '/^ updateBorderLayout() {$/,/^ }$/ s|this\.showWindowBorders();|this.showWindowBorders();\n this.showUnfocusedBorders();|' "$WINDOW_JS"
# Append the method right after `updateBorderLayout()`
awk '
/^ updateBorderLayout\(\) \{$/ { inFn=1 }
inFn && /^ \}$/ {
print
print ""
print " showUnfocusedBorders() {"
print " if (!this.ext.settings.get_boolean(\"focus-border-toggle\")) return;"
print " this.tree.nodeWindows.forEach((nodeWindow) => {"
print " let metaWindow = nodeWindow.nodeValue;"
print " if (!metaWindow || metaWindow === this.focusMetaWindow) return;"
print " if (metaWindow.minimized || metaWindow.is_fullscreen()) return;"
print " if (metaWindow.get_workspace() !== global.workspace_manager.get_active_workspace()) return;"
print " let maximized = false;"
print " try { maximized = metaWindow.is_maximized(); }"
print " catch (e) { maximized = metaWindow.get_maximized() === 3; }"
print " if (maximized) return;"
print " if (nodeWindow.parentNode.isTabbed() || nodeWindow.parentNode.isStacked()) return;"
print " let windowActor = metaWindow.get_compositor_private();"
print " if (!windowActor || !windowActor.border) return;"
print " let border = windowActor.border;"
print " border.set_style_class_name(\"window-unfocused-border\");"
print " let rect = metaWindow.get_frame_rect();"
print " let inset = 1;"
print " border.set_size(rect.width + inset * 2, rect.height + inset * 2);"
print " border.set_position(rect.x - inset, rect.y - inset);"
print " border.show();"
print " if (global.window_group && global.window_group.contains(border)) {"
print " global.window_group.remove_child(border);"
print " global.window_group.insert_child_above(border, windowActor);"
print " }"
print " });"
print " }"
inFn=0
next
}
{ print }
' "$WINDOW_JS" > "$WINDOW_JS.tmp" && mv "$WINDOW_JS.tmp" "$WINDOW_JS"
grep -q 'this\.showUnfocusedBorders();' "$WINDOW_JS" \
&& grep -q 'showUnfocusedBorders() {' "$WINDOW_JS" \
|| {
echo "ERROR: patch anchors did not match (upstream changed?): $WINDOW_JS" >&2
exit 1
}
echo "Patched: $WINDOW_JS"

View file

@ -0,0 +1,34 @@
#!/bin/sh
# Make the `GSettings` schemas of all user-installed GNOME Shell extensions
# visible to a plain `gsettings`, by symlinking the *.gschema.xml files
# into the user schema directory and compiling them
#
# Idempotent: Re-running refreshes the links, prunes dangling ones
# (e.g., from removed extensions), and recompiles
set -eu
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
EXTENSIONS_DIR="$XDG_DATA_HOME/gnome-shell/extensions"
SCHEMAS_DIR="$XDG_DATA_HOME/glib-2.0/schemas"
mkdir -p "$SCHEMAS_DIR"
# Prune symlinks whose target no longer exists
find "$SCHEMAS_DIR" -maxdepth 1 -type l ! -exec test -e {} \; -exec rm -v -- {} \;
# Link every schema shipped by an extension
for xml in "$EXTENSIONS_DIR"/*/schemas/*.gschema.xml; do
[ -e "$xml" ] || continue # because the glob does not expand if empty
echo " $(basename "$xml")"
ln -sf -- "$xml" "$SCHEMAS_DIR/"
done
glib-compile-schemas "$SCHEMAS_DIR"

75
.local/bin/gnome-shell-restart Executable file
View file

@ -0,0 +1,75 @@
#!/bin/sh
# Restart `gnome-shell` in place,
# preserving the session and all windows
#
# Usually survives, but treat it like a logout:
# save your work first, because a crash on the way down can end the session
#
# Debian normally forbids starting/stopping this unit by hand;
# we allow it via a user-level drop-in (installed on first run)
set -eu
if [ "${XDG_SESSION_TYPE:-}" != "x11" ]; then
echo "gnome-shell-restart: only works on X11, not on ${XDG_SESSION_TYPE:-unknown}" >&2
exit 1
fi
_UNIT="org.gnome.Shell@x11.service"
_DROPIN_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$_UNIT.d"
_DROPIN_FILE="$_DROPIN_DIR/allow-restart.conf"
if [ ! -f "$_DROPIN_FILE" ] || ! grep -q "SuccessExitStatus" "$_DROPIN_FILE"; then
mkdir -p "$_DROPIN_DIR"
cat > "$_DROPIN_FILE" << 'EOF'
# Written by ~/.local/bin/gnome-shell-restart
# Debian forbids starting and stopping this unit by hand
# to protect Wayland sessions, where the shell cannot restart in place
# => We are on X11
[Unit]
RefuseManualStart=no
RefuseManualStop=no
# The old shell sometimes crashes while shutting down.
#
# Without this line, `systemd` treats that crash as a real failure
# and ends the whole session (i.e., logs us out)
#
# => We count a segfault as a normal exit
#
# If the shell truly keeps crashing, the start limit still ends the session
[Service]
SuccessExitStatus=1 SEGV
EOF
systemctl --user daemon-reload
fi
# The unit allows at most 3 starts per 15 seconds
# and one restart too many counts as a failure and logs us out
# => Refuse to restart again too soon after the last one
_STARTED="$(systemctl --user show -p ActiveEnterTimestamp --value "$_UNIT")"
if [ -n "$_STARTED" ]; then
_AGE=$(( $(date +%s) - $(date -d "$_STARTED" +%s) ))
if [ "$_AGE" -lt 20 ]; then
echo "gnome-shell-restart: last shell start was only ${_AGE}s ago" >&2
echo "gnome-shell-restart: refusing to trip the start limit (wait $(( 20 - _AGE ))s)" >&2
exit 1
fi
fi
# Forget any earlier failure, so it does not block this restart
systemctl --user reset-failed "$_UNIT" 2> /dev/null || true
exec systemctl --user restart "$_UNIT"

78
.local/bin/key-bindings Executable file
View file

@ -0,0 +1,78 @@
#!/bin/sh
# Show the key bindings from ~/.config/gnome-settings/key-bindings.ini,
# one binding per line, flattened from the comment tables in that file
#
# The key column comes first and the search input is prefilled with "^",
# so that a typed pattern only matches the key column
set -eu
INI="${XDG_CONFIG_HOME:-$HOME/.config}/gnome-settings/key-bindings.ini"
awk -F '|' '
/^# .*\|/ {
sub(/^# ?/, "")
for (i = 1; i <= NF; i++) gsub(/^[ \t]+|[ \t]+$/, "", $i)
key = $1
if (key == "Key(s)") { # Header of the <Super> table
mode = "layers"
for (i = 2; i <= NF; i++) mod[i] = $i
next
}
if (key == "Key") { # Header of the browser table
mode = "browser"
next
}
if (mode == "" || key ~ /^[-+]+$/) next
# Every line starts with a layer rank used by `sort` below:
# 1 <Super>, 2 <Super><Shift>, 3 <Super><Control>,
# 4 <Super><Alt>, 5 <Super><Alt><Shift>, 6 browser bindings
if (mode == "browser") {
if (key == "") next
k = split(key, parts, ">")
line[++n] = sprintf("6 %-17s %-20s %s: %s", parts[k],
substr(key, 1, length(key) - length(parts[k])), $2, $3)
next
}
if (key == "") { # Row without a key => prefix or continuation
for (i = 2; i <= NF; i++) {
cell = $i
if (cell == "") continue
if (cell ~ /^\.\.\./) { # Continues the row above
sub(/^\.\.\. */, "", cell)
sub(/ \.\.\.$/, "", line[last[i]])
line[last[i]] = line[last[i]] " " cell
} else { # Prefix for the rows below
sub(/ \.\.\.$/, "", cell)
prefix[i] = cell
}
}
next
}
for (i = 2; i <= NF; i++) {
cell = $i
if (cell == "") continue
if (cell ~ /^\.\.\./) {
sub(/^\.\.\. */, "", cell)
cell = prefix[i] " " cell
}
line[++n] = sprintf("%d %-17s %-20s %s", i - 1, key, mod[i], cell)
last[i] = n
}
}
END { for (i = 1; i <= n; i++) print line[i] }
' "$INI" \
| LC_ALL=C sort -s -f -k2,2 -k1,1n \
| cut -d ' ' -f 2- \
| rofi -dmenu -i -p keys -no-custom -matching regex -filter '^' -no-sort \
-theme-str '* { font: "FiraCode Nerd Font 10"; } window { width: 60em; } listview { lines: 20; }' \
>/dev/null

View file

@ -0,0 +1,49 @@
#!/bin/sh
# Move the active window to the first empty workspace and follow it
#
# Note: This assumes that only the main monitor has workspaces enabled
set -eu
active=$(xprop -root _NET_ACTIVE_WINDOW | awk '{print $NF}')
desktop=$(xprop -id "$active" _NET_WM_DESKTOP 2>/dev/null | awk '{print $NF}')
# 4294967295 (i.e., 0xFFFFFFFF) means "on all workspaces",
# which is what `mutter` reports for windows on secondary monitors
# when "workspaces-only-on-primary" is true
if [ "$desktop" = "4294967295" ] || [ -z "$desktop" ]; then
command -v notify-send >/dev/null \
&& notify-send 'Window Management' 'Active window is not on a workspace on the main screen'
exit 1
fi
occupied_workspaces=$(wmctrl -l) || exit 1 # fail safe
occupied_workspaces=$(echo "$occupied_workspaces" | awk '{print $2}' | sort -u)
total=$(wmctrl -d | wc -l)
# 0-based index for the first workspace
i=0
while [ "$i" -lt "$total" ]; do
if ! echo "$occupied_workspaces" | grep -qx "$i"; then
wmctrl -r :ACTIVE: -t "$i"
wmctrl -s "$i"
exit 0
fi
i=$((i + 1))
done
command -v notify-send >/dev/null \
&& notify-send 'Window Management' 'No empty workspace available'
exit 1

162
.local/bin/nextcloud-restore Executable file
View file

@ -0,0 +1,162 @@
#!/bin/sh
# This script ensures my `nextcloud` clients operate in a uniform way
set -eu
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
NEXTCLOUD_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/Nextcloud/nextcloud.cfg"
SERVER_URL_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/Nextcloud/server.txt"
if [ -r "$SERVER_URL_FILE" ]; then
# `tr` strips spaces, tabs, and CR/LF
SERVER_URL=$(head -n 1 "$SERVER_URL_FILE" | tr -d '[:space:]')
else
SERVER_URL=""
fi
[ "${1:-}" = "--force" ] && force=1 || force=0
if [ -z "$SERVER_URL" ]; then
if [ "$force" -eq 1 ]; then
echo "" >&2
echo "WARN: No usable server URL at $SERVER_URL_FILE, leaving the server URL unmanaged" >&2
echo "" >&2
else
echo "" >&2
echo "No usable server URL found at $SERVER_URL_FILE" >&2
echo "" >&2
echo "Create it with a single line, e.g., 'https://cloud.example.com'," >&2
echo "or re-run with --force to skip the URL settings" >&2
echo "" >&2
exit 1
fi
fi
if ! command -v crudini >/dev/null 2>&1; then
echo "crudini is not installed => cannot configure nextcloud" >&2
exit 1
fi
if ! command -v nextcloud >/dev/null 2>&1; then
echo "Nextcloud is not installed" >&2
exit 1
fi
del_key() { # section key
crudini --del "$NEXTCLOUD_CFG" "$1" "$2"
}
set_key() { # section key value
crudini --set "$NEXTCLOUD_CFG" "$1" "$2" "$3"
}
# Stop `nextcloud`
nextcloud --quit 2>/dev/null || true
for _ in $(seq 30); do
pgrep -x nextcloud >/dev/null || break
sleep 0.5
done
if pgrep -x nextcloud >/dev/null; then
echo "Nextcloud did not exit; aborting" >&2
exit 1
fi
mkdir -p "$(dirname "$NEXTCLOUD_CFG")"
touch "$NEXTCLOUD_CFG"
# Configure base settings for `nextcloud`
if [ -n "$SERVER_URL" ]; then
set_key General forceOverrideServerUrl true
set_key General overrideServerUrl "$SERVER_URL"
fi
set_key General overrideLocalDir "$HOME/Cloud"
set_key General launchOnSystemStartup true
set_key General monoIcons true
set_key General crashReporter false
# Windows-only, kept for completeness
set_key General showInExplorerNavigationPane true
set_key General optionalServerNotifications false
set_key General showCallNotifications false
set_key General showChatNotifications false
set_key General isVfsEnabled false
set_key General confirmExternalStorage true
set_key General useNewBigFolderSizeLimit false
set_key General newBigFolderSizeLimit 999
set_key General notifyExistingFoldersOverLimit false
set_key General stopSyncingExistingFoldersOverLimit false
set_key General moveToTrash true
set_key General promptDeleteAllFiles true
set_key Proxy type 0
# Make `nextcloud` sync at full speed, with sane fallback limits
# Modes:
# - 0 => unlimited
# - -1 => automatic limit
# - +1 => manually set limit
# Limits:
# - values are in KBytes/s
# - we assume line speeds of 100/30 Mbit/s, to be capped at 80%
set_key BWLimit useDownloadLimit 0
# 100 Mbit/s / 8 => 12500 KBytes/s * 0.8 => 10000
set_key BWLimit downloadLimit 10000
set_key BWLimit useUploadLimit 0
# 30 Mbit/s / 8 => 3750 KBytes/s * 0.8 => 3000
set_key BWLimit uploadLimit 3000
set_key Nextcloud remotePollInterval 60000
# Because of a bug, `nextcloud` wants to create logs at a deprecated location
# => An empty file at that location makes `nextcloud` use the new location
# inside ~/.config/Nextcloud
rm -rf "$XDG_DATA_HOME/Nextcloud"
touch "$XDG_DATA_HOME/Nextcloud"
# Resume `nextcloud`
setsid nextcloud --background >/dev/null 2>&1 &

View file

@ -0,0 +1,88 @@
#!/bin/sh
# Triggered by <Super><Alt>bracketleft
# as defined in [org/gnome/settings-daemon/plugins/media-keys]
# in ~/.config/gnome-settings/key-bindings.ini
#
# This script makes the screen a bit cooler than it currently is,
# unless it is currently in a ramping phase and not fully tinted
#
# The default warmth is restored at login by ~/.local/bin/gnome-restore
set -eu
# GNOME's GUI lets one choose between 1700 and 4700 only,
# and 6500 is the point where no color correction is applied at all
# while 1000 is the absolute minimum supported by the OS
_MIN_TEMPERATURE=1200
_MAX_TEMPERATURE=4700
notify() {
ID_FILE="${XDG_RUNTIME_DIR}/night-light-notification-id"
_id=$(cat "$ID_FILE" 2>/dev/null || true)
if [ -n "$_id" ]; then
notify-send -p -t 5000 --replace-id="$_id" "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
else
notify-send -p -t 5000 "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
fi
}
Schema=org.gnome.settings-daemon.plugins.color
active=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
applied=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color Temperature \
| sed -E 's/.*uint32 ([0-9]+).*/\1/')
automatic=$(gsettings get $Schema night-light-schedule-automatic)
current=$(gsettings get $Schema night-light-temperature | awk '{print $2}')
# Cooling never turns "Night Light" on
if [ "$active" = "(<false>,)" ]; then
notify "is not tinting the screen right now (target: ${current}K)"
exit 0
fi
# While the screen is not (fully) tinted,
# stepping the target has no visible effect
# => Only show what is going on
if [ "$automatic" = "true" ] && [ $((applied - current)) -gt 300 ]; then
notify "is still following the schedule's ramp (target: ${current}K)"
exit 0
fi
# Walking past the cool end switches the feature off
if [ "$current" -ge $_MAX_TEMPERATURE ]; then
gsettings set $Schema night-light-enabled false
notify "is now off (stepped past ${_MAX_TEMPERATURE}K)"
exit 0
fi
# 500 = (4700 - 1700 + 500) / 7
# => Seven steps spanning more than the range in the GUI,
# mainly because we want to allow a bit more warmth (i.e., +500)
new=$((current + 500))
if [ "$new" -gt $_MAX_TEMPERATURE ]; then
new=$_MAX_TEMPERATURE
fi
gsettings set $Schema night-light-temperature "uint32 $new"

View file

@ -0,0 +1,102 @@
#!/bin/sh
# Triggered by <Super><Alt>bracketright
# as defined in [org/gnome/settings-daemon/plugins/media-keys]
# in ~/.config/gnome-settings/key-bindings.ini
#
# This script ensures "Night Light" is forced on
# or makes the screen a bit warmer than it currently is
#
# The default warmth is restored at login by ~/.local/bin/gnome-restore
set -eu
# GNOME's GUI lets one choose between 1700 and 4700 only,
# and 6500 is the point where no color correction is applied at all
# while 1000 is the absolute minimum supported by the OS
_MIN_TEMPERATURE=1200
_MAX_TEMPERATURE=4700
_DEFAULT_TEMPERATURE=2200
notify() {
ID_FILE="${XDG_RUNTIME_DIR}/night-light-notification-id"
_id=$(cat "$ID_FILE" 2>/dev/null || true)
if [ -n "$_id" ]; then
notify-send -p -t 5000 --replace-id="$_id" "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
else
notify-send -p -t 5000 "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
fi
}
Schema=org.gnome.settings-daemon.plugins.color
active=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
applied=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color Temperature \
| sed -E 's/.*uint32 ([0-9]+).*/\1/')
automatic=$(gsettings get $Schema night-light-schedule-automatic)
current=$(gsettings get $Schema night-light-temperature | awk '{print $2}')
if [ "$active" = "(<true>,)" ] \
&& { [ "$automatic" = "false" ] || [ $((applied - current)) -le 300 ]; }; then
at_target=true
else
at_target=false
fi
# Step only if the screen already shows the target tint
# while off or ramping in, forcing on below provides the warming already
if [ "$at_target" = "true" ]; then
# 500 = (4700 - 1700 + 500) / 7
# => Seven steps spanning more than the range in the GUI,
# mainly because we want to allow a bit more warmth (i.e., +500)
new=$((current - 500))
if [ "$new" -lt $_MIN_TEMPERATURE ]; then
new=$_MIN_TEMPERATURE
fi
gsettings set $Schema night-light-temperature "uint32 $new"
# Untinted and parked at a cool target
# => Forcing on would barely show otherwise
elif [ "$active" = "(<false>,)" ] && [ "$current" -gt $_DEFAULT_TEMPERATURE ]; then
gsettings set $Schema night-light-temperature "uint32 $_DEFAULT_TEMPERATURE"
fi
# Always force "Night Light" on, saving a <Super><Alt>backslash call,
# with the same settings as in ~/.local/bin/night-light-toggle-schedule
if [ "$active" = "(<false>,)" ] || [ "$automatic" = "true" ]; then
gsettings set $Schema night-light-enabled true
gsettings set $Schema night-light-schedule-automatic false
gsettings set $Schema night-light-schedule-from 0.0
gsettings set $Schema night-light-schedule-to 24.0
fi
# Notify only if the press produced no visible change
# (i.e., already at the minimum and not just snapped to full warmth by the force)
if [ "$at_target" = "true" ] && [ "$current" -le $_MIN_TEMPERATURE ]; then
notify "is already as warm as it gets (${_MIN_TEMPERATURE}K)"
fi

25
.local/bin/night-light-reset Executable file
View file

@ -0,0 +1,25 @@
#!/bin/sh
# Ensure "Night Light" is enabled,
# set to run according to the automatic schedule,
# and reverted to the `_DEFAULT_TEMPERATURE`
#
# Intended to be run at 03:00 by the `night-light-reset.timer` systemd
# user unit, so that a forced state does not persist
#
# See ~/.local/bin/night-light-toggle-schedule for detailed documentation
set -eu
_DEFAULT_TEMPERATURE=2200
Schema=org.gnome.settings-daemon.plugins.color
gsettings set $Schema night-light-enabled true
gsettings set $Schema night-light-schedule-automatic true
gsettings set $Schema night-light-schedule-from 18.0
gsettings set $Schema night-light-schedule-to 6.0
gsettings set $Schema night-light-temperature "uint32 $_DEFAULT_TEMPERATURE"

View file

@ -0,0 +1,75 @@
#!/bin/sh
# Triggered by <Super><Alt><Shift>backslash
# as defined in [org/gnome/settings-daemon/plugins/media-keys]
# in ~/.config/gnome-settings/key-bindings.ini
#
# See ~/.local/bin/night-light-toggle-schedule
# for the other half of the toggling logic
# and extensive documentation
set -eu
notify() {
ID_FILE="${XDG_RUNTIME_DIR}/night-light-notification-id"
_id=$(cat "$ID_FILE" 2>/dev/null || true)
if [ -n "$_id" ]; then
notify-send -p -t 5000 --replace-id="$_id" "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
else
notify-send -p -t 5000 "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
fi
}
Schema=org.gnome.settings-daemon.plugins.color
if [ "$(gsettings get $Schema night-light-enabled)" = "true" ]; then
active_before=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
gsettings set $Schema night-light-enabled false
if [ "$active_before" = "(<false>,)" ]; then
notify "is now disabled, but the screen was not tinted anyways"
fi
else
gsettings set $Schema night-light-enabled true
# We could start from our default warmth, as defined
# in [org/gnome/settings-daemon/plugins/color]
# in ~/.config/gnome-settings/misc.ini
# but choose not to do that because this is meant to be
# a plain on/off switch with no side effects
# => Snapping is left to <Super><Alt>bracketright, which is about
# warmth rather than about the feature being on or off
#
# _DEFAULT_TEMPERATURE=2200
# current=$(gsettings get $Schema night-light-temperature | awk '{print $2}')
#
# if [ "$current" -gt $_DEFAULT_TEMPERATURE ]; then
# gsettings set $Schema night-light-temperature "uint32 $_DEFAULT_TEMPERATURE"
# fi
# Give the settings daemon a moment to recompute "NightLightActive"
sleep 0.1
active_after=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
if [ "$active_after" = "(<false>,)" ]; then
notify "is now enabled, but the schedule keeps it off for now"
fi
fi

View file

@ -0,0 +1,145 @@
#!/bin/sh
# Triggered by <Super><Alt>backslash
# as defined in [org/gnome/settings-daemon/plugins/media-keys]
# in ~/.config/gnome-settings/key-bindings.ini
#
# Night Light control:
#
# - <Super><Alt>backslash => "when" toggle
# + "Force" the screen to be warm right now!
# (Turns Night Light on if it was off, and keeps it on all day)
# + "Un-force" the Night Light into the local evening schedule
# - <Super><Alt><Shift>backslash => "if" toggle
# Plain on/off switch for the Night Light feature
# - <Super><Alt>bracketleft => Make the screen cooler
# - <Super><Alt>bracketright => Make the screen warmer, and "force" warmness
#
#
# State machine:
#
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | | "IF" toggle | "WHEN" toggle | "make COOLER" | "make WARMER" |
# | Current STATE | <Super><Alt><Shift>backslash | <Super><Alt>backslash | <Super><Alt>bracketleft | <Super><Alt>bracketright |
# | | | | | |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | DISABLED | ENABLE | enabled | still disabled | enabled |
# | + scheduled | + still scheduled | + FORCED on | + still scheduled | + FORCED on |
# | => no tint | => no or set tint | => set tint | => still no tint | => set tint |
# | | based on time N? | | N! | *1 |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | DISABLED | ENABLE | enabled | still disabled | enabled |
# | + forced on | + still forced | + still FORCED on | + still forced | + still FORCED on |
# | => no tint | => set tint | => set tint | => still no tint | => set tint |
# | | | | N! | *1 |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | enabled | DISABLE | still enabled | still enabled | still enabled |
# | + SCHEDULED (day) | + still scheduled | + FORCED on | + still scheduled | + FORCED on |
# | => no tint yet | => still no tint | => set tint | => still no tint | => set tint |
# | | N! | | N! | *1 |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | enabled | DISABLE | still enabled | still enabled | still enabled |
# | + SCHEDULED (ramping) | + still scheduled | + FORCED on | + still scheduled | + FORCED on |
# | => some tint already | => no tint | => set tint | => no change in tint | => set tint |
# | | | | N! | |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | enabled | DISABLE | still enabled | still enabled | still enabled |
# | + SCHEDULED (night) | + still scheduled | + FORCED on | + still scheduled | + FORCED on |
# | => set tint | => no tint | => set tint | => LESS tint | => MORE tint |
# | | | N! | *2 N? | N? |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# | enabled | DISABLE | still enabled | still enabled | still enabled |
# | + FORCED on | + still forced on | + back to SCHEDULED | + still forced on | + still forced on |
# | => set tint | => no tint | => no or set tint | => LESS tint | => MORE tint |
# | | | based on time N? | *2 N? | N? |
# +-----------------------+------------------------------+------------------------------+------------------------------+------------------------------+
# Legend:
# - N! => A notification is shown for sure because there is no visible change on the screen
# - N? => A notification is shown if there was no visible change on the screen
# - *1 => The temperature is snapped to 2200K to ensure a visible effect
# - *2 => The tint is turned off entirely when stepping over the maximum temperature
#
# Notes:
# - The main state property or state change is shown in CAPS
# - Steps range from 1200K (warmest) to 4700K (coolest),
# which is a bit more than the 1700K to 4700K in `gnome-control-center`
# - If the range is already exhausted, so that the tint does not change, a notification is shown
# - A 03:00 timer re-enables the Night Light feature and puts it back on the automatic schedule
# with the `$_DEFAULT_TEMPERATURE` set
#
set -eu
notify() {
ID_FILE="${XDG_RUNTIME_DIR}/night-light-notification-id"
_id=$(cat "$ID_FILE" 2>/dev/null || true)
if [ -n "$_id" ]; then
notify-send -p -t 5000 --replace-id="$_id" "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
else
notify-send -p -t 5000 "Night Light" "$1" > "$ID_FILE" || rm -f "$ID_FILE"
fi
}
Schema=org.gnome.settings-daemon.plugins.color
active_before=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
applied=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color Temperature \
| sed -E 's/.*uint32 ([0-9]+).*/\1/')
automatic=$(gsettings get $Schema night-light-schedule-automatic)
enabled=$(gsettings get $Schema night-light-enabled)
target=$(gsettings get $Schema night-light-temperature | awk '{print $2}')
if [ "$automatic" = "true" ] || [ "$enabled" = "false" ]; then
# Force "Night Light" enabled, enabling it if needed
gsettings set $Schema night-light-enabled true
gsettings set $Schema night-light-schedule-automatic false
gsettings set $Schema night-light-schedule-from 0.0
gsettings set $Schema night-light-schedule-to 24.0
if [ "$active_before" = "(<true>,)" ] && [ "$automatic" = "true" ] \
&& [ $((applied - target)) -le 300 ]; then
notify "is now forced to stay on and no longer on an automatic schedule"
fi
else
# Back to the live location's schedule
gsettings set $Schema night-light-schedule-automatic true
gsettings set $Schema night-light-schedule-from 18.0
gsettings set $Schema night-light-schedule-to 6.0
if [ "$active_before" = "(<true>,)" ]; then
# Give the settings daemon a moment to recompute "NightLightActive"
sleep 0.1
active_after=$(gdbus call --session \
--dest org.gnome.SettingsDaemon.Color \
--object-path /org/gnome/SettingsDaemon/Color \
--method org.freedesktop.DBus.Properties.Get \
org.gnome.SettingsDaemon.Color NightLightActive)
if [ "$active_after" = "(<true>,)" ]; then
notify "is still on because of an automatic schedule"
fi
fi
fi

152
.local/bin/randomize-background Executable file
View file

@ -0,0 +1,152 @@
#!/bin/sh
# Determine the next wallpaper by round robin from the cloud
# and install it as the background files referenced in
# ~/.config/gnome-settings/misc.ini
#
# Candidates:
# - Pairs => Both "<name>-d.jpg" and "<name>-l.jpg" exist
# - Singles => Any other "*.jpg" not following the -d/-l scheme,
# used as dark and light at the same time
#
# The live `gsettings` URIs point at uniquely named copies
# because `gnome-shell` caches background images by file URI;
# `gnome-restore` cleans up that temporary state during the next login
set -eu
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
SOURCE_DIR="$HOME/Cloud/Null-Tech/Backgrounds"
TARGET_DIR="$XDG_DATA_HOME/backgrounds"
LOCAL_INI="$XDG_CONFIG_HOME/gnome-settings/local/temporary_background.ini"
STATE_FILE="$XDG_STATE_HOME/current-background.txt"
[ -d "$SOURCE_DIR" ] || {
echo "No such folder: $SOURCE_DIR" >&2
exit 1
}
# One candidate per line, tab-separated: <dark-file> <light-file>
candidates=$(
for jpg in "$SOURCE_DIR"/*.jpg; do
[ -e "$jpg" ] || continue # because the glob does not expand if empty
case "$jpg" in
*-d.jpg) # Complete pairs only
stem="${jpg%-d.jpg}"
[ -f "$stem-l.jpg" ] && printf '%s\t%s\n' "$jpg" "$stem-l.jpg"
;;
*-l.jpg) # Already handled via its "-d" counterpart above
;;
*) # A single file to be used for both modes
printf '%s\t%s\n' "$jpg" "$jpg"
;;
esac
done
)
if [ -z "$candidates" ]; then
echo "No usable *.jpg files in: $SOURCE_DIR" >&2
exit 1
fi
echo "Found $(printf '%s\n' "$candidates" | wc -l) candidate(s)"
# The glob expands alphabetically => "$candidates" is already sorted
# => Pick the first candidate sorting strictly after the last-used one,
# falling back to the first one if necessary
# (e.g., for start, wrap-around, or missing state file)
last_background=$(cat "$STATE_FILE" 2>/dev/null || true)
next_background=$(printf '%s\n' "$candidates" | awk -F '\t' -v last_background="$last_background" '$1 > last_background { print; exit }')
[ -n "$next_background" ] || next_background=$(printf '%s\n' "$candidates" | head -n 1)
dark_source=$(printf '%s' "$next_background" | cut -f 1)
light_source=$(printf '%s' "$next_background" | cut -f 2)
echo "Chosen background: $(basename "$dark_source")"
dark_target="$TARGET_DIR/dark.jpg"
light_target="$TARGET_DIR/light.jpg"
# Create a unique name per run
# so that the URIs below never repeat
timestamp=$(date +%s)
dark_live_target="$TARGET_DIR/dark-tmp-$timestamp.jpg"
light_live_target="$TARGET_DIR/light-tmp-$timestamp.jpg"
mkdir -p \
"$TARGET_DIR" \
"$(dirname "$LOCAL_INI")" \
"$(dirname "$STATE_FILE")"
# Fixed-name copies, referenced as login-time defaults in the *.ini files
cp -- "$dark_source" "$dark_target"
cp -- "$light_source" "$light_target"
# Uniquely named copies for the live `gsettings` URIs
# to ensure `gnome-shell` uses the correct files right away
cp -- "$dark_source" "$dark_live_target"
cp -- "$light_source" "$light_live_target"
printf '%s\n' "$dark_source" > "$STATE_FILE"
Background=org.gnome.desktop.background
Screensaver=org.gnome.desktop.screensaver
gsettings set $Background picture-uri-dark "file://$dark_live_target"
gsettings set $Background picture-uri "file://$light_live_target"
gsettings set $Screensaver picture-uri "file://$dark_live_target"
# Persist the unique URIs for `gnome-restore`,
# which loads local/*.ini after the base *.ini files
cat > "$LOCAL_INI" <<EOF
# Written by $(basename "$0") - do not edit
[org/gnome/desktop/background]
picture-uri-dark='file://$dark_live_target'
picture-uri='file://$light_live_target'
[org/gnome/desktop/screensaver]
picture-uri='file://$dark_live_target'
EOF
# Remove the uniquely named copies of previously chosen wallpapers
find "$TARGET_DIR" -name 'dark-tmp-*.jpg' ! -name "dark-tmp-$timestamp.jpg" -delete
find "$TARGET_DIR" -name 'light-tmp-*.jpg' ! -name "light-tmp-$timestamp.jpg" -delete
echo "Installed as: $TARGET_DIR/{dark,light}.jpg"

36
.local/bin/screenshot-screen Executable file
View file

@ -0,0 +1,36 @@
#!/bin/sh
# Take a screenshot of the whole screen
# (i.e., what "<Shift>Print" does)
#
# The screenshot machinery lives inside `gnome-shell`
# and cannot be called directly because
# its D-Bus methods are restricted to trusted callers
#
# => We synthesize the key press instead, after a short delay
# so that a launcher's keyboard grab (e.g., `rofi`) is released
set -eu
[ "${XDG_SESSION_TYPE:-}" = "x11" ] || {
echo "X11 only (xdotool)" >&2
exit 1
}
# Optional countdown in seconds (e.g., `screenshot-screen 3`),
# defaulting to 0.2
countdown="${1:-0.2}"
case "$countdown" in
''|.|*[!0-9.]*|*.*.*)
echo "Not a number: $countdown" >&2
exit 1
;;
esac
sleep "$countdown"
xdotool key shift+Print

25
.local/bin/screenshot-ui Executable file
View file

@ -0,0 +1,25 @@
#!/bin/sh
# Open GNOME's screenshot UI
# (i.e., what a simple "Print" does)
#
# The screenshot machinery lives inside `gnome-shell`
# and cannot be called directly because
# its D-Bus methods are restricted to trusted callers
#
# => We synthesize the key press instead, after a short delay
# so that a launcher's keyboard grab (e.g., `rofi`) is released
set -eu
[ "${XDG_SESSION_TYPE:-}" = "x11" ] || {
echo "X11 only (xdotool)" >&2
exit 1
}
sleep 0.2
xdotool key Print

36
.local/bin/screenshot-window Executable file
View file

@ -0,0 +1,36 @@
#!/bin/sh
# Take a screenshot of the focused window
# (i.e., what "<Alt>Print" does)
#
# The screenshot machinery lives inside `gnome-shell`
# and cannot be called directly because
# its D-Bus methods are restricted to trusted callers
#
# => We synthesize the key press instead, after a short delay
# so that a launcher's keyboard grab (e.g., `rofi`) is released
set -eu
[ "${XDG_SESSION_TYPE:-}" = "x11" ] || {
echo "X11 only (xdotool)" >&2
exit 1
}
# Optional countdown in seconds (e.g., `screenshot-window 3`),
# defaulting to 0.2
countdown="${1:-0.2}"
case "$countdown" in
''|.|*[!0-9.]*|*.*.*)
echo "Not a number: $countdown" >&2
exit 1
;;
esac
sleep "$countdown"
xdotool key alt+Print

27
.local/bin/wifi-toggle Executable file
View file

@ -0,0 +1,27 @@
#!/bin/sh
# Toggle the WiFi on or off
set -eu
_notify() {
command -v notify-send >/dev/null 2>&1 || return 0
notify-send 'WiFi' "$1"
}
case "$(nmcli -t radio wifi)" in
enabled)
nmcli radio wifi off
_notify 'Radio turned off'
;;
disabled)
nmcli radio wifi on
_notify 'Radio turned on'
;;
*)
_notify 'No radio available'
exit 1
;;
esac

View file

@ -0,0 +1,4 @@
# Desktop Entries
This folder contains `*.desktop` launchers
for some scripts in [`~/.local/bin`](../../bin).

View file

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Capture the whole screen
Exec=screenshot-screen
Icon=applets-screenshooter
Terminal=false
Categories=Utility;

View file

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Open GNOME's screenshot UI
Exec=screenshot-ui
Icon=applets-screenshooter
Terminal=false
Categories=Utility;

View file

@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Capture the focused window
Exec=screenshot-window
Icon=applets-screenshooter
Terminal=false
Categories=Utility;