aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2020-06-05 20:34:37 -0400
committerGitHub <noreply@github.com>2020-06-05 20:34:37 -0400
commitc314f39086049e073bf0548d6429ba971c43e15d (patch)
tree3b82e0726ab701deb449b8de5c9287dab76d5ec4
parent99028d7f518ff19b3ec414941f0a19f18bbbbede (diff)
downloadxesite-c314f39086049e073bf0548d6429ba971c43e15d.tar.xz
xesite-c314f39086049e073bf0548d6429ba971c43e15d.zip
blog: why i use suckless (#162)
-rw-r--r--blog/why-i-use-suckless-tools-2020-06-05.markdown309
1 files changed, 309 insertions, 0 deletions
diff --git a/blog/why-i-use-suckless-tools-2020-06-05.markdown b/blog/why-i-use-suckless-tools-2020-06-05.markdown
new file mode 100644
index 0000000..a736848
--- /dev/null
+++ b/blog/why-i-use-suckless-tools-2020-06-05.markdown
@@ -0,0 +1,309 @@
+---
+title: "Why I Use Suckless Tools"
+date: 2020-06-05
+---
+
+# Why I Use Suckless Tools
+
+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:
+
+- [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 tenants 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 hightly 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.