foxygit / dotfiles Log in
commit db8110568c63f2aa3c05ace63937c21f40a1ee1e
Author:     MrJensK <jens.se@icloud.com>
AuthorDate: Sun Aug 30 20:08:14 2026 +0200
Commit:     MrJensK <jens.se@icloud.com>
CommitDate: Sun Aug 30 20:08:14 2026 +0200

    update
---
 README.md                              |  69 ++++++++++-
 applications/geany.desktop             | 171 ++++++++++++++++++++++++++
 bin/gcfs                               | 214 +++++++++++++++++++++++++++++++++
 bin/geany                              |   8 ++
 bin/status                             | 170 ++++++++++++++++++++++++++
 config/gcfs/vaults.conf                |   5 +
 config/geany/colorschemes/gruvbox.conf |  95 +++++++++++++++
 config/geany/geany.conf                | 201 +++++++++++++++++++++++++++++++
 install.sh                             |  87 ++++++++++++++
 st.code-workspace                      |  11 ++
 themes/Gruvbox-Dark/gtk-3.0/gtk.css    |  26 ++++
 themes/Gruvbox-Dark/index.theme        |   9 ++
 udev/71-bluetooth-no-autosuspend.rules |   5 +
 13 files changed, 1067 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 9079f81..a778ba4 100644
--- a/README.md
+++ b/README.md
@@ -85,9 +85,16 @@ The script installs system packages, builds and installs `sxwm` (with all local
 patches applied), `sxbar`, `dmenu`, `st` (with all local patches applied) and
 `bluetui` from source, installs the JetBrainsMono Nerd Font, copies config files
 (backing up anything that already exists), installs the utility scripts under
