foxygit / dotfiles Log in
commits tags
README.md
                                 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

Platform Shell WM Bar License


Contents


About

This is my personal dotfiles setup for a lightweight, keyboard-driven X11 desktop environment built around sxwm (tiling window manager, patched with a handful of local fixes — see below), sxbar (status bar), dmenu (application launcher) and st (terminal, patched with a curated set of upstream suckless patches — see below). Everything is set up automatically via 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)
📶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

git clone <this repo>
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 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@<adapter>.service so DHCP only runs on the adapter you chose. 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.

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
│   ├── 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
├── 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:

PatchFixes
cursor-dpi-fix.patchIncorrect cursor scaling on high-DPI displays
net-active-window.patchAdds _NET_ACTIVE_WINDOW support — required for sxbar's taskbar module; without it, clicking a taskbar entry does nothing
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.patchWindows 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.patchFullscreening 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.patchA 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

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:

PatchAdds
01-delkey.patchFixes the Delete key to send \033[3~ instead of the legacy \033[P
02-scrollback.patchKeyboard (Shift+PageUp/Down) and mouse-wheel scrollback, disabled in the alt screen (vim, less, etc.)
03-clipboard.patchSelecting 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.patchRenders box-drawing/braille characters natively instead of relying on the font
05-vertcenter.patchVertically centers glyphs that don't fill the cell (pairs with boxdraw)
06-alpha.patchBackground transparency (-A/alpha), needs a compositor
07-blinking_cursor.patchBlinking cursor support with a configurable rate
08-bold-is-not-bright.patchBold text no longer automatically renders in the bright color variant
09-anysize.patchWindow can be resized to any pixel size instead of snapping to whole cells
10-histsize-5000.patchBumps the scrollback buffer from 2000 to 5000 lines (local tweak, not from upstream — matches the previous kitty scrollback_lines)
11-kitty-graphics-protocol.patchInline image display via a subset of the 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 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 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):

  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

ShortcutAction
mod + ReturnOpen st
mod + bOpen Firefox
mod + pdmenu (application launcher)
mod + qClose window
mod + shift + eQuit sxwm
mod + cCentre window
mod + mToggle monocle mode
mod + j / mod + kFocus next / previous window
mod + comma / mod + periodFocus previous / next monitor
mod + shift + comma / mod + shift + periodMove window to previous / next monitor
mod + shift + j / mod + shift + kMove window in master/stack
mod + l / mod + hIncrease / decrease master area
mod + ctrl + l / mod + ctrl + hIncrease / decrease stack window size
mod + Arrow keysMove window
mod + shift + Arrow keysResize window
mod + o / mod + iIncrease / decrease gaps
mod + spaceToggle floating
mod + shift + spaceToggle global floating
mod + shift + fFullscreen
mod + rReload 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.

Status bar modules (sxbar)

ModuleRefresh intervalColourNotes
🕐 Clock1 s#50fa7bCentred in the bar
📅 Date60 s#8be9fd
🔋 Battery30 s#ffb86c
🔊 Volume5 s#ff79c6
⚙️ CPU3 s#bd93f9
☀️ Brightness5 s#f1fa8cHover to reveal a slider
🔵 Bluetooth10 s#4a9effClick for an on/off/scan/pair menu
👤 User menu300 s#ff5555Click for sleep/log out/shut down
🌐 Network10 s#2ee6d6Click for a WiFi/Ethernet + IP menu
🎵 Media2 s#cba6f7Hover for album art + prev/play-pause/next
🗔 Taskbar1 sSecondary bar; click an entry to focus that window
⭐ Start menu3600 sHover 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

    (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 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

The screensaver renders bin/screensaver.txt through random terminaltexteffects 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 — editor/tagbar/msgwin

    fonts set to JetBrainsMono Nerd Font, color_scheme=gruvbox.conf.

  • config/geany/colorschemes/gruvbox.conf

    — Gruvbox dark syntax colours for the editor itself.

  • 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 — 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 — 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

Desktop wallpaper

Set automatically via feh --bg-scale in config/sxwmrc.


Built for a fast, minimal, keyboard-driven workday.