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"