-`~/.local/bin`, and offers to write `~/.xinitrc` for you. A couple of steps are
-interactive (writing `~/.xinitrc`, enabling the lid-watch daemon) so you can opt
-out if you already have your own setup for those.
+`~/.local/bin`, sets up Geany with a Gruvbox theme (see [Geany](#geany) below),
+and offers to write `~/.xinitrc` for you. A couple of steps are interactive
+(writing `~/.xinitrc`, enabling the lid-watch daemon, setting
+`HandleLidSwitch=ignore`, installing the Bluetooth no-autosuspend udev rule)
+so you can opt out if you already have your own setup for those.
+
+`HandleLidSwitch=ignore` only takes effect on the next reboot — the script
+deliberately doesn't restart `systemd-logind` live, since on this machine's
+manually-started (no display manager) X session that has hung the whole
+desktop and forced a hard reboot.

 ## Structure

@@ -97,6 +104,8 @@ dotfiles/
 ├── BG/
 │   └── j9huwdxo1zzg1.jpeg       # Desktop wallpaper
 ├── bin/                          # Utility scripts, installed to ~/.local/bin
+│   ├── gcfs                       # Minimal gocryptfs vault manager (config-driven)
+│   ├── geany                      # Wrapper: launches Geany with GTK_THEME=Gruvbox-Dark
 │   ├── internal-display          # on/off/toggle/status for the internal (eDP) screen
 │   ├── lid-display-watch         # Background daemon: auto internal-display on lid open/close
 │   ├── lidsuspend                # Temporarily inhibit lid-close suspend
@@ -104,7 +113,8 @@ dotfiles/
 │   ├── screensaver                # Runs random TTE animations in a loop
 │   ├── screensaverd               # Idle watcher, launches the screensaver after inactivity
 │   ├── screensaver-launch         # Launches the screensaver fullscreen on every monitor
-│   └── screensaver.txt            # ASCII art shown by the screensaver
+│   ├── screensaver.txt            # ASCII art shown by the screensaver
+│   └── status                     # Overview: WM stack, services, network, bluetooth, audio, battery, lock state
 ├── *.patch                       # Local sxwm patches, see "sxwm patches" below
 ├── st-patches/                    # Upstream st patches, see "st patches" below
 │   ├── 01-delkey.patch
@@ -118,9 +128,23 @@ dotfiles/
 │   ├── 09-anysize.patch
 │   ├── 10-histsize-5000.patch
 │   └── 11-kitty-graphics-protocol.patch
+├── applications/
+│   └── geany.desktop              # Geany launcher override (forces Gruvbox-Dark GTK theme)
+├── udev/
+│   └── 71-bluetooth-no-autosuspend.rules  # Opt-in: disables USB autosuspend on the BT adapter
+├── themes/
+│   └── Gruvbox-Dark/               # GTK3 theme, installed to ~/.local/share/themes
+│       ├── index.theme
+│       └── gtk-3.0/gtk.css
 └── config/
     ├── sxwmrc                    # sxwm config (keybinds, layout, colours)
     ├── sxbarc                    # sxbar config (modules, colours, icons)
+    ├── gcfs/
+    │   └── vaults.conf            # gcfs vault list (ships with the commented example only)
+    ├── geany/
+    │   ├── geany.conf             # Geany prefs (Nerd Font, Gruvbox editor colour scheme)
+    │   └── colorschemes/
+    │       └── gruvbox.conf       # Gruvbox editor colour scheme
     └── st/
         └── config.def.h           # st config (font, colours, alpha, cursor, keybinds)
 ```
@@ -304,6 +328,17 @@ Installed to `~/.local/bin` by `install.sh` and wired into `sxwmrc`/`~/.xinitrc`
 - **`screensaver`** — runs random [TTE](https://github.com/ChrisBuilds/terminaltexteffects)
   (terminal text effects) animations in a loop over the ASCII art in
   `screensaver.txt`.
+- **`status`** — read-only overview: which WM-stack processes and systemd
+  services (bluetooth, pipewire, pipewire-pulse, wireplumber) are running,
+  network/wifi state, connected Bluetooth devices, default audio sink,
+  battery/AC state, DPMS/lid/lidsuspend state, and currently playing media.
+- **`gcfs [status|mount|umount|mount-all|umount-all|add|init|edit]`** — minimal
+  [gocryptfs](https://github.com/rfjakob/gocryptfs) vault manager. Reads a
+  `name|cipherdir|mountpoint` list from `~/.config/gcfs/vaults.conf`, shows each
+  vault's mount state, and mounts/unmounts by name or number (run with no args
+  for an interactive menu). Lets `gocryptfs` do its own password prompt, so no
+  secret passes through the script. Needs `gocryptfs` and `fusermount` (`fuse3`),
+  both pulled in by `install.sh`.

 ## Screensaver

@@ -312,6 +347,32 @@ The screensaver renders `bin/screensaver.txt` through random
 animations (installed via `pipx` during setup) inside a fullscreen st
 window, and exits the instant you touch the mouse or keyboard.

+## Geany
+
+Geany is set up with a Gruvbox dark theme and JetBrainsMono Nerd Font, covering
+both the editor and the surrounding GTK chrome (menus, toolbar, sidebar):
+
+- **[`config/geany/geany.conf`](config/geany/geany.conf)** — editor/tagbar/msgwin
+  fonts set to JetBrainsMono Nerd Font, `color_scheme=gruvbox.conf`.
+- **[`config/geany/colorschemes/gruvbox.conf`](config/geany/colorschemes/gruvbox.conf)**
+  — Gruvbox dark syntax colours for the editor itself.
+- **[`themes/Gruvbox-Dark/`](themes/Gruvbox-Dark/)** — a GTK3 theme (Adwaita-dark's
+  structure with Gruvbox colours layered on via `@define-color`) for the rest of
+  the window, since Geany's own `color_scheme` only styles the editor pane, not
+  the menus/toolbar/sidebar.
+- **[`bin/geany`](bin/geany)** — wrapper installed to `~/.local/bin/geany` that
+  launches the real `/usr/bin/geany` with `GTK_THEME=Gruvbox-Dark`. A shell
+  alias isn't enough here: dmenu/sxwm never source `~/.bashrc`, but `~/.local/bin`
+  is first in `PATH` (see `~/.xinitrc`), so the wrapper shadows the system binary
+  for every launch path (dmenu, `.desktop`, terminal).
+- **[`applications/geany.desktop`](applications/geany.desktop)** — copy of the
+  system `.desktop` entry with `Exec=env GTK_THEME=Gruvbox-Dark geany %F`, so
+  app-launcher entries that read `.desktop` files (not just `$PATH`) pick up the
+  theme too.
+
+The theme is scoped to Geany only — the system GTK theme (Adwaita) is untouched,
+so other GTK apps aren't affected.
+
 ## Wallpaper

 <div align="center">
diff --git a/applications/geany.desktop b/applications/geany.desktop
new file mode 100644
index 0000000..f885e6e
--- /dev/null
+++ b/applications/geany.desktop
@@ -0,0 +1,171 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name[ar]=Geany
+Name[ast]=Geany
+Name[be]=Geany
+Name[bg]=Geany
+Name[ca]=Geany
+Name[cs]=Geany
+Name[da]=Geany
+Name[de]=Geany
+Name[el]=Geany
+Name[en_GB]=Geany
+Name[es]=Geany
+Name[et]=Geany
+Name[eu]=Geany
+Name[fa]=Geany
+Name[fi]=Geany
+Name[fr]=Geany
+Name[gl]=Geany
+Name[he]=Geany
+Name[hi]=जीनि
+Name[hu]=Geany
+Name[id]=Geany
+Name[ie]=Geany
+Name[it]=Geany
+Name[ja]=Geany
+Name[kk]=Geany
+Name[ko]=지니
+Name[ku]=جێنی
+Name[lb]=Geany
+Name[lt]=Geany
+Name[lv]=Geany
+Name[mn]=Жиени
+Name[nl]=Geany
+Name[nn]=Geany
+Name[pl]=Geany
+Name[pt]=Geany
+Name[pt_BR]=Geany
+Name[ro]=Geany
+Name[ru]=Geany
+Name[sk]=Geany
+Name[sl]=Geany
+Name[sr]=Geany
+Name[sv]=Geany
+Name[tr]=Geany
+Name[vi]=Geany
+Name[zh_CN]=Geany
+Name[zh_TW]=Geany
+Name=Geany
+GenericName[ar]=بيئة التطوير المتكاملة
+GenericName[ast]=Entornu Integráu de Desarrollu
+GenericName[be]=Інтэграванае асяроддзе распрацоўкі
+GenericName[bg]=Вградена среда за разработка
+GenericName[ca]=Entorn integrat de desenvolupament
+GenericName[cs]=Integrované vývojové prostředí
+GenericName[de]=Integrierte Entwicklungsumgebung
+GenericName[el]=Ενιαίο Περιβάλλον Ανάπτυξης
+GenericName[en_GB]=Integrated Development Environment
+GenericName[es]=Entorno de desarrollo integrado
+GenericName[et]=Integreeritud arenduskeskkond
+GenericName[eu]=Garapen ingurune integratua
+GenericName[fa]=محیط توسعه ی نرم افزار
+GenericName[fi]=Integroitu ohjelmointiympäristö
+GenericName[fr]=Environnement de Développement Intégré
+GenericName[gl]=Contorno integrado de desenvolvemento
+GenericName[he]=סביבת פיתוח משולבת
+GenericName[hi]=एकीकृत विकास वातावरण
+GenericName[hu]=Integrált fejlesztői környezet
+GenericName[id]=Integrated Development Environment
+GenericName[ie]=Integrat ambiente de developation
+GenericName[it]=Ambiente di sviluppo integrato
+GenericName[ja]=統合開発環境
+GenericName[kk]=Интеграцияланған өндіру ортасы
+GenericName[ko]=통합 개발 환경
+GenericName[ku]=دەوروبەری پەرەپێدانی تەواوکاری
+GenericName[lb]=Integréiert Entwécklungsumgebung
+GenericName[lt]=Integruota kūrimo aplinka
+GenericName[lv]=Integrētā izstrādes vide
+GenericName[nl]=Geïntegreerde ontwikkelomgeving
+GenericName[nn]=Integrert utviklingsmiljø
+GenericName[pl]=Zintegrowane środowisko programistyczne
+GenericName[pt]=Ambiente integrado de desenvolvimento
+GenericName[pt_BR]=Ambiente de Desenvolvimento Integrado
+GenericName[ro]=Mediu de dezvoltare
+GenericName[ru]=Интегрированная среда разработки
+GenericName[sk]=Integrované vývojové prostredie
+GenericName[sl]=Vdelano razvojno okolje
+GenericName[sr]=Интегрисано развојно окружење
+GenericName[sv]=Integrerad utvecklingsmiljö
+GenericName[tr]=Entegre Geliştirme Ortamı
+GenericName[uk]=Інтегроване середовище розробки
+GenericName[vi]=Môi trường Phát triển Hợp nhất
+GenericName[zh_CN]=集成开发环境
+GenericName[zh_TW]=整合開發環境
+GenericName=Integrated Development Environment
+Comment[ar]=بيئة تطوير خفيفة وسريع تستخدم مكتبات GTK+
+Comment[ast]=Un IDE rápidu y llixeru basáu en GTK+
+Comment[be]=Хуткае і легкаважнае асяроддзе распрацоўкі, выкарыстоўваючае GTK+
+Comment[ca]=Un IDE ràpid i lleuger fet amb GTK+
+Comment[cs]=Rychlé a lehké IDE pro GTK+
+Comment[de]=Eine kleine und schnelle Entwicklungsumgebung für GTK+
+Comment[el]=Γρήγορο και ελαφρύ GTK+ IDE
+Comment[en_GB]=A fast and lightweight IDE using GTK+
+Comment[es]=Un IDE rápido y ligero para GTK+
+Comment[et]=Väike ja kiire IDE GTK+ baasil
+Comment[eu]=GTK+ erabiltzen duen IDE azkar eta arina
+Comment[fa]=A fast and lightweight IDE using GTK+
+Comment[fi]=Nopea ja kevyt GTK+-pohjainen ohjelmointiympäristö
+Comment[fr]=Un EDI rapide et léger utilisant GTK+
+Comment[gl]=Un IDE rápido e lixeiro empregando GTK+
+Comment[he]=סביבת פיתוח משולבת קטנה וקלת משקל העושה שימוש ב־GTK+
+Comment[hi]=एक तेज और हलका GTK+ का उपयोग कर आईडीई
+Comment[hu]=Gyors és pehelykönnyű IDE GTK+ alapokon
+Comment[id]=Sebuah IDE yang cepat dan ringan menggunakan GTK+
+Comment[ie]=Un rapid e minimalistic IDE que usa GTK+
+Comment[it]=Un IDE veloce e leggero che usa GTK+
+Comment[ja]=GTK+ を用いた高速で軽量な IDE
+Comment[kk]=GTK+ негізіндегі жылдам әрі жеңіл өндіру ортасы
+Comment[ko]=빠르고 가벼운 GTK+ 기반의 통합개발환경
+Comment[ku]=بيئة تطوير خفيفة وسريع تستخدم مكتبات GTK+
+Comment[lb]=En klenge an schnelle IDE fir GTK+
+Comment[lt]=Greita ir supaprastinta kūrimo aplinka naudojanti GTK+
+Comment[lv]=Ātra un viegla IDE, lietojoša GTK+
+Comment[nl]=Een snelle en lichtgewicht IDE, gebaseerd op GTK+
+Comment[nn]=Eit raskt og lett IDE som nyttar GTK+
+Comment[pl]=Szybkie i lekkie środowisko programistyczne oparte na GTK+
+Comment[pt]=Um IDE rápido e leve, usando GTK+
+Comment[pt_BR]=Um IDE rápida e leve usando GTK+
+Comment[ro]=Un IDE rapid folosind GTK+
+Comment[ru]=Быстрая и легковесная среда разработки, использующая GTK+
+Comment[sk]=Rýchle a nenáročné IDE pre GTK+
+Comment[sl]=Hitro in lahkotno vdelano razvojno okolje z uporabo GTK+
+Comment[sr]=Брзо и лагано GTK развојно окружење
+Comment[sv]=Ett snabbt och lättviktigt IDE som använder GTK+
+Comment[tr]=GTK+ kullanan hızlı ve hafif bir IDE
+Comment[uk]=Швидке та компактне середовище розробки (IDE) на основі GTK+
+Comment[vi]=Một IDE nhanh và nhẹ nhàng dùng GTK+
+Comment[zh_CN]=GTK+ 编写的轻快的 IDE
+Comment[zh_TW]=一個快速且輕巧的 GTK+ 整合開發環境
+Comment=A fast and lightweight IDE using GTK+
+Exec=env GTK_THEME=Gruvbox-Dark geany %F
+Icon=geany
+Terminal=false
+Categories=GTK;Development;IDE;TextEditor;
+MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
+StartupNotify=true
+Keywords[ca]=Text;Editor;
+Keywords[de]=Text;Editor;
+Keywords[el]=Κείμενο;Επεξεργαστής;
+Keywords[es]=Texto;Editor;
+Keywords[et]=Tekst;Redaktor;
+Keywords[fr]=Texte;Éditeur;
+Keywords[hu]=Szöveg;Szerkesztő;
+Keywords[id]=Text;Editor
+Keywords[ie]=textu;redactor;
+Keywords[it]=Testo;Editor;
+Keywords[ja]=Text;Editor;
+Keywords[kk]=Мәтін;Түзеткіш;
+Keywords[lt]=Teksto;Redaktorius;
+Keywords[lv]=Teksts;Redaktors;
+Keywords[nl]=Tekst;Editor;
+Keywords[pt]=Texto;Editor;
+Keywords[ru]=Текст;Редактор;
+Keywords[sk]=Text;Editor;
+Keywords[sv]=Text;Editor;
+Keywords[tr]=Metin;Düzenleyici;
+Keywords[uk]=Текст;Редактор;
+Keywords[zh_CN]=文本;编辑器;
+Keywords[zh_TW]=文字;編輯器;
+Keywords=Text;Editor;
diff --git a/bin/gcfs b/bin/gcfs
new file mode 100755
index 0000000..bd4381c
--- /dev/null
+++ b/bin/gcfs
@@ -0,0 +1,214 @@
+#!/usr/bin/env bash
+#
+# gcfs — a minimal gocryptfs vault manager
+#
+# Config-driven, monochrome, dependency-light. Lets gocryptfs handle the
+# password prompt itself, so no secret ever touches this script.
+#
+# Config:  $XDG_CONFIG_HOME/gcfs/vaults.conf
+# Format:  name|cipherdir|mountpoint      (# comments and blank lines ignored)
+#
+# Usage:   gcfs                 interactive menu
+#          gcfs status          show all vaults and their state (default)
+#          gcfs mount   <name>  mount a vault (name or number)
+#          gcfs umount  <name>  unmount a vault (name or number)
+#          gcfs mount-all       mount every vault
+#          gcfs umount-all      unmount every mounted vault
+#          gcfs add             register a new vault interactively
+#          gcfs init            create a fresh gocryptfs cipherdir
+#          gcfs edit            open the config in $EDITOR
+#          gcfs help            this text
+
+set -euo pipefail
+
+CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gcfs"
+CONFIG_FILE="$CONFIG_DIR/vaults.conf"
+
+# ---- styling (degrades gracefully with no tty) --------------------------
+if [[ -t 1 ]]; then
+  B=$(tput bold    2>/dev/null || true)
+  D=$(tput dim     2>/dev/null || true)
+  U=$(tput smul    2>/dev/null || true)
+  R=$(tput sgr0    2>/dev/null || true)
+else
+  B=""; D=""; U=""; R=""
+fi
+
+ON="●"    # mounted
+OFF="○"   # unmounted
+BAD="✗"   # broken (missing cipherdir)
+
+# ---- output helpers -----------------------------------------------------
+msg()  { printf '%s\n' "$*"; }
+info() { printf '%s\n' "${D}$*${R}"; }
+warn() { printf '%s\n' "${B}!${R} $*" >&2; }
+die()  { printf '%s\n' "${B}✗${R} $*" >&2; exit 1; }
+
+# ---- dependency + config bootstrap --------------------------------------
+need() { command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"; }
+need gocryptfs
+need fusermount
+
+ensure_config() {
+  [[ -f "$CONFIG_FILE" ]] && return
+  mkdir -p "$CONFIG_DIR"
+  cat > "$CONFIG_FILE" <<'EOF'
+# gcfs vaults — one per line:  name|cipherdir|mountpoint
+# leading ~/ is expanded to your home. lines starting with # are ignored.
+#
+# example:
+# docs|~/.vaults/docs|~/vault/docs
+EOF
+  info "created $CONFIG_FILE — add a vault with 'gcfs add'"
+}
+
+# expand only a leading ~/ (safe, no eval)
+expand() { case "$1" in "~/"*) printf '%s\n' "$HOME/${1#\~/}";; *) printf '%s\n' "$1";; esac; }
+
+# ---- load vaults into parallel arrays -----------------------------------
+declare -a NAMES CIPHERS MOUNTS
+load_vaults() {
+  NAMES=(); CIPHERS=(); MOUNTS=()
+  local line name cipher mount
+  while IFS='|' read -r name cipher mount || [[ -n "$name" ]]; do
+    [[ -z "${name// }" ]] && continue
+    [[ "${name#\#}" != "$name" ]] && continue
+    name="${name## }"; name="${name%% }"
+    NAMES+=("$name")
+    CIPHERS+=("$(expand "${cipher## }")")
+    MOUNTS+=("$(expand "${mount## }")")
+  done < "$CONFIG_FILE"
+}
+
+is_mounted() { mountpoint -q -- "$1" 2>/dev/null; }
+
+# resolve a user token (name or 1-based index) to an array index
+resolve() {
+  local tok="$1" i
+  if [[ "$tok" =~ ^[0-9]+$ ]]; then
+    (( tok >= 1 && tok <= ${#NAMES[@]} )) || die "no vault #$tok"
+    printf '%s\n' "$(( tok - 1 ))"; return
+  fi
+  for i in "${!NAMES[@]}"; do
+    [[ "${NAMES[$i]}" == "$tok" ]] && { printf '%s\n' "$i"; return; }
+  done
+  die "no vault named '$tok'"
+}
+
+# ---- commands -----------------------------------------------------------
+cmd_status() {
+  load_vaults
+  (( ${#NAMES[@]} )) || { info "no vaults configured — 'gcfs add'"; return; }
+
+  local w=4 i
+  for i in "${!NAMES[@]}"; do (( ${#NAMES[$i]} > w )) && w=${#NAMES[$i]}; done
+
+  printf '%s\n' "${B}  # $(printf "%-${w}s" NAME)  MOUNTPOINT${R}"
+  for i in "${!NAMES[@]}"; do
+    local mark clr
+    if [[ ! -f "${CIPHERS[$i]}/gocryptfs.conf" ]]; then
+      mark="$BAD"; clr="$D"
+    elif is_mounted "${MOUNTS[$i]}"; then
+      mark="$ON"; clr="$B"
+    else
+      mark="$OFF"; clr="$D"
+    fi
+    printf '%s %2d %s %s  %s%s\n' \
+      "$clr$mark$R" "$((i+1))" "$B" \
+      "$(printf "%-${w}s" "${NAMES[$i]}")" \
+      "${D}${MOUNTS[$i]}${R}" "$R"
+  done
+}
+
+cmd_mount() {
+  local i; i=$(resolve "$1"); load_vaults
+  local cipher="${CIPHERS[$i]}" mount="${MOUNTS[$i]}" name="${NAMES[$i]}"
+  [[ -f "$cipher/gocryptfs.conf" ]] || die "'$name': no gocryptfs.conf in $cipher"
+  if is_mounted "$mount"; then info "'$name' already mounted"; return; fi
+  mkdir -p -- "$mount"
+  msg "${B}unlocking${R} $name → ${D}$mount${R}"
+  if gocryptfs -- "$cipher" "$mount"; then
+    msg "$ON mounted $name"
+  else
+    die "failed to mount $name"
+  fi
+}
+
+cmd_umount() {
+  local i; i=$(resolve "$1"); load_vaults
+  local mount="${MOUNTS[$i]}" name="${NAMES[$i]}"
+  is_mounted "$mount" || { info "'$name' not mounted"; return; }
+  if fusermount -u -- "$mount" 2>/dev/null; then
+    msg "$OFF unmounted $name"
+  else
+    warn "'$name' is busy — close open files, or force with: fusermount -uz -- '$mount'"
+    return 1
+  fi
+}
+
+cmd_mount_all()  { load_vaults; local i; for i in "${!NAMES[@]}"; do cmd_mount  "$((i+1))" || true; done; }
+cmd_umount_all() { load_vaults; local i; for i in "${!NAMES[@]}"; do cmd_umount "$((i+1))" || true; done; }
+
+cmd_add() {
+  local name cipher mount
+  read -rp "name:        " name
+  read -rp "cipherdir:   " cipher
+  read -rp "mountpoint:  " mount
+  [[ -n "$name" && -n "$cipher" && -n "$mount" ]] || die "all fields required"
+  printf '%s|%s|%s\n' "$name" "$cipher" "$mount" >> "$CONFIG_FILE"
+  msg "$OFF added $name"
+}
+
+cmd_init() {
+  local cipher="${1:-}"
+  [[ -n "$cipher" ]] || read -rp "new cipherdir: " cipher
+  cipher="$(expand "$cipher")"
+  mkdir -p -- "$cipher"
+  gocryptfs -init -- "$cipher"
+  info "initialised — register it with 'gcfs add' (cipherdir: $cipher)"
+}
+
+cmd_edit() { "${EDITOR:-vi}" "$CONFIG_FILE"; }
+
+cmd_help() { sed -n '3,29p' "$0" | sed 's/^# \{0,1\}//'; }
+
+# ---- interactive menu ---------------------------------------------------
+menu() {
+  while :; do
+    printf '\n'
+    cmd_status
+    printf '\n%s[number]%s toggle  %s[a]%sdd  %s[i]%snit  %s[q]%suit  ' \
+      "$B" "$R" "$B" "$R" "$B" "$R" "$B" "$R"
+    read -r choice || { printf '\n'; break; }
+    case "$choice" in
+      q|Q|"") break ;;
+      a|A)    cmd_add ;;
+      i|I)    cmd_init ;;
+      *)
+        load_vaults
+        idx=$(resolve "$choice") || continue
+        if is_mounted "${MOUNTS[$idx]}"; then
+          cmd_umount "$choice" || true
+        else
+          cmd_mount "$choice" || true
+        fi
+        ;;
+    esac
+  done
+}
+
+# ---- dispatch -----------------------------------------------------------
+ensure_config
+case "${1:-menu}" in
+  menu)               menu ;;
+  status|st|ls)       cmd_status ;;
+  mount|m)            shift; [[ $# -ge 1 ]] || die "usage: gcfs mount <name|#>"; cmd_mount "$1" ;;
+  umount|unmount|u)   shift; [[ $# -ge 1 ]] || die "usage: gcfs umount <name|#>"; cmd_umount "$1" ;;
+  mount-all)          cmd_mount_all ;;
+  umount-all)         cmd_umount_all ;;
+  add)                cmd_add ;;
+  init)               shift; cmd_init "${1:-}" ;;
+  edit)               cmd_edit ;;
+  help|-h|--help)     cmd_help ;;
+  *)                  die "unknown command '$1' — try 'gcfs help'" ;;
+esac
diff --git a/bin/geany b/bin/geany
new file mode 100755
index 0000000..135960c
--- /dev/null
+++ b/bin/geany
@@ -0,0 +1,8 @@
+#!/bin/bash
+# geany
+# Wrapper so Geany always launches with the Gruvbox-Dark GTK theme,
+# regardless of launcher (dmenu, .desktop file, terminal). Needed because
+# dmenu/sxwm resolve `geany` via PATH and never source .bashrc, so a shell
+# alias alone doesn't reach it - ~/.local/bin is first in PATH (see
+# .xinitrc), so this wrapper shadows /usr/bin/geany for every launch path.
+exec env GTK_THEME=Gruvbox-Dark /usr/bin/geany "$@"
diff --git a/bin/status b/bin/status
new file mode 100755
index 0000000..0f12982
--- /dev/null
+++ b/bin/status
@@ -0,0 +1,170 @@
+#!/bin/bash
+# status
+# Snabb översikt över skrivbordet: WM-stacken, bakgrundsdaemons, systemd-
+# tjänster, nätverk, bluetooth, ljud, batteri och skärm/lock-läge. Rör inget,
+# bara läser av och skriver ut.
+
+if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors 2>/dev/null)" -ge 8 ]; then
+    C_HEAD=$(tput bold)$(tput setaf 3)   # gul
+    C_OK=$(tput setaf 2)                 # grön
+    C_BAD=$(tput setaf 1)                # röd
+    C_DIM=$(tput dim)
+    C_RESET=$(tput sgr0)
+else
+    C_HEAD=""; C_OK=""; C_BAD=""; C_DIM=""; C_RESET=""
+fi
+
+header() { printf "\n%s== %s ==%s\n" "$C_HEAD" "$1" "$C_RESET"; }
+ok()      { printf "  %s✔%s %s\n" "$C_OK" "$C_RESET" "$1"; }
+bad()     { printf "  %s✘%s %s\n" "$C_BAD" "$C_RESET" "$1"; }
+line()    { printf "  %s\n" "$1"; }
+
+proc_status() {
+    local label="$1" pattern="$2"
+    local pid
+    # pgrep -x trunkerar mot /proc/[pid]/comm (max 15 tecken), vilket ger
+    # falska negativ för längre namn som "lid-display-watch". Matchar
+    # istället mot hela kommandoraden med en ord-gräns runt namnet.
+    pid=$(pgrep -f "(^|/)${pattern}([[:space:]]|\$)" | head -1)
+    if [ -n "$pid" ]; then
+        ok "$label (pid $pid)"
+    else
+        bad "$label - körs inte"
+    fi
+}
+
+strip_ansi() { sed -e 's/\x1b\[[0-9;]*m//g'; }
+
+service_status() {
+    local scope="$1" name="$2"
+    local -a scope_args=()
+    [ -n "$scope" ] && scope_args=("$scope")
+    if systemctl "${scope_args[@]}" is-active --quiet "$name" 2>/dev/null; then
+        ok "$name"
+    else
+        bad "$name - $(systemctl "${scope_args[@]}" is-active "$name" 2>/dev/null)"
+    fi
+}
+
+header "System"
+line "$(hostname) - $(uptime -p 2>/dev/null | sed 's/^up //') - load: $(cut -d' ' -f1-3 /proc/loadavg)"
+line "kernel: $(uname -r)"
+
+header "Skrivbord"
+proc_status "Xorg"            Xorg
+proc_status "sxwm"            sxwm
+proc_status "sxbar"           sxbar
+proc_status "screensaverd"    screensaverd
+proc_status "lid-display-watch" lid-display-watch
+
+header "Tjänster"
+service_status "" bluetooth
+service_status "--user" pipewire
+service_status "--user" pipewire-pulse
+service_status "--user" wireplumber
+
+header "Nätverk"
+while read -r ifc state addr _; do
+    [ "$ifc" = "lo" ] && continue
+    if [ "$state" = "UP" ]; then
+        ok "$ifc: $addr"
+    else
+        bad "$ifc: nere"
+    fi
+done < <(ip -br addr show 2>/dev/null)
+if command -v iwctl >/dev/null 2>&1; then
+    while read -r wifi_dev; do
+        [ -n "$wifi_dev" ] || continue
+        ssid=$(iwctl station "$wifi_dev" show 2>/dev/null | strip_ansi | sed -n 's/^[[:space:]]*Connected network[[:space:]]\+//p' | sed 's/[[:space:]]*$//')
+        [ -n "$ssid" ] && line "wifi ($wifi_dev): ansluten till \"$ssid\""
+    done < <(iwctl station list 2>/dev/null | strip_ansi | awk '/connected/{print $1}')
+fi
+
+header "Bluetooth"
+if command -v bluetoothctl >/dev/null 2>&1; then
+    powered=$(bluetoothctl show 2>/dev/null | awk -F': ' '/^\tPowered:/{print $2}')
+    if [ "$powered" = "yes" ]; then
+        ok "adapter på"
+    else
+        bad "adapter av"
+    fi
+    connected=$(bluetoothctl devices Connected 2>/dev/null)
+    if [ -n "$connected" ]; then
+        while read -r _ mac name; do
+            ok "ansluten: $name ($mac)"
+        done <<< "$connected"
+    else
+        line "${C_DIM}inga anslutna enheter${C_RESET}"
+    fi
+else
+    bad "bluetoothctl saknas"
+fi
+
+header "Ljud"
+if command -v wpctl >/dev/null 2>&1; then
+    sink_vol=$(wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null)
+    if [ -n "$sink_vol" ]; then
+        if echo "$sink_vol" | grep -q MUTED; then
+            bad "output: $sink_vol"
+        else
+            ok "output: $sink_vol"
+        fi
+    else
+        bad "ingen default-output"
+    fi
+else
+    bad "wpctl saknas"
+fi
+
+header "Batteri"
+for supply in /sys/class/power_supply/*/; do
+    name=$(basename "$supply")
+    type=$(cat "${supply}type" 2>/dev/null)
+    case "$type" in
+        Battery)
+            cap=$(cat "${supply}capacity" 2>/dev/null)
+            st=$(cat "${supply}status" 2>/dev/null)
+            if [ "$st" = "Charging" ] || { [ -n "$cap" ] && [ "$cap" -gt 20 ] 2>/dev/null; }; then
+                ok "$name: $cap% ($st)"
+            else
+                bad "$name: $cap% ($st)"
+            fi
+            ;;
+        Mains|USB)
+            online=$(cat "${supply}online" 2>/dev/null)
+            if [ "$online" = "1" ]; then
+                line "$name: ansluten"
+            else
+                line "$name: inte ansluten"
+            fi
+            ;;
+    esac
+done
+
+header "Skärm / lock"
+dpms=$(xset q 2>/dev/null | awk '/Monitor is/{print $NF}')
+[ -n "$dpms" ] && line "DPMS: $dpms"
+lid=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \
+    org.freedesktop.login1.Manager LidClosed 2>/dev/null | awk '{print $2}')
+case "$lid" in
+    true)  line "lock: stängt" ;;
+    false) line "lock: öppet" ;;
+esac
+handle=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \
+    org.freedesktop.login1.Manager HandleLidSwitch 2>/dev/null | cut -d'"' -f2)
+[ -n "$handle" ] && line "HandleLidSwitch: $handle"
+if command -v lidsuspend >/dev/null 2>&1; then
+    line "lidsuspend: $(lidsuspend status 2>/dev/null)"
+fi
+pause_file="${XDG_RUNTIME_DIR:-/tmp}/screensaver-paused"
+[ -e "$pause_file" ] && line "screensaver: pausad"
+
+if command -v playerctl >/dev/null 2>&1; then
+    pstatus=$(playerctl status 2>/dev/null)
+    if [ "$pstatus" = "Playing" ]; then
+        header "Media"
+        ok "$(playerctl metadata --format '{{artist}} - {{title}}' 2>/dev/null)"
+    fi
+fi
+
+echo
diff --git a/config/gcfs/vaults.conf b/config/gcfs/vaults.conf
new file mode 100644
index 0000000..4f7ef32
--- /dev/null
+++ b/config/gcfs/vaults.conf
@@ -0,0 +1,5 @@
+# gcfs vaults — one per line:  name|cipherdir|mountpoint
+# leading ~/ is expanded to your home. lines starting with # are ignored.
+#
+# example:
+# docs|~/.vaults/docs|~/vault/docs
diff --git a/config/geany/colorschemes/gruvbox.conf b/config/geany/colorschemes/gruvbox.conf
new file mode 100644
index 0000000..272b88a
--- /dev/null
+++ b/config/geany/colorschemes/gruvbox.conf
@@ -0,0 +1,95 @@
+[theme_info]
+name=Gruvbox
+description=Gruvbox dark, retro groove color scheme for Geany
+version=1.0
+author=
+url=
+
+[named_styles]
+
+default=0xEBDBB2;0x282828;false;false
+error=0xFB4934;0x282828;true;false
+
+# Editor styles
+#-------------------------------------------------------------------------------
+
+selection=0xEBDBB2;0x504945;false;true
+current_line=0xEBDBB2;0x3C3836;true
+brace_good=0xB8BB26;0x3C3836;true;false
+brace_bad=0xFB4934;0x3C3836;true;false
+margin_line_number=0xA89984;0x282828
+margin_folding=0xEBDBB2;0x3C3836
+fold_symbol_highlight=0xFBF1C7
+indent_guide=0x504945
+caret=0xFBF1C7;0x282828;false
+marker_line=0xEBDBB2;0x504945
+marker_search=0x282828;0xFABD2F
+marker_mark=0x282828;0xB8BB26
+call_tips=0xEBDBB2;0x3C3836;false;false
+white_space=0x665C54;0x282828;true;false
+
+# Programming languages
+#-------------------------------------------------------------------------------
+
+comment=0x928374;;false;true
+comment_doc=0x928374;;false;true
+comment_line=comment
+comment_line_doc=comment_doc
+comment_doc_keyword=comment_doc,bold
+comment_doc_keyword_error=comment_doc,italic
+
+number=0xD3869B
+number_1=number
+number_2=number_1
+
+type=0xFABD2F;;false
+class=type
+function=0xB8BB26;;false
+parameter=0x83A598
+
+keyword=0xFB4934;;true
+keyword_1=keyword
+keyword_2=0xD65D0E;;true
+keyword_3=keyword_1
+keyword_4=keyword_1
+
+identifier=default
+identifier_1=identifier
+identifier_2=identifier_1
+identifier_3=identifier_1
+identifier_4=identifier_1
+
+string=0xB8BB26
+string_1=string
+string_2=string_1
+string_3=default
+string_4=default
+string_eol=0xFB4934;0x3C3836
+character=0xD3869B
+backticks=string_2
+here_doc=string_2
+
+label=0x8EC07C,bold
+preprocessor=0x8EC07C
+regex=0xFE8019
+operator=0xFE8019
+decorator=0xFABD2F,bold
+other=0x83A598
+
+# Markup-type languages
+#-------------------------------------------------------------------------------
+
+tag=0xFB4934
+tag_unknown=tag,bold
+tag_end=tag,bold
+attribute=0xFABD2F
+attribute_unknown=attribute,bold
+value=string_1
+entity=default
+
+# Diff
+#-------------------------------------------------------------------------------
+
+line_added=0xB8BB26
+line_removed=0xFB4934
+line_changed=0xFABD2F
diff --git a/config/geany/geany.conf b/config/geany/geany.conf
new file mode 100644
index 0000000..9b46966
--- /dev/null
+++ b/config/geany/geany.conf
@@ -0,0 +1,201 @@
+[geany]
+pref_main_load_session=true
+pref_main_project_file_in_basedir=true
+pref_main_save_winpos=true
+pref_main_save_wingeom=true
+pref_main_confirm_exit=false
+pref_main_suppress_status_messages=false
+switch_msgwin_pages=false
+beep_on_errors=true
+auto_focus=false
+sidebar_symbol_visible=true
+sidebar_openfiles_visible=true
+editor_font=JetBrainsMono Nerd Font Mono 11
+tagbar_font=JetBrainsMono Nerd Font 9
+msgwin_font=JetBrainsMono Nerd Font Mono 9
+show_notebook_tabs=true
+show_tab_cross=true
+tab_order_ltr=true
+tab_order_beside=false
+tab_pos_editor=2
+tab_pos_msgwin=0
+tab_label_length=1000
+show_indent_guide=false
+show_white_space=false
+show_line_endings=false
+show_line_endings_only_when_differ=false
+show_markers_margin=true
+show_linenumber_margin=true
+long_line_enabled=true
+long_line_type=0
+long_line_column=72
+long_line_color=#C2EBC2
+symbolcompletion_max_height=10
+symbolcompletion_min_chars=4
+use_folding=true
+unfold_all_children=false
+use_indicators=true
+line_wrapping=false
+auto_close_xml_tags=true
+complete_snippets=true
+auto_complete_symbols=true
+pref_editor_disable_dnd=false
+pref_editor_smart_home_key=true
+pref_editor_newline_strip=false
+line_break_column=72
+auto_continue_multiline=true
+comment_toggle_mark=~
+scroll_stop_at_last_line=true
+autoclose_chars=0
+pref_editor_default_new_encoding=UTF-8
+pref_editor_default_open_encoding=none
+default_eol_character=2
+pref_editor_new_line=true
+pref_editor_ensure_convert_line_endings=false
+pref_editor_replace_tabs=false
+pref_editor_trail_space=false
+pref_toolbar_show=true
+pref_toolbar_append_to_menu=false
+pref_toolbar_use_gtk_default_style=true
+pref_toolbar_use_gtk_default_icon=true
+pref_toolbar_icon_style=0
+pref_toolbar_icon_size=0
+pref_template_developer=
+pref_template_company=
+pref_template_mail=mrfox@foxy
+pref_template_initial=
+pref_template_version=1.0
+pref_template_year=%Y
+pref_template_date=%Y-%m-%d
+pref_template_datetime=%d.%m.%Y %H:%M:%S %Z
+context_action_cmd=
+sidebar_visible=true
+statusbar_visible=true
+msgwindow_visible=false
+fullscreen=false
+symbols_group_by_type=true
+color_picker_palette=
+scribble_text=Skriv vad du vill här, använd den som ett klotterplank
+scribble_pos=54
+custom_date_format=
+default_open_path=
+cmdline_new_files=true
+notebook_double_click_hides_widgets=false
+tab_close_switch_to_mru=false
+tab_pos_sidebar=2
+openfiles_path_mode=2
+sidebar_pos=0
+symbols_sort_mode=0
+msgwin_orientation=1
+highlighting_invert_all=false
+pref_main_search_use_current_word=true
+check_detect_indent=false
+detect_indent_width=false
+use_tab_to_indent=true
+backspace_unindent=true
+pref_editor_tab_width=4
+indent_mode=2
+indent_type=1
+virtualspace=1
+change_history_markers=false
+change_history_indicators=false
+autocomplete_doc_words=false
+completion_drops_rest_of_word=false
+autocompletion_max_entries=30
+autocompletion_update_freq=250
+color_scheme=gruvbox.conf
+scroll_lines_around_cursor=0
+mru_length=10
+disk_check_timeout=30
+show_editor_scrollbars=true
+brace_match_ltgt=false
+use_gtk_word_boundaries=true
+complete_snippets_whilst_editing=false
+indent_hard_tab_width=8
+editor_ime_interaction=0
+use_atomic_file_saving=false
+gio_unsafe_save_backup=false
+use_gio_unsafe_file_saving=true
+keep_edit_history_on_reload=true
+show_keep_edit_history_on_reload_msg=true
+reload_clean_doc_on_file_change=false
+save_config_on_file_change=true
+extract_filetype_regex=-\\*-\\s*([^\\s]+)\\s*-\\*-
+allow_always_save=false
+find_selection_type=0
+replace_and_find_by_default=true
+show_symbol_list_expanders=true
+compiler_tab_autoscroll=true
+statusbar_template=line: %l / %L	 col: %c	 sel: %s	 %w      %t      %mEOL: %M      encoding: %e      filetype: %f      scope: %S
+new_document_after_close=false
+msgwin_status_visible=true
+msgwin_compiler_visible=true
+msgwin_messages_visible=true
+msgwin_scribble_visible=true
+warn_on_project_close=true
+
+[tools]
+terminal_cmd=x-terminal-emulator -e "/bin/sh %c"
+browser_cmd=
+grep_cmd=grep
+
+[printing]
+print_cmd=
+use_gtk_printing=true
+print_line_numbers=true
+print_page_numbers=true
+print_page_header=true
+page_header_basename=false
+page_header_datefmt=%c
+
+[VTE]
+load_vte=true
+font=JetBrainsMono Nerd Font Mono 11
+scroll_on_key=true
+scroll_on_out=true
+enable_bash_keys=true
+ignore_menu_bar_accel=false
+follow_path=false
+run_in_vte=false
+skip_run_script=false
+cursor_blinks=false
+scrollback_lines=500
+shell=/bin/bash
+colour_fore=#EBDBB2
+colour_back=#282828
+send_cmd_prefix=
+send_selection_unsafe=false
+
+[build-menu]
+number_ft_menu_items=0
+number_non_ft_menu_items=0
+number_exec_menu_items=0
+
+[search]
+pref_search_hide_find_dialog=false
+pref_search_always_wrap=false
+pref_search_current_file_dir=true
+fif_regexp=false
+fif_case_sensitive=true
+fif_match_whole_word=false
+fif_invert_results=false
+fif_recursive=false
+fif_extra_options=
+fif_use_extra_options=false
+fif_files=
+fif_files_mode=0
+find_regexp=false
+find_regexp_multiline=false
+find_case_sensitive=false
+find_escape_sequences=false
+find_match_whole_word=false
+find_match_word_start=false
+find_close_dialog=true
+replace_regexp=false
+replace_regexp_multiline=false
+replace_case_sensitive=false
+replace_escape_sequences=false
+replace_match_whole_word=false
+replace_match_word_start=false
+replace_search_backwards=false
+replace_close_dialog=true
diff --git a/install.sh b/install.sh
index 51fcbfc..1ad92fa 100755
--- a/install.sh
+++ b/install.sh
@@ -43,6 +43,9 @@ sudo apt-get install -y \
     dmenu \
     imagemagick \
     bc \
+    geany \
+    gocryptfs \
+    fuse3 \

 mkdir -p "$BUILD_DIR"
 cd "$BUILD_DIR"
@@ -142,6 +145,27 @@ copy_config() {

 copy_config sxwmrc sxwmrc
 copy_config sxbarc sxbar/sxbarc
+copy_config geany/geany.conf geany/geany.conf
+copy_config geany/colorschemes/gruvbox.conf geany/colorschemes/gruvbox.conf
+
+echo "==> Installerar Gruvbox GTK-tema (för Geanys gränssnitt, inte bara editorn)..."
+mkdir -p "$HOME/.local/share/themes/Gruvbox-Dark/gtk-3.0"
+cp "$SCRIPT_DIR/themes/Gruvbox-Dark/gtk-3.0/gtk.css" "$HOME/.local/share/themes/Gruvbox-Dark/gtk-3.0/gtk.css"
+cp "$SCRIPT_DIR/themes/Gruvbox-Dark/index.theme" "$HOME/.local/share/themes/Gruvbox-Dark/index.theme"
+echo "    Gruvbox-Dark -> ~/.local/share/themes/Gruvbox-Dark"
+
+echo "==> Installerar geany-wrapper..."
+# Sätter GTK_THEME=Gruvbox-Dark oavsett hur Geany startas. Ett bash-alias
+# räcker inte -- dmenu/sxwm kör aldrig ~/.bashrc, men ärver PATH från
+# ~/.xinitrc där ~/.local/bin ligger först, så denna skuggar /usr/bin/geany.
+cp "$SCRIPT_DIR/bin/geany" "$HOME/.local/bin/geany"
+chmod +x "$HOME/.local/bin/geany"
+echo "    geany -> ~/.local/bin/geany"
+
+echo "==> Installerar Geany .desktop-override (samma tema från app-startare)..."
+mkdir -p "$HOME/.local/share/applications"
+cp "$SCRIPT_DIR/applications/geany.desktop" "$HOME/.local/share/applications/geany.desktop"
+echo "    geany.desktop -> ~/.local/share/applications/geany.desktop"

 echo "==> Kopierar skrivbordsbakgrund..."
 mkdir -p "$HOME/BG"
@@ -201,6 +225,26 @@ cp "$SCRIPT_DIR/bin/mouse-settings" "$HOME/.local/bin/mouse-settings"
 chmod +x "$HOME/.local/bin/mouse-settings"
 echo "    mouse-settings -> ~/.local/bin/mouse-settings"

+echo "==> Installerar status..."
+mkdir -p "$HOME/.local/bin"
+cp "$SCRIPT_DIR/bin/status" "$HOME/.local/bin/status"
+chmod +x "$HOME/.local/bin/status"
+echo "    status -> ~/.local/bin/status"
+
+echo "==> Installerar gcfs (minimal gocryptfs-valvhanterare)..."
+mkdir -p "$HOME/.local/bin"
+cp "$SCRIPT_DIR/bin/gcfs" "$HOME/.local/bin/gcfs"
+chmod +x "$HOME/.local/bin/gcfs"
+# Lägg bara exempel-configen om ingen redan finns -- gcfs skapar en identisk
+# vid första körning, och en riktig vaults.conf ska aldrig skrivas över.
+if [ ! -f "$HOME/.config/gcfs/vaults.conf" ]; then
+    mkdir -p "$HOME/.config/gcfs"
+    cp "$SCRIPT_DIR/config/gcfs/vaults.conf" "$HOME/.config/gcfs/vaults.conf"
+    echo "    gcfs -> ~/.local/bin/gcfs, vaults.conf -> ~/.config/gcfs/"
+else
+    echo "    gcfs -> ~/.local/bin/gcfs (behöll befintlig ~/.config/gcfs/vaults.conf)"
+fi
+
 echo "==> Installerar tte (terminaltexteffects) via pipx..."
 pipx install terminaltexteffects
 pipx ensurepath
@@ -231,6 +275,49 @@ else
     echo "    Hoppar över lid-display-watch"
 fi

+echo ""
+echo "==> Stänga av locket ska inte göra något (ingen suspend/hibernate)"
+echo ""
+read -p "Sätta HandleLidSwitch=ignore i /etc/systemd/logind.conf? (j/n) " -n 1 -r
+echo
+if [[ $REPLY =~ ^[Jj]$ ]]; then
+    LOGIND_CONF="/etc/systemd/logind.conf"
+    if grep -q '^HandleLidSwitch=igonre$' "$LOGIND_CONF" 2>/dev/null; then
+        # Stavfel som gjort att systemd-logind ignorerat raden och fallit
+        # tillbaka på det inbyggda default-värdet ("suspend") istället
+        sudo sed -i 's/^HandleLidSwitch=igonre$/HandleLidSwitch=ignore/' "$LOGIND_CONF"
+        echo "    HandleLidSwitch=igonre -> ignore (rättade stavfel)"
+    elif grep -q '^HandleLidSwitch=' "$LOGIND_CONF" 2>/dev/null; then
+        echo "    HandleLidSwitch redan satt, rör inte: $(grep '^HandleLidSwitch=' "$LOGIND_CONF")"
+    else
+        echo 'HandleLidSwitch=ignore' | sudo tee -a "$LOGIND_CONF" > /dev/null
+        echo "    HandleLidSwitch=ignore tillagd"
+    fi
+    echo "    Kräver omstart för att aktiveras. Kör INTE 'systemctl restart"
+    echo "    systemd-logind' live -- på den här maskinen (X startas manuellt"
+    echo "    via startx/.xinitrc, ingen display manager) har det hängt hela"
+    echo "    skrivbordet och krävt en hård omstart."
+else
+    echo "    Hoppar över HandleLidSwitch"
+fi
+
+echo ""
+echo "==> Förhindra att Bluetooth tappar anslutningar vid systeminaktivitet"
+echo ""
+read -p "Installera udev-regel som stänger av USB-autosuspend för Bluetooth-adaptern? (j/n) " -n 1 -r
+echo
+if [[ $REPLY =~ ^[Jj]$ ]]; then
+    # Specifik för den här laptopens BT-chip (Qualcomm Atheros 0cf3:e500).
+    # Annan hårdvara: hitta idVendor/idProduct via `lsusb`, matcha mot
+    # /sys/class/bluetooth/hci0 -> realpath för att hitta rätt usb-enhet.
+    sudo cp "$SCRIPT_DIR/udev/71-bluetooth-no-autosuspend.rules" /etc/udev/rules.d/71-bluetooth-no-autosuspend.rules
+    sudo udevadm control --reload-rules
+    sudo udevadm trigger --attr-match=idVendor=0cf3 --attr-match=idProduct=e500
+    echo "    71-bluetooth-no-autosuspend.rules -> /etc/udev/rules.d/"
+else
+    echo "    Hoppar över Bluetooth-udev-regeln"
+fi
+
 echo "==> Konfigurerar skärmsläckning på TTY1 (innan inloggning/efter utloggning)..."
 GRUB_FILE="/etc/default/grub"
 if grep -q 'consoleblank=' "$GRUB_FILE" 2>/dev/null; then
diff --git a/st.code-workspace b/st.code-workspace
new file mode 100644
index 0000000..d3ab080
--- /dev/null
+++ b/st.code-workspace
@@ -0,0 +1,11 @@
+{
+	"folders": [
+		{
+			"path": "../proj/st"
+		},
+		{
+			"path": "."
+		}
+	],
+	"settings": {}
+}
\ No newline at end of file
diff --git a/themes/Gruvbox-Dark/gtk-3.0/gtk.css b/themes/Gruvbox-Dark/gtk-3.0/gtk.css
new file mode 100644
index 0000000..ce3097e
--- /dev/null
+++ b/themes/Gruvbox-Dark/gtk-3.0/gtk.css
@@ -0,0 +1,26 @@
+@import url("resource:///org/gtk/libgtk/theme/Adwaita/gtk-contained-dark.css");
+
+/* Gruvbox dark palette overrides layered on top of Adwaita-dark's structure */
+@define-color theme_fg_color #ebdbb2;
+@define-color theme_text_color #ebdbb2;
+@define-color theme_bg_color #282828;
+@define-color theme_base_color #1d2021;
+@define-color theme_selected_bg_color #458588;
+@define-color theme_selected_fg_color #ebdbb2;
+@define-color theme_unfocused_selected_bg_color #3c3836;
+@define-color theme_unfocused_selected_fg_color #ebdbb2;
+@define-color insensitive_bg_color #32302f;
+@define-color insensitive_fg_color #7c6f64;
+@define-color insensitive_base_color #282828;
+@define-color theme_unfocused_bg_color #282828;
+@define-color theme_unfocused_fg_color #a89984;
+@define-color theme_unfocused_base_color #282828;
+@define-color theme_unfocused_text_color #bdae93;
+@define-color borders #504945;
+@define-color unfocused_borders #3c3836;
+@define-color warning_color #fabd2f;
+@define-color error_color #fb4934;
+@define-color success_color #b8bb26;
+@define-color destructive_color #fb4934;
+@define-color content_view_bg #1d2021;
+@define-color placeholder_text_color #928374;
diff --git a/themes/Gruvbox-Dark/index.theme b/themes/Gruvbox-Dark/index.theme
new file mode 100644
index 0000000..3d61ed2
--- /dev/null
+++ b/themes/Gruvbox-Dark/index.theme
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=X-GNOME-Metatheme
+Name=Gruvbox-Dark
+Comment=Gruvbox dark, layered on Adwaita-dark
+
+[X-GNOME-Metatheme]
+GtkTheme=Gruvbox-Dark
+IconTheme=Adwaita
+CursorTheme=Adwaita
diff --git a/udev/71-bluetooth-no-autosuspend.rules b/udev/71-bluetooth-no-autosuspend.rules
new file mode 100644
index 0000000..469fa7d
--- /dev/null
+++ b/udev/71-bluetooth-no-autosuspend.rules
@@ -0,0 +1,5 @@
+# Qualcomm Atheros Bluetooth (0cf3:e500) tappade aktiva anslutningar när
+# systemet gick helt inaktivt (t.ex. vid skärmsläckning) - kernelns generella
+# USB-autosuspend (power/control=auto, 2000ms) stängde ner adaptern. Detta
+# rör bara denna enhet, inte systemets sömn/suspend-inställningar i övrigt.
+ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0cf3", ATTR{idProduct}=="e500", TEST=="power/control", ATTR{power/control}="on"