Fix silently-broken gTile keybindings and add Tactile extension

gsettings set/reset against org.gnome.shell.extensions.* schemas fails
with "No such schema" from a plain shell (the schema is bundled inside
the extension, never registered system-wide) -- this was silently
no-op'ing all 16 preset-resize-1..16 keybindings plus the preset-resize-21
self-heal in startup_bernina_terminal on every single run. Switched all of
these to dconf write/reset, which operates on the raw key store and needs
no schema.

Also hardened install_gtile against transient failures at login (no
retry, no logging previously made intermittent skips undiagnosable) by
extracting the shared logic into install_gnome_extension and adding
retries + a log file, then reused it for a new install_tactile, wiring
the Tactile extension (tactile@lundal.io, vetted against gnome-shell 40)
into startup_bernina_terminal alongside gTile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
lemke_handClaude Sonnet 5 committed 2026-09-07 16:01:24 +02:00
1 parent 2d8b6e046f
commit fddc40f1d2
4 files changed
+177 -90

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# Install/update a gnome-shell extension from the shared applications
# directory into the user's local extensions dir, and enable it. Shared by
# install_gtile and install_tactile -- see those for which UUID/build was
# vetted compatible with this rhel9 / gnome-shell 40 desktop.
#
# Usage: install_gnome_extension <uuid>
UUID="${1:?Usage: $(basename "$0") <uuid>}"
SRC="/sf/bernina/applications/${UUID}"
EXT_DIR="${HOME}/.local/share/gnome-shell/extensions"
DEST="${EXT_DIR}/${UUID}"
log() { echo "$(date '+%F %T') install_gnome_extension(${UUID}): $*"; }
# This runs at login via autostart, right when /sf/bernina (a Kerberos-secured
# NFS4 mount, sec=krb5) may still be settling its GSS context for this brand
# new session -- retry briefly instead of permanently giving up on a
# transient "not there yet" (confirmed intermittent gTile failures previously
# had no logging at all here, so a one-shot failure like this could never be
# told apart from a real missing-source case; see gtile_link_migration memory).
tries=0
until [ -d "$SRC" ]; do
tries=$((tries + 1))
if [ "$tries" -ge 10 ]; then
log "source $SRC still not found after ${tries}s, giving up"
exit 0
fi
sleep 1
done
[ "$tries" -gt 0 ] && log "source $SRC took ${tries}s to become available"
# PSI's home-sync (linux_home_links.yaml) links this dir to the SMB network
# drive, but a known depth-nesting bug in that tool leaves it a broken
# symlink (missing a "../../../" prefix) -- fall back to a plain local
# directory rather than failing the install.
if [ -L "$EXT_DIR" ] && [ ! -e "$EXT_DIR" ]; then
log "$EXT_DIR is a broken symlink ($(readlink "$EXT_DIR")), replacing with a local directory"
rm -f "$EXT_DIR"
fi
# Clear anything GNOME Shell's own extensions.gnome.org auto-update check
# may have already staged here before allow-extension-installation (set by
# the main startup script) had a chance to stop it -- a stale staged build
# would otherwise still be sitting here ready to silently swap in on some
# future shell restart, superseding the pinned version reinstalled below.
rm -rf "${HOME}/.local/share/gnome-shell/extension-updates/${UUID}"
src_version=$(sed -n 's/.*"version": *\([0-9]*\).*/\1/p' "$SRC/metadata.json")
dest_version=""
[ -f "$DEST/metadata.json" ] && dest_version=$(sed -n 's/.*"version": *\([0-9]*\).*/\1/p' "$DEST/metadata.json")
reinstalled=false
if [ ! -d "$DEST" ] || [ "$src_version" != "$dest_version" ]; then
mkdir -p "$(dirname "$DEST")"
rsync -a --delete "$SRC/" "$DEST/"
glib-compile-schemas "$DEST/schemas" 2>/dev/null
log "installed version $src_version to $DEST (was: ${dest_version:-none})"
reinstalled=true
fi
# A plain `enable` is a no-op if the extension is already enabled -- the
# normal case every login -- so it does NOT make gnome-shell pick up a
# version this script just rsynced onto disk above (extensions aren't
# hot-reloaded on file change). Force a disable/enable cycle whenever we
# actually changed what's on disk, so the running session matches disk
# immediately instead of only from the next full login onward (confirmed:
# this exact gap is what left saresb-cons-05 running a stale in-memory gTile
# version even after the rsync above had already put the correct one back
# on disk).
if $reinstalled && gnome-extensions list --enabled 2>/dev/null | grep -qx "$UUID"; then
gnome-extensions disable "$UUID" 2>/dev/null
fi
# enable the extension (needs a live gnome-shell D-Bus session, i.e.
# org.gnome.Shell.Extensions already registered -- also not guaranteed yet
# this early at login, so retry briefly before falling back).
enabled=false
tries=0
until gnome-extensions enable "$UUID" 2>/dev/null; do
tries=$((tries + 1))
if [ "$tries" -ge 5 ]; then
break
fi
sleep 1
done
if [ "$tries" -lt 5 ] || gnome-extensions list --enabled 2>/dev/null | grep -qx "$UUID"; then
enabled=true
[ "$tries" -gt 0 ] && log "gnome-extensions enable succeeded after ${tries}s"
fi
if ! $enabled; then
# shell D-Bus interface still not reachable -- fall back to writing the
# enabled-extensions list directly; gnome-shell picks this up itself once
# it (or its extension-directory file monitor) is ready.
log "gnome-extensions enable unreachable after ${tries}s, falling back to gsettings"
python3 - "$UUID" <<'EOF'
import subprocess, sys
uuid = sys.argv[1]
current = eval(subprocess.check_output(
["gsettings", "get", "org.gnome.shell", "enabled-extensions"]).decode())
if uuid not in current:
current.append(uuid)
subprocess.call(["gsettings", "set", "org.gnome.shell",
"enabled-extensions", str(current)])
EOF
fi
+2 -71
View File
@@ -1,73 +1,4 @@
#!/bin/bash
# Install/update the gTile gnome-shell extension from the shared applications
# directory into the user's local extensions dir, and enable it.
# Compatible gTile build for this rhel9 / gnome-shell 40 desktop lives in
# /sf/bernina/applications/gTile@vibou.
UUID="gTile@vibou"
SRC="/sf/bernina/applications/${UUID}"
EXT_DIR="${HOME}/.local/share/gnome-shell/extensions"
DEST="${EXT_DIR}/${UUID}"
if [ ! -d "$SRC" ]; then
echo "install_gtile: source $SRC not found, skipping" >&2
exit 0
fi
# PSI's home-sync (linux_home_links.yaml) links this dir to the SMB network
# drive, but a known depth-nesting bug in that tool leaves it a broken
# symlink (missing a "../../../" prefix) -- fall back to a plain local
# directory rather than failing the install.
if [ -L "$EXT_DIR" ] && [ ! -e "$EXT_DIR" ]; then
echo "install_gtile: $EXT_DIR is a broken symlink ($(readlink "$EXT_DIR")), replacing with a local directory" >&2
rm -f "$EXT_DIR"
fi
# Clear anything GNOME Shell's own extensions.gnome.org auto-update check
# may have already staged here before allow-extension-installation (set by
# the main startup script) had a chance to stop it -- a stale staged build
# would otherwise still be sitting here ready to silently swap in on some
# future shell restart, superseding the pinned version reinstalled below.
rm -rf "${HOME}/.local/share/gnome-shell/extension-updates/${UUID}"
src_version=$(sed -n 's/.*"version": *\([0-9]*\).*/\1/p' "$SRC/metadata.json")
dest_version=""
[ -f "$DEST/metadata.json" ] && dest_version=$(sed -n 's/.*"version": *\([0-9]*\).*/\1/p' "$DEST/metadata.json")
reinstalled=false
if [ ! -d "$DEST" ] || [ "$src_version" != "$dest_version" ]; then
mkdir -p "$(dirname "$DEST")"
rsync -a --delete "$SRC/" "$DEST/"
glib-compile-schemas "$DEST/schemas" 2>/dev/null
echo "install_gtile: installed gTile version $src_version to $DEST"
reinstalled=true
fi
# A plain `enable` is a no-op if the extension is already enabled -- the
# normal case every login -- so it does NOT make gnome-shell pick up a
# version this script just rsynced onto disk above (extensions aren't
# hot-reloaded on file change). Force a disable/enable cycle whenever we
# actually changed what's on disk, so the running session matches disk
# immediately instead of only from the next full login onward (confirmed:
# this exact gap is what left saresb-cons-05 running a stale in-memory
# version even after the rsync above had already put the correct one back
# on disk).
if $reinstalled && gnome-extensions list --enabled 2>/dev/null | grep -qx "$UUID"; then
gnome-extensions disable "$UUID" 2>/dev/null
fi
# enable the extension (needs a live gnome-shell D-Bus session)
if ! gnome-extensions enable "$UUID" 2>/dev/null; then
# shell D-Bus interface not reachable yet (e.g. very early in session
# startup) -- fall back to writing the enabled-extensions list directly
python3 - "$UUID" <<'EOF'
import subprocess, sys
uuid = sys.argv[1]
current = eval(subprocess.check_output(
["gsettings", "get", "org.gnome.shell", "enabled-extensions"]).decode())
if uuid not in current:
current.append(uuid)
subprocess.call(["gsettings", "set", "org.gnome.shell",
"enabled-extensions", str(current)])
EOF
fi
# /sf/bernina/applications/gTile@vibou (shell-version includes "40").
exec /sf/bernina/bin/install_gnome_extension "gTile@vibou"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
# Compatible Tactile build for this rhel9 / gnome-shell 40 desktop lives in
# /sf/bernina/applications/tactile@lundal.io -- version 27 (extensions.gnome.org
# package 40260), whose shell-version list covers 3.32 through 44, including
# this desktop's 40.10.
exec /sf/bernina/bin/install_gnome_extension "tactile@lundal.io"
+62 -19
View File
@@ -203,7 +203,12 @@ set_gnome_keyboard_shortcut 'dark screens' 'xset dpms force off' '<Ctrl><Alt>d'
# reason for it to be on when every extension this desktop uses is already
# managed by hand.
gsettings set org.gnome.shell allow-extension-installation false
install_gtile
# Logged (unlike a bare call) so an intermittent skip/fallback here -- e.g.
# /sf/bernina (Kerberos NFS4) not yet reachable this early at login, or
# gnome-shell's D-Bus Extensions interface not registered yet -- leaves a
# trace instead of silently vanishing, since this runs unattended via
# autostart with no terminal to watch.
install_gtile >> "$HOME/.install_gtile.log" 2>&1
dconf write /org/gnome/shell/extensions/gtile/resize21 "'3x3 0:2 0:2, 0:1 0:2, 0:2 1:2, 0:1 1:2'"
dconf write /org/gnome/shell/extensions/gtile/resize22 "'3x3 1:2 1:2, 1:1 1:2, 0:2 2:2, 0:1 2:2'"
dconf write /org/gnome/shell/extensions/gtile/resize23 "'3x3 2:2 2:2, 2:1 2:2, 1:2 2:2, 1:1 2:2'"
@@ -219,7 +224,15 @@ dconf write /org/gnome/shell/extensions/gtile/resize29 "'3x3 2:0 2:0, 2:0 2:1, 1
# every other preset-resize-2N sibling was untouched and still works fine)
# -- reset it back to gTile's own default rather than hardcoding a value
# here, so this self-heals regardless of what that default actually is.
gsettings reset org.gnome.shell.extensions.gtile preset-resize-21
# NOTE: `dconf reset`, not `gsettings reset` -- gTile's schema is a
# relocatable one bundled inside the extension itself and never registered
# system-wide, so plain `gsettings` (run outside gnome-shell's own process,
# which loads it internally) can't see it at all ("No such schema",
# confirmed by hand -- this line was silently failing every single run).
# `dconf reset` only touches the raw key/value store, no schema needed --
# gnome-shell's own GSettings wrapper then falls back to the compiled-in
# default the next time it reads the now-unset key.
dconf reset /org/gnome/shell/extensions/gtile/preset-resize-21
# Second gTile preset set: a 4x4 grid on <Super><Shift> + the right-hand
# key block, laid out to spatially match the keyboard (row of keys top to
@@ -260,22 +273,52 @@ dconf write /org/gnome/shell/extensions/gtile/resize13 "'4x4 0:0 3:3, 0:1 2:3, 0
dconf write /org/gnome/shell/extensions/gtile/resize14 "'4x4 1:1 3:3, 0:1 2:3, 0:1 0:3, 0:3 0:3'"
dconf write /org/gnome/shell/extensions/gtile/resize15 "'4x4 2:2 3:3, 2:3 2:3, 2:3 0:3, 0:3 0:3'"
dconf write /org/gnome/shell/extensions/gtile/resize16 "'4x4 3:3 3:3, 2:3 2:3, 2:3 0:3, 0:3 0:3'"
gsettings set org.gnome.shell.extensions.gtile preset-resize-1 "['<Super><Shift>7']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-2 "['<Super><Shift>8']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-3 "['<Super><Shift>9']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-4 "['<Super><Shift>0']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-5 "['<Super><Shift>u']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-6 "['<Super><Shift>i']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-7 "['<Super><Shift>o']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-8 "['<Super><Shift>p']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-9 "['<Super><Shift>j']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-10 "['<Super><Shift>k']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-11 "['<Super><Shift>l']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-12 "['<Super><Shift>semicolon']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-13 "['<Super><Shift>m']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-14 "['<Super><Shift>comma']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-15 "['<Super><Shift>period']"
gsettings set org.gnome.shell.extensions.gtile preset-resize-16 "['<Super><Shift>slash']"
# NOTE: `dconf write`, not `gsettings set` -- gTile's schema is bundled
# inside the extension and never registered system-wide, so plain
# `gsettings` (run outside gnome-shell's own process) can't see it at all
# ("No such schema", confirmed by hand). These 16 lines used to be
# `gsettings set` and were therefore silently failing every single run --
# the resize1-16 grid layouts above were being written fine (dconf write
# never needed the schema), but the keybindings meant to trigger them never
# actually got assigned, leaving preset-resize-1..16 at gTile's factory
# defaults (<Super><Alt>KP_N / <Super><Control>KP_N) instead of this
# right-hand block. This is almost certainly why this second preset set
# looked like it "didn't work" while the numpad set above (which only ever
# needed dconf write, no keybinding reassignment) did.
dconf write /org/gnome/shell/extensions/gtile/preset-resize-1 "['<Super><Shift>7']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-2 "['<Super><Shift>8']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-3 "['<Super><Shift>9']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-4 "['<Super><Shift>0']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-5 "['<Super><Shift>u']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-6 "['<Super><Shift>i']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-7 "['<Super><Shift>o']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-8 "['<Super><Shift>p']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-9 "['<Super><Shift>j']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-10 "['<Super><Shift>k']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-11 "['<Super><Shift>l']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-12 "['<Super><Shift>semicolon']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-13 "['<Super><Shift>m']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-14 "['<Super><Shift>comma']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-15 "['<Super><Shift>period']"
dconf write /org/gnome/shell/extensions/gtile/preset-resize-16 "['<Super><Shift>slash']"
#install/update and enable the tactile extension (a second, simpler
#grid-tiling extension alongside gTile above), then pin its keyboard
#shortcuts to its own shipped defaults so they self-heal against the same
#kind of stray-override drift documented for gTile's preset-resize-21.
# `dconf reset`, not `gsettings reset` -- see the note on gTile's
# preset-resize-21 above: tactile's schema is bundled inside the extension
# and never registered system-wide, so plain `gsettings` can't see it.
install_tactile >> "$HOME/.install_tactile.log" 2>&1
dconf reset /org/gnome/shell/extensions/tactile/show-tiles
dconf reset /org/gnome/shell/extensions/tactile/hide-tiles
dconf reset /org/gnome/shell/extensions/tactile/show-settings
dconf reset /org/gnome/shell/extensions/tactile/next-monitor
dconf reset /org/gnome/shell/extensions/tactile/prev-monitor
dconf reset /org/gnome/shell/extensions/tactile/layout-1
dconf reset /org/gnome/shell/extensions/tactile/layout-2
dconf reset /org/gnome/shell/extensions/tactile/layout-3
dconf reset /org/gnome/shell/extensions/tactile/layout-4
# gnome terminal color profiles: install Solarized Dark/Light and Spacedust
# (plus a "1-Spacedust" copy that sorts to the top of the profile menu) if
@@ -337,5 +380,5 @@ command -v notify-send >/dev/null 2>&1 && notify-send -u low "Bernina Terminal"
# make rhel9 text larger and rely on default dash, no extra cluttering dock.
dconf write /org/gnome/desktop/interface/text-scaling-factor 1.25
dconf write /org/gnome/shell/enabled-extensions "['appindicatorsupport@rgcjonas.gmail.com', 'gTile@vibou']"
dconf write /org/gnome/shell/enabled-extensions "['appindicatorsupport@rgcjonas.gmail.com', 'gTile@vibou', 'tactile@lundal.io']"
dconf write /org/gnome/shell/disabled-extensions "['dash-to-dock@gnome-shell-extensions.gcampax.github.com']"