My experience with dwm started as an interest in tiling window managers - had previously tried i3, but never got comfortable with the keybindings. When I found about the whole suckless project, the idea of a simpler WM that offered tiling and was very light made me interested in trying it on an older netbook I had.

Since I had no clue on how to patch it, found an interesting fork on github that had some patches I was interested in. Some months later I got tired of it and chose to go all the way to patching dwm myself, and getting the settings I wanted.

And there came the idea of documenting the process, albeit a little late, but better now before I forget the details. The following is a rough guide to patching and customizing dwm, and how to deal with some of the minor details. For a beginner's guide on how to use dwm, see the excellent Dave's Visual Guide to dwm.

Notice: I'm using the git version of dwm, which is slightly different and I recommend above the stable versions since it has several fixes.

I've applied the following patches:

  • Tilegap
  • Statuscolors
  • Systray
  • Scratchpad
  • Pertag
  • Cfacts
  • Bottomstack layout (with custom gaps)
  • Attachaside
  • Clear layout
  • Status bar padding

Colors

The easiest part: colors are defined as strings, using an RGB format, and are arrays of characters.

static const char normbordercolor[] = "#393f45";

The name doesn't matter, and you can have a many colors defined as you like. What's important is where you put them on the colors array:

static const char *colors[][3]      = {
	/*               fg         bg         border   */
    [SchemeNorm] = { normfgcolor,   normbgcolor,   normbordercolor, },
    [SchemeSel]  = { selfgcolor,    selbgcolor,    selbordercolor , },
	[SchemeUrg] =  { urgfgcolor,    urgbgcolor,    urgbordercolor },
};

Let me explain what each of these means:

  • [SchemeNorm] holds the colors used for the inactive windows and tags, fg being the color of words or icons that define each tag, bgthe background of each, and border the one for inactive windows.
  • [SchemeSel], same idea as before, but for selected windows and tags. In my config I've set the background of both to be the same and only change the foreground color.
  • [SchemeUrg] is an interesting scheme, used for notifications and warnings - for example, having Firefox open in one tag, and in another tag I open a link, the tag containing Firefox will change its colors.

For the sake of consistency, I stick to the colorscheme of my terminal (at the time of writing this, base16 Ashes).

Fonts

dwm has this very cool feature called font fallback which allows you to define several fonts, and if the first one can't display a character correctly, then dwm will try to use the following font, and so on. This could be useful if you have a font that doesn't support glyphs or unicode characters, but still want to be able to display them.

static const char *fonts[] = { "SauceCodePro Nerd Font:size=9:width=1" };

I've also seen that some people have had performance issues with some of the Nerd Fonts, and this method has allowed them to prevent that. Haven't had such problems so ended up putting just one font.

Tags

Similarly to the concept of several workspaces, dwm incorporates tags, which are like tabs were you can put your windows in. What's interesting tho, is the ability of combining several of these tags - that is, displaying the windows of several at once. By default, they're named from 1 to 9, but you can change that for any text or unicode icon you like - I think there's no minimum beyond 1 tag, but I'm not sure if there's a limit of how many dwm can deal with.

static const char *tags[] = { "", "", "", "", "" };

Rules

We can also define how certain programs will behave - this is handy to quickly set up the ones we use a lot, and be certain that, for example, Firefox will always open on the fourth tag of our third monitor and will always be floating no matter what.

static const Rule rules[] = {
	/* xprop(1):
	 *	WM_CLASS(STRING) = instance, class
	 *	WM_NAME(STRING) = title
	 */
	/* class      instance    title       tags mask     isfloating   monitor */
	{ "Gimp",     NULL,       NULL,       0,            1,           -1 },
	{ "Firefox",  NULL,       NULL,       0,       		0,           -1 },
};

A far more detailed explanation of how rules work can be found here.

Layouts

This is just a list of all the layouts we have installed, the first three the default ones dwm includes, and the rest, the ones we can add by patching. Since they're represented with a string, we can change that to a nice unicode character like I've done here.

static const Layout layouts[] = {
	/* symbol       arrange function */
	{ "",      tile },    /* first entry is default */
	{ "",      NULL },    /* no layout function means floating behavior */
	{ "",      monocle },
	{ "",      bstack },
	{ "",      bstackhoriz },
    { "",      clean },
};

Keybindings and custom commands

Before going into patching, I'll describe one of the most powerful customizations: keybindings. dwm can launch applications and use shell commands, and understanding how to add your own besides the default ones goes a long way to boost your laziness productivity.

First, let's take a quick glance at the MODKEY thing that's defined here:

#define MODKEY Mod1Mask

This is basically the main key when using dwm, over which we append others - for example, MODKEY + i moves the focus to another window, MODKEY + f changes the layout to floating, etc. This particular MODKEY is mapped to Mod1, which is the left Alt key. Since this one is often used by many programs, I changed it to the Windows key on my keyboard, which is Mod4.

Now, let's dig at adding commands: we define our command as a pointer to a static array of strings, giving each flag and parameter it's own place. Finally, we add NULL at the end to indicate that there are no more commands left. As an example, I've created this to take screenshots.

static const char *scrotcmd[] = { "scrot", "--exec", "mv -v $f ~/Pictures/Screenshots/", NULL };

Note that the third option here is a single parameter that you give to scrot, so it'd normally look like this on the command line:

$ scrot --exec 'mv -v $f ~/Pictures/Screenshots/'

Then, to be able to assign a keybinding to this new command, we go to the keys array, and at the bottom, we can add our own:

