310 lines
15 KiB
Markdown
310 lines
15 KiB
Markdown
---
|
|
title: "Why I Use Suckless Tools"
|
|
date: 2020-06-05
|
|
---
|
|
|
|
Software is complicated. Foundational building blocks of desktop environments
|
|
tend to grow year over year until it's difficult to understand or maintain them.
|
|
[Suckless][suckless] offers an alternative to this continuous cycle of bloat and
|
|
meaningless redesign. Suckless tools aim to keep things simple, minimal, usable
|
|
and hackable by default. Their window manager [dwm][dwm] is just a window
|
|
manager. It doesn't handle things like transparency, compositing or volume
|
|
control. Their terminal [st][st] is just a terminal. It doesn't handle fancy
|
|
things like ancient terminal kinds that died out long ago. It just displays
|
|
text. It doesn't handle things that tmux or similar could take care of, because
|
|
tmux can do a better job at that than st ever could on its own.
|
|
|
|
[suckless]: https://suckless.org/
|
|
[dwm]: https://dwm.suckless.org/
|
|
[st]: https://st.suckless.org/
|
|
|
|
Suckless tools are typically configured in C, the language they are written in.
|
|
However as a side effect of suckless tools having their configuration baked into
|
|
the executable at compile time, they start up _instantly_. If something goes
|
|
wrong while using them, you can easily jump right into the code that implements
|
|
them and nail down issues using basic debugger skills.
|
|
|
|
However, even though the window manager is meager, it still offers places for
|
|
you to make it look beautiful. For examples of beautiful dwm setups, see [this
|
|
search of /r/unixporn on reddit][unixporndwm].
|
|
|
|
[unixporndwm]: https://www.reddit.com/r/unixporn/search?q=dwm&restrict_sr=1
|
|
|
|
I would like to walk through my dwm setup, how I have it configured all of the
|
|
parts at play as well as an example of how I debug problems in my dwm config.
|
|
|
|
## My dwm Config
|
|
|
|
As dwm is configured in C, there's also a community of people creating
|
|
[patches][dwmpatches] for dwm that add extra features like additional tiling
|
|
methods, the ability to automatically start things with dwm, transparency for
|
|
the statusbar and so much more. I use the following patches:
|
|
|
|
[dwmpatches]: https://dwm.suckless.org/patches/
|
|
|
|
- [alpha](https://dwm.suckless.org/patches/alpha/)
|
|
- [autostart](https://dwm.suckless.org/patches/autostart/)
|
|
- [bottomstack](https://dwm.suckless.org/patches/bottomstack/)
|
|
- [dwmc](https://dwm.suckless.org/patches/dwmc/)
|
|
- [pertag](https://dwm.suckless.org/patches/pertag/)
|
|
- [systray](https://dwm.suckless.org/patches/systray/)
|
|
- [uselessgap](https://dwm.suckless.org/patches/uselessgap/)
|
|
|
|
This combination of patches allows me to make things feel comfortable and
|
|
predictable enough that I can rely entirely on muscle memory for most of my
|
|
window management. Nearly all of it is done with the keyboard too.
|
|
|
|
[Here][dwmconfig] is my config file. It's logically broken into two big sections:
|
|
|
|
[dwmconfig]: https://tulpa.dev/cadey/dwm/src/commit/8ea55d397459a865041b96d5b4933f426d010e6d/config.def.h
|
|
|
|
- Variables
|
|
- Keybinds
|
|
|
|
I'll go into more detail about these below.
|
|
|
|
### Variables
|
|
|
|
The main variables in my config control the following:
|
|
|
|
- border width
|
|
- size of the gaps when tiling windows
|
|
- the snap width
|
|
- system tray errata
|
|
- the location of the bar
|
|
- the fonts
|
|
- colors
|
|
- transparency values for the bar
|
|
- workspace names (mine are based off of the unicode emoticon `(ノ◕ヮ◕)ノ*:・゚✧`)
|
|
- app-specific hacks
|
|
- default settings for the tiling layouts
|
|
- if windows should be forced into place or not
|
|
- window layouts
|
|
|
|
All of these things control various errata. As a side effect of making them all
|
|
compile time constants, these settings don't have to be loaded into the program
|
|
because they're already a part of it. I use the [Hack][hackfont] font on my
|
|
desktop and with emacs.
|
|
|
|
[hackfont]: https://sourcefoundry.org/hack/
|
|
|
|
### Keybinds
|
|
|
|
The real magic of tiling window managers is that all of the window management
|
|
commands are done with my keyboard. Alt is the key I have devoted to controlling
|
|
the window manager. All of my window manager control chords use the alt key.
|
|
|
|
Here are the main commands and what they do:
|
|
|
|
| Command | Effect |
|
|
|--------------------------------------|------------------------------------------------------------------------------------------------------|
|
|
| Alt-p | Spawn a program by name |
|
|
| Alt-Shift-Enter | Open a new terminal window |
|
|
| Alt-b | Hide the bar if it is shown, show the bar if it is hidden |
|
|
| Alt-j | Move focus down the stack of windows |
|
|
| Alt-k | Move focus up the stack of windows |
|
|
| Alt-i | Increase the number of windows in the primary area |
|
|
| Alt-d | Decrease the number of windows in the primary area |
|
|
| Alt-h | Make the primary area smaller by 5% |
|
|
| Alt-l | Make the primary area larger by 5% |
|
|
| Alt-Enter | Move the currently active window into the primary area |
|
|
| Alt-Tab | Switch to the most recently active workspace |
|
|
| Alt-Shift-C | Nicely ask a window to close |
|
|
| Alt-t | Select normal tiling mode for the current workspace |
|
|
| Alt-f | Select floating (non-tiling) mode for the current workspace |
|
|
| Alt-m | Select monocle (fullscreen active window) mode for the current workspace |
|
|
| Alt-u | Select bottom-stacked tiling mode for the current workspace |
|
|
| Alt-o | Select bottom-stacked horizontal tiling mode for the current workspace (useful on vertical monitors) |
|
|
| Alt-e | Open a new emacs window |
|
|
| Alt-Space | Switch to the most recently used tiling method |
|
|
| Alt-Shift-Space | Detach the currently active window from tiling |
|
|
| Alt-1 thru Alt-9 | Switch to a given workspace |
|
|
| Alt-Shift-1 thru Alt-Shift-9 | Move the active window to a given workspace |
|
|
| Alt-0 | Show all windows on all workspaces |
|
|
| Alt-Shift-0 | Show the active window on all workspaces |
|
|
| Alt-Comma and Alt-Period | Move focus to the other monitor |
|
|
| Alt-Shift-Comma and Alt-Shift-Period | Move the active window to the other monitor |
|
|
| Alt-Shift-q | Uncleanly exit dwm and kill the session |
|
|
|
|
This is just enough commands that I can get things done, but not so many that I
|
|
get overwhelmed and forget what keybind does what. I have most of this committed
|
|
to muscle memory (and had to look at the config file to write out this table),
|
|
and as a result nearly all of my window management is done with my keyboard.
|
|
|
|
The rest of my config handles things like Alt-Right-Click to resize windows
|
|
arbitrarily, signals with dwmc and other overhead like that.
|
|
|
|
## The Other Parts
|
|
|
|
The rest of my desktop environment is built up using a few other tools that
|
|
build on top of dwm. You can see the NixOS modules I've made for it
|
|
[here](https://github.com/Xe/nixos-configs/blob/master/common/programs/dwm.nix)
|
|
and [here](https://github.com/Xe/nixos-configs/blob/master/common/users/cadey/dwm.nix):
|
|
|
|
- [xrandr](https://wiki.archlinux.org/index.php/Xrandr) to set up my multiple
|
|
monitors and rotation for them
|
|
- [feh](https://feh.finalrewind.org/) to set my wallpaper
|
|
- [picom](https://github.com/yshui/picom) to handle compositing effects like
|
|
transparency, blur and drop shadows for windows
|
|
- [pasystray](https://github.com/christophgysin/pasystray) for controlling my
|
|
system volume
|
|
- [dunst](https://dunst-project.org/) for notifications
|
|
- [xmodmap](https://wiki.archlinux.org/index.php/Xmodmap) for rebinding the caps
|
|
lock key to the escape key
|
|
- [cabytcini](https://tulpa.dev/cadey/cabytcini) to show the current time and
|
|
weather in my dwm bar
|
|
|
|
Each of these tools has their own place in the stack and they all work together
|
|
to give me a coherent and cohesive environment that I can use for Netflix,
|
|
programming, playing Steam games and more.
|
|
|
|
cabytcini is a program I created for myself as part of my goal to get more
|
|
familiar with Rust. As of the time of this post being written, it uses only 11
|
|
megabytes of ram and is configured using a config file located at
|
|
`~/.config/cabytcini/gaftercu'a.toml`. It scrapes data from the API server I use
|
|
for my wall-mounted clock to show me the weather in Montreal. I've been meaning
|
|
to write more about it, but it's currently only documented in Lojban.
|
|
|
|
## Debugging dwm
|
|
|
|
Software is imperfect, even smaller programs like dwm can still have bugs in
|
|
them. Here's the story of how I debugged and bisected a problem with [my dwm
|
|
config](https://tulpa.dev/cadey/dwm) recently.
|
|
|
|
I had just gotten the second monitor set up and noticed that whenever I sent a
|
|
window to it, the entire window manager seemed to get locked up. I tried sending
|
|
the quit command to see if it would respond to that, and it failed. I opened up
|
|
a virtual terminal with control-alt-F1 and logged in there, then I launched
|
|
[htop](https://hisham.hm/htop/) to see if the process was blocked.
|
|
|
|
It reported dwm was using 100% CPU. This was odd. I then decided to break out
|
|
the debugger and see what was going on. I attached to the dwm process with `gdb
|
|
-p (pgrep dwm)` and then ran `bt full` to see where it was stuck.
|
|
|
|
The backtrace revealed it was stuck in the `drawbar()` function. It was stuck in
|
|
a loop that looked something like this:
|
|
|
|
```c
|
|
for (c = m->clients; c; c = c->next) {
|
|
occ |= c->tags;
|
|
if (c->isurgent)
|
|
urg |= c->tags;
|
|
}
|
|
```
|
|
|
|
dwm stores the list of clients per tag in a singly linked list, so the root
|
|
cause could be related to a circular linked list somehow, right?
|
|
|
|
I decided to check this by printing `c` and `c->next` in GDB to see what was
|
|
going on:
|
|
|
|
```
|
|
gdb> print c
|
|
0xfad34f
|
|
gdb> print c->next
|
|
0xfad34f
|
|
```
|
|
|
|
The linked list was circular. dwm was stuck iterating an infinite loop. I looked
|
|
at the type of `c` and saw it was something like this:
|
|
|
|
```c
|
|
struct Client {
|
|
char name[256];
|
|
float mina, maxa;
|
|
float cfact;
|
|
int x, y, w, h;
|
|
int oldx, oldy, oldw, oldh;
|
|
int basew, baseh, incw, inch, maxw, maxh, minw, minh;
|
|
int bw, oldbw;
|
|
unsigned int tags;
|
|
int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
|
|
Client *next;
|
|
Client *snext;
|
|
Monitor *mon;
|
|
Window win;
|
|
};
|
|
```
|
|
|
|
So, `next` is a pointer to the next client (if it exists). Setting the pointer
|
|
to `NULL` would probably break dwm out of the infinite loop. So I decided to
|
|
test that by running:
|
|
|
|
```
|
|
gdb> set var c->next = 0x0
|
|
```
|
|
|
|
To set the next pointer to null. dwm immediately got unstuck and exited
|
|
(apparently my quit command from earlier got buffered), causing the login screen
|
|
to show up. I was able to conclude that something was wrong with my dwm setup.
|
|
|
|
I know this behavior worked on release versions of dwm, so I decided to load up
|
|
KDE and then take a look at what was going on with [Xephyr][xephyr] and [git
|
|
bisect][gitbisect].
|
|
|
|
[xephyr]: https://wiki.archlinux.org/index.php/Xephyr
|
|
[gitbisect]: https://www.metaltoad.com/blog/beginners-guide-git-bisect-process-elimination
|
|
|
|
I created two fake monitors with Xephyr:
|
|
|
|
```console
|
|
$ Xephyr -br -ac -noreset -screen 800x600 -screen 800x600 +xinerama :1 &
|
|
```
|
|
|
|
And then started to git bisect my dwm fork:
|
|
|
|
```console
|
|
$ cd ~/code/cadey/dwm
|
|
$ git bisect init
|
|
$ git bisect bad HEAD
|
|
$ git bisect good cb3f58ad06993f7ef3a7d8f61468012e2b786cab
|
|
```
|
|
|
|
I registered the bad commit (the current one) and the last known good commit
|
|
(from when [dwm 6.2 was
|
|
released](https://tulpa.dev/cadey/dwm/commit/cb3f58ad06993f7ef3a7d8f61468012e2b786cab))
|
|
and started to recreate the conditions of the hang.
|
|
|
|
I set the `DISPLAY` environment variable so that dwm would use the fake
|
|
monitors:
|
|
|
|
```console
|
|
$ export DISPLAY=:1
|
|
```
|
|
|
|
and then rebuilt/ran dwm:
|
|
|
|
```console
|
|
$ make clean && rm config.h && make && ./dwm
|
|
```
|
|
|
|
Once I had dwm up and running, I created a terminal window and tried to send it
|
|
to the other screen. If it worked, I marked the commit as good with `git bisect
|
|
good`, and if it hung I marked the commit as bad with `git bisect bad`. 7
|
|
iterations later and I found out that the [attachbelow][attachbelow] patch was
|
|
the culprit.
|
|
|
|
[attachbelow]: https://dwm.suckless.org/patches/attachbelow/
|
|
|
|
I reverted the patch on the master branch, rebuilt and re-ran dwm and tried to
|
|
send the terminal window between the fake monitors. It worked every time. Then I
|
|
committed the revert of attachbelow, pushed it to my [NUR
|
|
repo](https://github.com/Xe/xepkgs/commit/c3bffbc8a3ebbaf13bee60e00c8002934d89e803),
|
|
and then rebuilt my tower's config once it passed CI.
|
|
|
|
Being a good internet citizen, I reported this to the [suckless mailing
|
|
list](https://lists.suckless.org/dev/2006/33946.html) and then was able to get a
|
|
reply back not only confirming the bug, but also with [a patch for the
|
|
patch](https://lists.suckless.org/dev/2006/33947.html) to fix the
|
|
behavior forever. I have yet to integrate this meta-patch into my dwm fork, but
|
|
I'll probably get around to it someday.
|
|
|
|
This really demonstrates one of the core tenets of the suckless philosophy
|
|
perfectly. I am not very familiar with how the dwm codebase works, but I am able
|
|
to dig into its guts and diagnose/fix things because it is intentionally kept as
|
|
simple as possible.
|
|
|
|
If you use Linux on a desktop/laptop, I highly suggest taking a look at
|
|
suckless software and experimenting with it. It is super optimized for
|
|
understandability and hacking, which is a huge breath of fresh air these days.
|