```
mmmm mm mm
*@@@***@@m @@ m@* ** @@ *@@@
@@ *@@m @@ @@* @@
@@ *@@ m@@*@@m @@@@@@ @@@@@ *@@@ @@ mm@*@@ m@@*@@@
@@ @@ @@* *@@ @@ @@ @@ !@ m@* @@ @@ **
@! m@@ @@ @@ @@ !@ !@ !@ !@****** *@@@@@m
@! m@!* @@ !@ @! !@ !@ !@ !@m m @@
!! !!! !@ !! !! !: !! !! !!****** *! @!
!: !!:* !!! !!! !! !: !! :! :!! !! !!
: : : : : : : : : ::: :: ::: : : : : : : : : :: : :!:
```
### My personal dotfiles for a minimal, tiling X11 desktop





---
## Contents
- [About](#about)
- [Features](#features)
- [Installation](#installation)
- [Structure](#structure)
- [sxwm patches](#sxwm-patches)
- [dmenu patches](#dmenu-patches)
- [st patches](#st-patches)
- [st quirks](#st-quirks)
- [Keyboard shortcuts (sxwm)](#keyboard-shortcuts-sxwm)
- [Status bar modules (sxbar)](#status-bar-modules-sxbar)
- [Utility scripts (bin/)](#utility-scripts-bin)
- [Screensaver](#screensaver)
- [Wallpaper](#wallpaper)
---
## About
This is my personal dotfiles setup for a lightweight, keyboard-driven X11 desktop
environment built around [`sxwm`](https://github.com/uint23/sxwm) (tiling window
manager, patched with a handful of local fixes — see [below](#sxwm-patches)),
[`sxbar`](https://github.com/MrJensK/sxbar) (status bar),
[`dmenu`](https://tools.suckless.org/dmenu/) (application launcher, patched to
appear centered — see [below](#dmenu-patches)) and
[`st`](https://st.suckless.org/) (terminal, patched with a curated set of
upstream suckless patches — see [below](#st-patches)). Everything is set up
automatically via [`install.sh`](install.sh) — clone the repo, run the script,
and you have a working environment.
## Features
| | |
|---|---|
| 🪟 | **sxwm** — tiling window manager, patched with 5 local fixes (DPI, taskbar focus, multi-monitor struts, hotplug remap, fullscreen targeting) |
| 📊 | **sxbar** — status bar with clock, date, battery, volume, CPU, brightness, Bluetooth, network, media player, taskbar and start menu popups |
| 🚀 | **dmenu** — fast application launcher (`mod + p`), centered on screen with results listed vertically |
| 📶 | **bluetui** — Bluetooth management right in the terminal |
| 🖥️ | **st** — suckless terminal, patched with 11 upstream fixes (scrollback, clipboard, box-drawing, alpha, blinking cursor, inline images, and more) |
| 🔤 | **JetBrainsMono Nerd Font** — icon support in the bar and terminal |
| 🔊 | **PipeWire** — audio (pipewire + pipewire-pulse + wireplumber) enabled automatically |
| 🔇 | Quiet install — PC speaker (`pcspkr`) and the X bell are disabled |
| 🌙 | Screen blanking / DPMS after 5 minutes of inactivity, plus a TTE-powered terminal screensaver |
| 💻 | Laptop-lid handling — auto-toggle the internal display on lid open/close, with an inhibitor to temporarily disable it |
| 🖱️ | Touchpad tweaks applied automatically (natural scrolling, tap-to-click, etc. via `xinput`) |
| ⌨️ | Swedish keyboard layout (`setxkbmap se`) |
| 🟢 | X autostarts on TTY1 at login |
## Installation
```bash
git clone
cd dotfiles
./install.sh
```
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`, 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,
Rust/`bluetui`, autostarting X on TTY1) so you can opt out if you already have
your own setup for those.
The first question is an optional interactive WiFi setup (`iwd` + `dhcpcd`),
done *before* the install so `apt` has network: it installs the two packages if
missing, starts `iwd`, lets you pick the adapter (only asked when there's more
than one), scans and lets you pick a network (or enter a hidden one), connects
with `iwctl`, and enables `dhcpcd@.service` so DHCP only runs on the
adapter you chose. Both are skipped if already usable — it checks for the
actual `iwctl` binary and a `dhcpcd@.service` unit, not just the package name,
since Debian's `dhcpcd-base` installs the `dhcpcd` binary without the systemd
units the script needs. If either is missing and there's no network yet to
install them with, it says so and skips WiFi setup instead of trying and
failing. If `dhcpcd` had to be installed, the generic `dhcpcd.service` (all
interfaces) that Debian enables is turned off again so it doesn't grab the
wired interface. 802.1x (enterprise) networks aren't handled — configure those
manually with `iwctl`.
Right after that (WiFi configured or not — e.g. you're on Ethernet), the
installer checks it actually has a working connection before starting the
unattended run, since apt and the `sxwm`/`sxbar`/`st` clones need one. With no
connection it asks whether to continue anyway (default no); declining exits
with a hint to plug in a cable or rerun and answer yes to the WiFi question.
The installer has an Omarchy-style UI: a centered logo, all the yes/no questions
asked up front (one keypress each), then an unattended run with a progress bar,
the current step and the tail of the log. Full output (apt, make, git, ...) goes
to `/tmp/dotfiles-install.log`; if a step fails the log tail is shown and the
script stops. `./install.sh --demo` plays the UI with fake steps and touches
nothing, useful for previewing or tweaking the look. Without a terminal
(piped/CI) it falls back to plain `==> step` lines and answers "no" to every
question.
`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
```
dotfiles/
├── install.sh # Main install script
├── BG/
│ └── j9huwdxo1zzg1.jpeg # Desktop wallpaper
├── bin/ # Utility scripts, installed to ~/.local/bin
│ ├── crypt # Mounts the gocryptfs vault on a USB stick to ~/crypt
│ ├── 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
│ ├── mouse-settings # Applies touchpad tweaks via xinput
│ ├── 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
│ └── status # Overview: WM stack, services, network, bluetooth, audio, battery, lock state
├── *.patch # Local sxwm patches, see "sxwm patches" below
├── dmenu-patches/ # Upstream dmenu patches, see "dmenu patches" below
│ └── 01-center.patch
├── st-patches/ # Upstream st patches, see "st patches" below
│ ├── 01-delkey.patch
│ ├── 02-scrollback.patch
│ ├── 03-clipboard.patch
│ ├── 04-boxdraw.patch
│ ├── 05-vertcenter.patch
│ ├── 06-alpha.patch
│ ├── 07-blinking_cursor.patch
│ ├── 08-bold-is-not-bright.patch
│ ├── 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)
```
## sxwm patches
`install.sh` clones upstream `sxwm` and applies these on top before building, in
order:
| Patch | Fixes |
|---|---|
| [`cursor-dpi-fix.patch`](cursor-dpi-fix.patch) | Incorrect cursor scaling on high-DPI displays |
| [`net-active-window.patch`](net-active-window.patch) | Adds `_NET_ACTIVE_WINDOW` support — required for sxbar's taskbar module; without it, clicking a taskbar entry does nothing. Only warps the cursor for real pager/taskbar requests (EWMH source indication `2`); Chromium/Electron apps (VS Code and others) send a source-`1` self-activation request when clicked while unfocused, which used to yank the cursor to the window's center right after the click |
| [`multi-monitor-struts.patch`](multi-monitor-struts.patch) | `_NET_WM_STRUT_PARTIAL` reservations weren't clamped to the panel's own monitor, so a monitor's reserved area could exceed its actual size and push windows off-screen |
| [`monitor-hotplug-remap.patch`](monitor-hotplug-remap.patch) | Windows lost their correct monitor association on hotplug (e.g. `lid-display-watch` toggling eDP); monitor ownership is now recomputed from each window's actual position |
| [`fullscreen-monitor-fix.patch`](fullscreen-monitor-fix.patch) | Fullscreening a window that had been moved to another monitor by an external tool (`xdotool`, `screensaver-launch`) fullscreened it on the wrong screen; the target monitor is now recomputed from the window's real geometry |
| [`strut-fresh-screen-size.patch`](strut-fresh-screen-size.patch) | A bottom/right-edge panel (e.g. sxbar's `secondary_bar`) could end up hidden behind normal windows — `reserve_bottom`/`reserve_right` are measured from the *opposite* screen edge and need the full screen size, but read it from `scr_width`/`scr_height`, cached globals only refreshed by `update_mons()`; `reserve_top`/`reserve_left` never depend on screen size so stayed correct. Reads the screen size fresh from the X server instead |
## dmenu patches
`install.sh` clones upstream `dmenu` (from suckless.org, not a distro package)
and applies this on top before building:
| Patch | Adds |
|---|---|
| [`01-center.patch`](dmenu-patches/01-center.patch) | The standard [suckless "center" patch](https://tools.suckless.org/dmenu/patches/center/): a `-c` flag that opens dmenu as a centered box instead of a full-width bar. `sxwmrc`'s `mod + p` bind passes `-c -l 10` — the vertical list (`-l lines`) is stock dmenu, no patch needed for that part |
## st patches
`install.sh` clones upstream `st` (v0.9.3) and applies these on top before
building, in order — all sourced from [st.suckless.org/patches](https://st.suckless.org/patches/):
| Patch | Adds |
|---|---|
| [`01-delkey.patch`](st-patches/01-delkey.patch) | Fixes the Delete key to send `\033[3~` instead of the legacy `\033[P` |
| [`02-scrollback.patch`](st-patches/02-scrollback.patch) | Keyboard (`Shift+PageUp/Down`) and mouse-wheel scrollback, disabled in the alt screen (vim, less, etc.) |
| [`03-clipboard.patch`](st-patches/03-clipboard.patch) | Selecting text also copies it to the `CLIPBOARD` selection, not just `PRIMARY`. Local fix on top of the upstream patch: empty/whitespace-only selections (a jittery click, dragging over blank cells) no longer overwrite `CLIPBOARD`, which used to wipe what you'd just copied in e.g. Firefox before you could paste it |
| [`04-boxdraw.patch`](st-patches/04-boxdraw.patch) | Renders box-drawing/braille characters natively instead of relying on the font |
| [`05-vertcenter.patch`](st-patches/05-vertcenter.patch) | Vertically centers glyphs that don't fill the cell (pairs with boxdraw) |
| [`06-alpha.patch`](st-patches/06-alpha.patch) | Background transparency (`-A`/`alpha`), needs a compositor |
| [`07-blinking_cursor.patch`](st-patches/07-blinking_cursor.patch) | Blinking cursor support with a configurable rate |
| [`08-bold-is-not-bright.patch`](st-patches/08-bold-is-not-bright.patch) | Bold text no longer automatically renders in the bright color variant |
| [`09-anysize.patch`](st-patches/09-anysize.patch) | Window can be resized to any pixel size instead of snapping to whole cells |
| [`10-histsize-5000.patch`](st-patches/10-histsize-5000.patch) | Bumps the scrollback buffer from 2000 to 5000 lines (local tweak, not from upstream — matches the previous kitty `scrollback_lines`) |
| [`11-kitty-graphics-protocol.patch`](st-patches/11-kitty-graphics-protocol.patch) | Inline image display via a subset of the [kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) (works with `icat`, image.nvim, yazi, etc.). Needs `imlib2`/`zlib` (installed by `install.sh`), plus `imagemagick` and `bc` at runtime for the bundled `icat` script. Hand-merged against the other patches — several hunks conflicted with boxdraw/vertcenter/anysize/blinking_cursor and were reconciled manually; confirmed working |
[`config/st/config.def.h`](config/st/config.def.h) is copied over the patched
source before building, so local tweaks survive a rebuild — edit it and
re-run `install.sh` to apply changes. It's tuned to match the previous kitty
setup: Gruvbox Dark Hard colours, opaque background, steady (non-blinking)
block cursor, JetBrainsMono Nerd Font, and 3 lines scrolled per mouse-wheel
notch. Live font zoom is built into vanilla st — `Ctrl+Shift+PageUp`/`PageDown`
to zoom, `Ctrl+Shift+Home` to reset — no patch needed.
`install.sh` also installs `icat` (the patch's bundled `icat-mini.sh`) to
`~/.local/bin/icat` — run `icat path/to/image.png` in st to display it inline.
Image protocol shortcuts (from patch 11): `Ctrl+Shift+click` on an image
previews it in `feh`, `Ctrl+Shift+middle-click` shows placement info in
`less`, `Ctrl+Shift+F1` toggles graphics debug output, `Ctrl+Shift+F6` dumps
internal state, `Ctrl+Shift+F7` unloads cached images to free RAM, and
`Ctrl+Shift+F8` toggles image display on/off entirely.
## st quirks
Coming from kitty, a few things about st behave differently enough to be
worth writing down:
- **Config is compiled in, not read at runtime.** kitty re-reads
`kitty.conf` on the fly; st bakes `config.def.h` into the binary at build
time as `config.h`. Changing font/colours/keybinds means editing
[`config/st/config.def.h`](config/st/config.def.h) and rebuilding (`./install.sh`,
or the manual steps below) — there's no config file to just edit and
reopen the terminal.
- **`config.h` doesn't auto-refresh from `config.def.h`.** The Makefile's
rule for `config.h` only runs if the file doesn't exist yet — `make clean`
does *not* delete it. If you rebuild in a directory that already has a
stale `config.h` from a previous build, your `config.def.h` edits are
silently ignored. Either `rm -f config.h` before `make`, or always build
in a fresh `git clone` (which is what `install.sh` does).
- **Manual rebuild, step by step** (what `install.sh` does for st, if you
want to do it yourself):
```bash
cd ~ # don't sit inside the directory you're about to rm -rf!
rm -rf /tmp/st-build
git clone --depth=1 https://git.suckless.org/st /tmp/st-build
cd /tmp/st-build
for p in ~/dotfiles/st-patches/*.patch; do patch -p1 < "$p"; done
cp ~/dotfiles/config/st/config.def.h config.def.h
make
sudo make install
```
- **Live font zoom exists but isn't advertised.** `Ctrl+Shift+PageUp`/`PageDown`
zooms the font in/out, `Ctrl+Shift+Home` resets it — built into vanilla st,
no patch needed. Easy to miss since kitty's equivalent
(`change_font_size`) is the one actually documented in `kitty.conf`.
- **No runtime equivalent of kitty's per-launch `--override`.** Things like
`window_padding_width` or `mouse_hide_wait` are compile-time-only in st;
`screensaver-launch` can only override font (`-f`) and alpha (`-A`) when
spawning the screensaver's st instance, not padding or cursor-hiding.
- **Window size is in character cells, not pixels.** `cols`/`rows` in
`config.def.h` set the *initial* size; kitty's `initial_window_width/height`
were literal pixels. With the `anysize` patch the window can still be
resized to any pixel size afterwards, it just doesn't start at one under
sxwm anyway since sxwm tiles it immediately.
- **`icat` needs `imagemagick` and `bc` at runtime**, not just build-time
`imlib2`/`zlib`. `make` succeeds without them; the script only fails the
first time you actually try to display an image. Both are in
`install.sh`'s package list now.
- **Scrollback is intentionally disabled inside full-screen apps** (vim,
less, tmux, etc. — anything using the alt screen). That's the
`02-scrollback.patch` behaviour, not a bug — those apps have their own
scrolling.
- **Selecting text copies to both `PRIMARY` and `CLIPBOARD`** (from
`03-clipboard.patch`), unlike vanilla st which only ever touches
`PRIMARY` on select and needs an explicit keybind to push to `CLIPBOARD`.
## Keyboard shortcuts (sxwm)
`mod` = Super
| Shortcut | Action |
|---|---|
| `mod + Return` | Open st |
| `mod + b` | Open Firefox |
| `mod + p` | dmenu (application launcher, centered, 10-line vertical list) |
| `mod + q` | Close window |
| `mod + shift + e` | Quit sxwm |
| `mod + c` | Centre window |
| `mod + m` | Toggle monocle mode |
| `mod + j` / `mod + k` | Focus next / previous window |
| `mod + comma` / `mod + period` | Focus previous / next monitor |
| `mod + shift + comma` / `mod + shift + period` | Move window to previous / next monitor |
| `mod + shift + j` / `mod + shift + k` | Move window in master/stack |
| `mod + l` / `mod + h` | Increase / decrease master area |
| `mod + ctrl + l` / `mod + ctrl + h` | Increase / decrease stack window size |
| `mod + Arrow keys` | Move window |
| `mod + shift + Arrow keys` | Resize window |
| `mod + o` / `mod + i` | Increase / decrease gaps |
| `mod + space` | Toggle floating |
| `mod + shift + space` | Toggle global floating |
| `mod + shift + f` | Fullscreen |
| `mod + r` | Reload config |
| `mod + alt + [1-4]` | Create scratchpad |
| `mod + ctrl + [1-4]` | Toggle scratchpad |
| `mod + alt + shift + [1-4]` | Remove scratchpad |
| `mod + [1-9]` | Switch workspace |
| `mod + shift + [1-9]` | Move window to workspace |
Media/function keys (volume, brightness, playback) are bound too — see
[`config/sxwmrc`](config/sxwmrc).
## Status bar modules (sxbar)
| Module | Refresh interval | Colour | Notes |
|---|---|---|---|
| 🕐 Clock | 1 s | `#50fa7b` | Centred in the bar |
| 📅 Date | 60 s | `#8be9fd` | |
| 🔋 Battery | 30 s | `#ffb86c` | |
| 🔊 Volume | 5 s | `#ff79c6` | |
| ⚙️ CPU | 3 s | `#bd93f9` | |
| ☀️ Brightness | 5 s | `#f1fa8c` | Hover to reveal a slider |
| 🔵 Bluetooth | 10 s | `#4a9eff` | Click for an on/off/scan/pair menu |
| 👤 User menu | 300 s | `#ff5555` | Click for sleep/log out/shut down |
| 🌐 Network | 10 s | `#2ee6d6` | Click for a WiFi/Ethernet + IP menu |
| 🎵 Media | 2 s | `#cba6f7` | Hover for album art + prev/play-pause/next |
| 🗔 Taskbar | 1 s | — | Secondary bar; click an entry to focus that window |
| ⭐ Start menu | 3600 s | — | Hover for favourite apps as popup buttons |
## Utility scripts (bin/)
Installed to `~/.local/bin` by `install.sh` and wired into `sxwmrc`/`~/.xinitrc`:
- **`mouse-settings`** — applies libinput touchpad tweaks via `xinput` (natural
scrolling on the touchpad only, tap-to-click, etc.). Safe to re-run any time.
- **`internal-display [on|off|toggle|status]`** — (de)activates the internal
(eDP) screen via `xrandr`. Refuses to turn it off if it's the only active
display.
- **`lid-display-watch`** — background daemon (started via sxwm's `exec`) that
watches the laptop lid state through `systemd-logind` and automatically turns
the internal display off on lid-close / on on lid-open, provided an external
monitor is connected. Opt-in during install.
- **`lidsuspend [on|off|status]`** — temporarily disables/re-enables what happens
on lid-close (suspend), without touching `/etc/systemd/logind.conf`, by
holding a systemd inhibitor lock.
- **`screensaverd`** — idle watcher; launches the screensaver after a period of
inactivity (default 150 s) and kills it the moment real activity resumes.
- **`screensaver-launch`** — launches the screensaver fullscreen on every
connected monitor.
- **`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`.
- **`crypt [mount|umount|status|toggle]`** — the no-config version of `gcfs` for
one USB stick: finds the gocryptfs cipherdir (the directory holding
`gocryptfs.conf`) under `/media/$USER` or `/run/media/$USER`, mounts it on
`~/crypt` and lets `gocryptfs` ask for the password. If the stick isn't mounted
yet it tries `udisksctl`; with several vaults it shows a menu. `CRYPT_MOUNT` /
`CRYPT_CIPHERDIR` override the mountpoint / cipherdir.
## Screensaver
The screensaver renders `bin/screensaver.txt` through random
[terminaltexteffects](https://github.com/ChrisBuilds/terminaltexteffects)
animations (installed via `pipx` during setup) inside a fullscreen st
window, and exits the instant you touch the mouse or keyboard. It also stops
itself the moment DPMS blanks the display or media starts playing (checked
every second by `screensaverd`), so it never keeps animating — and burning
CPU/GPU — into a screen nobody can see.
Idle timeout, frame rate, cell size and art are all environment variables,
read fresh each time `screensaverd` launches it — set them in `~/.xinitrc`
before `exec sxwm` (or wherever `screensaverd` gets started) to override the
defaults:
| Variable | Default | Effect |
|---|---|---|
| `SCREENSAVER_IDLE_SECONDS` | `150` | Seconds of inactivity before the screensaver launches |
| `SCREENSAVER_FPS` | `30` | `tte`'s `--frame-rate`. Higher looks smoother but costs more CPU — mostly matters once the cell count (below) is already low; at the default full-monitor cell count most effects are CPU-bound computing the frame and barely respond to this at all |
| `SCREENSAVER_FONT_SIZE` | `36` | st's font `pixelsize` while the screensaver runs. Bigger font = fewer, larger terminal cells for `tte` to animate over the same screen area, which is the bigger lever of the two — measured ~65% less CPU from cell count alone, before even touching frame rate |
| `SCREENSAVER_ART` | `screensaver.txt` next to the script | Path to the ASCII/ANSI art file to animate |
## 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
Set automatically via `feh --bg-scale` in [`config/sxwmrc`](config/sxwmrc).
---
Built for a fast, minimal, keyboard-driven workday.