static Key keys[] = {
	/* modifier            key           function    argument */
	{ MODKEY,              XK_p,         spawn,      {.v = dmenucmd } },
	{ MODKEY|ShiftMask,    XK_Return,    spawn,      {.v = termcmd } },
    ...
    /* custom */
	{ False,               XK_Print,     spawn,      {.v = scrotcmd } },

A quick explanation of what's going on here:

  • In the first column we have to tell dwm if we're using a modifier key plus another key like Ctrl or Shift. In our case we don't need any so we'll leave it as False.
  • The following column is the actual key we want to press, in our case the Imp Pnt one. you can find a complete list of keys here.
  • function calls one of dwm's functions, we're interested in the spawn one since it's used for shell commands.
  • Lastly, we have to say what our command is! I've got no clue of what the syntax exactly means on this case, so just go with {.v = yourcommand }.

And that's pretty much it! But wait, what if I want to use those fancy buttons to lower/raise volume? Those keys are covered with another library, which we need to import at the beginning of our config:

#include <X11/XF86keysym.h>

I use the following commands to adjust volume:

static const char *volupcmd[] = { "amixer", "-q", "set", "Master", "5%+", NULL };
static const char *voldncmd[] = { "amixer", "-q", "set", "Master", "5%-", NULL };

Now, when adding a keybinding, notice the different naming for they key:

{ False,    XF86XK_AudioRaiseVolume,    spawn,    {.v = volupcmd } },
{ False,    XF86XK_AudioLowerVolume,    spawn,    {.v = voldncmd } },

You can find more media keys here.

To finish this section, there's a part in the config about adding bindings to mouse buttons but I haven't got use for that so I can't guide you there.

Patches

This is one if the most interesting parts about the suckless project but also what detracts others away from their software: they implement the bare, minimum functionality in a very compact and simple way, and to add more, you have to use patches. These are usually made and mantained by the community and add plenty of interesting twists to the usual workflow. Many fear this approach due to potential conflicts, and they're partly right, but solving these is trivial in most cases I've encountered. I'll quickly show how to add some simple patches and how to deal with conflicts.

Let's begin with a simple patch, let's say you want to add the pertag patch. You simply do:

$ patch -p1 < dwm-pertag-20170513-ceac8c9.diff
patching file dwm.c
Hunk #1 succeeded at 111 (offset -1 lines).
Hunk #2 succeeded at 131 (offset -1 lines).
Hunk #3 succeeded at 273 (offset -1 lines).
Hunk #4 succeeded at 642 (offset -1 lines).
Hunk #5 succeeded at 653 (offset -1 lines).
Hunk #6 succeeded at 992 (offset -2 lines).
Hunk #7 succeeded at 1527 (offset -2 lines).
Hunk #8 succeeded at 1548 (offset -2 lines).
Hunk #9 succeeded at 1725 (offset -2 lines).
Hunk #10 succeeded at 1764 (offset -2 lines).
Hunk #11 succeeded at 2085 (offset -1 lines).

Easy and smooth! Now recompile dwm and you're ready to go. You'll see a new file was generated called dwm.c.orig - this is the original file from before we applied the patch. If everything works fine, you can safely delete it.

Let's try to add more patches, say, new layouts like fibonacci and bottomstack. As before, we do:

$ patch -p1 < dwm-fibonacci-5.8.2.diff
patching file config.def.h
Hunk #1 succeeded at 39 (offset 10 lines).
Hunk #2 succeeded at 45 (offset 10 lines).
patching file fibonacci.c

$ patch -p1 < dwm-bottomstack-20160719-56a31dc.diff
patching file config.def.h
Hunk #1 FAILED at 41.
Hunk #2 succeeded at 79 (offset 3 lines).
1 out of 2 hunks FAILED -- saving rejects to file config.def.h.rej
patching file dwm.c
Hunk #1 succeeded at 235 (offset 1 line).
Hunk #2 succeeded at 2225 (offset 85 lines).

Don't panic, this is usual - let's check what specific changes couldn't be applied. Checking the config.def.h.rej file we can see:

--- config.def.h
+++ config.def.h
@@ -41,6 +41,8 @@ static const Layout layouts[] = {
 	{ "[]=",      tile },    /* first entry is default */
 	{ "><>",      NULL },    /* no layout function means floating behavior */
 	{ "[M]",      monocle },
+	{ "TTT",      bstack },
+	{ "===",      bstackhoriz },
 };
 
 /* key definitions */

The conflicts come from the patch expecting your code to look exactly like this, so let's see how that part of config.def.h actually looks like:

    { "[]=",      tile },    /* first entry is default */
	{ "><>",      NULL },    /* no layout function means floating behavior */
	{ "[M]",      monocle },
 	{ "[@]",      spiral },
 	{ "[\\]",      dwindle },
};

/* key definitions */

Now it's obvious - it didn't expect those those new layouts we added before since the patch was probably made on a vanilla dwm. This is an easy fix, as we just need to paste the layouts we wanted to add, so it looks like this:

    { "[]=",      tile },    /* first entry is default */
	{ "><>",      NULL },    /* no layout function means floating behavior */
	{ "[M]",      monocle },
    { "TTT",      bstack },
	{ "===",      bstackhoriz },
 	{ "[@]",      spiral },
 	{ "[\\]",      dwindle },
};

/* key definitions */

And that's pretty much the source of 90% of patch conflicts. There are some other weird cases that might pop up if you have some specific patches that want to modify a common part of the code for, say, drawing the status bar. And those are the tough ones if you are not familiar with C.