dwm window manager

12 Nov

I am interested in window managers that can do both tiling and floating layouts. I don't think I'll ever become a tiling purist, but trying out dwm for some longer time has been a good experience.
There's some truth in it - the lack of "features" helps to focus on what's relevant. Both looking at and getting around my dwm desktop has been a pleasant experience (i cannot pretend that looks aren't important at all).

Version tested: dwm-6.1
Disclaimer: some bits of description are copied over from here.

Screenshot clean 2

Because dwm is customized through editing its source code, it’s pointless to make binary packages of it. This keeps its userbase small and elitist. No novices asking stupid questions. (source)

Not very endearing. Still, dwm is packaged in most distros' repositories, and I think it's useful to get a first impression. A default (packaged) dwm desktop can be seen here.
Customisations are made by editing config.h, then compiling. The process is simpler than it sounds! You should try it. Compilation is very fast, dependencies are few, the single binary is very small.
Personally I would be willing to wait a few more µs during startup just to have a normal config file, but this is not a dealbreaker for me, in fact I can see the appeal.

Most people will want to change the default modifier key from left (Mod1) to the Windows key (Mod4). And the default font, and some keybinds. I have not fully explored the possibilities, but in principle everything can be configured.

The border between configuration and programming becomes fluid.

This makes it kind of hard to find complete documentation. the customisation page explains the process and gives a few examples, but for most things there is no complete list of features.
For example: no complete list of all functions that could be accessed with key- or mouse bindings. Of course such a list could never be complete, since you can write your own functions and compile them in!
So any request for complete documentation can always, conveniently, be countered by "Read the code".

But that isn't a crucial problem for me; if I had decided to keep using dwm, there would have been a lot more to explore & configure, maybe with hotkeys and clickable areas (taskbar, statusline, root window...) to start with.

And in any case, I learned something about compiling, the Arch Build System, and patching code with patch and diff along the way.

In its default configuration, dwm is a very simple tiling window manager. It uses the master-stack model - in tiling mode, starting a new application puts the new app in the "master" area on the left side of the screen, and puts the old "master" app in a tile in the vertical stack on the right side.

Due to dwm's simplicity I was quickly acquainted with the tiling layout and its keybinds; it's all really quite simple, and memorizing just a few keybinds is enough to get around. The workspaces are really layouts, there's 9 of them, plus 0 which means all of them.

Now when you do tiling you start to realise that many applications are really written with floating layouts in mind. Automatic windows resizing, as it happens all the time with tilers, often results in somewhat messed-up UIs: geany's adjustable statusbar might suddenly cover most of the window and I have to drag it down again, to give just one example. But the advantages of tiling are numerous, and maybe in time one would also learn to choose one's applications differently.

Screenshot dirty 1

There is something called EWMH compliance - without going into detail, it's a set of rules that allows desktop applications (the window manager, some sort of panel, window switcher and the applications themeselves) of UNIX-like operating systems to communicate with each other. This set of rules makes it possible to mix window managers and panels and more-or-less build some sort of custom environment.
Not so with dwm (and many other tiling window managers).
But it doesn't really matter that much; dwm's environment is very much usable - otherwise, what would be the point? But be aware that your favorite panel might become useless, amongst other things.

Apart from that I found out that I can live easily without a system tray or notification area, I use two customized versions of dmenu_run and was trying out rofi as a windows switcher. I was planning to emulate openbox' root menu by binding the rootwindow right-click to a simple, mouse-aware menu application - maybe jgmenu.

Nothing is perfect. There are, however, a few things I cannot live with, and the quest for the perfect window manager has to continue.

  • floating is clearly a second class citizen with dwm. Dialog windows are recognized and pop up more-or-less in the center, but in floating mode new windows always open in the top-left corner, there's no placement policy whatsoever. I see no functions (that I could bind to keys or mouse actions) that would help me to, even manually, place the windows side by side, or resize them. I only can resize windows by dragging the right mouse button (with a modifier). That just isn't good enough; I'm loosing ease of use from this lack.

  • Focus Follows Mouse: It's enough to move the pointer into a window to give it focus. I hate it! I like to just flick the mouse out of the way and concentrate on typing and moving about with the keyboard, but this makes it impossible. There's an "option" to enable Click To Focus by commenting out [EnterNotify] = enternotify, from dwm.c - but then I have to click a window twice to give it focus. Not so good either.

  • some applications take 100% cpu when they open a window under dwm. It has something to do with floating (conky with its own window, supertuxkart, virtualbox) but also happens in tiling layouts (extremetuxracer). In all cases the application and the X server are hogging resources together, not dwm, but it doesn't happen in any other window manager.

This last point especially made me leave dwm. I really took some time figuring it out, but could not find anything on the web and relevant forums relating to it. Fearing that the only reply would be "because these applications are broken" I did not want to start a thread myself. ☹

There is some appeal in the minimalism; you don't have many features, but soon realize that those few features are designed and chosen carefully and consciously. If that is what you are looking for, together with great speed and minimal dependencies, dwm is a great choice. I think it is designed to go together with certain applications - vim, dmenu, maybe a few more from the suckless pages. You may need to adjust your software habits to a particular frame of mind.

In other words, forget about graphical menus, icons, eyecandy, cruft in general and use the keyboard more. Embrace the source. I am admittedly only partially in that frame of mind. It's all a matter of degrees. I am not willing to give up playing supertuxkart because my window manager promotes a particular software philosphy.

This article is part of another article.
There was a thread on crunchbang forums which inspired me to do something like this myself. You can compare this article with the older dwm article from that thread.
There also is an accompanying forum thread on bunsenlabs forums.

/* appearance */
static const char *fonts[] = {
    "ubuntu:style=light:size=11"
};
static const char dmenufont[]       = "ubuntu:style=light:size=11";
static const char rofifont[]       = "monaco 10";
static const char normbordercolor[] = "#444444";
static const char normbgcolor[]     = "#222222";
static const char normfgcolor[]     = "#bbbbbb";
static const char selbordercolor[]  = "#005577";
static const char selbgcolor[]      = "#005577";
static const char selfgcolor[]      = "#eeeeee";
static const unsigned int borderpx  = 2;        /* border pixel of windows */
static const unsigned int snap      = 32;       /* snap pixel */
static const int showbar            = 1;        /* 0 means no bar */
static const int topbar             = 0;        /* 0 means bottom bar */

/* tagging */
static const char *tags[] = { "1 ", "2 ", "3 ", "4 ", "5 ", "6 ", "7 ", "8 ", "9 " };

static const Rule rules[] = {
    /* xprop(1):
     *  WM_CLASS(STRING) = instance, class
     *  WM_NAME(STRING) = title
     */
    /* class          instance        title             tags mask     isfloating   monitor */
    { "Conky",        NULL,           NULL,             0,            False,       -1 },
    { "Thunderbird",  NULL,           NULL,             1 << 8,       False,       -1 },
    { "VirtualBox",   NULL,           NULL,             1 << 2,       True,        -1 },
    { "Gimp",         NULL,           NULL,             1 << 6,       False,       -1 },
    { "Pale moon",    NULL,           NULL,             1 << 7,       False,       -1 },
    { "Firefox",      NULL,           NULL,             1 << 7,       False,       -1 },
};

/* layout(s) */
static const float mfact     = 0.5; /* factor of master area size [0.05..0.95] */
static const int nmaster     = 1;    /* number of clients in master area */
static const int resizehints = 0;    /* 1 means respect size hints in tiled resizals */

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

/* key definitions */
#define MODKEY Mod4Mask
#define TAGKEYS(KEY,TAG) \
    { MODKEY,                       KEY,      view,           {.ui = 1 << TAG} }, \
    { MODKEY|ControlMask,           KEY,      toggleview,     {.ui = 1 << TAG} }, \
    { MODKEY|ShiftMask,             KEY,      tag,            {.ui = 1 << TAG} }, \
    { MODKEY|ControlMask|ShiftMask, KEY,      toggletag,      {.ui = 1 << TAG} },

/* helper for spawning shell commands in the pre dwm-5.0 fashion */
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }

/* commands */
static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
static const char *dmenucmd[] = { "dmenu_run_hist", "-m", dmenumon, "-fn", dmenufont, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbgcolor, "-sf", selfgcolor, NULL };
static const char *pacmenucmd[] = { "pacmenu", "-m", dmenumon, "-fn", rofifont, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbgcolor, "-sf", selfgcolor, NULL };
static const char *rofiswitcher[] = { "rofi", "-font", rofifont, "-show", "window", NULL };
static const char *termcmd[]  = { "urxvt", NULL };
static const char *conkyweather[]  = { "/home/data/mygit/conky-itl-weather/startconky", "--toggle", "config/noto2.cfg", NULL };
static const char *conkykeys[]  = { "/home/user/.config/conky/keybinds/toggle", NULL };

static Key keys[] = {
    /* modifier                     key        function        argument */
    { MODKEY,                       XK_Return, spawn,          {.v = termcmd } },
    { ShiftMask,                    XK_Return, spawn,          {.v = dmenucmd } },
    { ControlMask|ShiftMask,        XK_Return, spawn,          {.v = pacmenucmd } },
    { ControlMask|Mod1Mask,         XK_w,      spawn,          {.v = conkyweather } },
    { MODKEY,                       XK_b,      togglebar,      {0} },
    { MODKEY,                       XK_j,      focusstack,     {.i = +1 } },
    { MODKEY,                       XK_k,      focusstack,     {.i = -1 } },
    { MODKEY,                       XK_i,      incnmaster,     {.i = +1 } },
    { MODKEY,                       XK_d,      incnmaster,     {.i = -1 } },
    { MODKEY,                       XK_h,      setmfact,       {.f = -0.05} },
    { MODKEY,                       XK_l,      setmfact,       {.f = +0.05} },
    { MODKEY|ShiftMask,             XK_Return, zoom,           {0} },
    { MODKEY,                       XK_Tab,    view,           {0} },
    { MODKEY,                       XK_q,      killclient,     {0} },
    { MODKEY,                       XK_t,      setlayout,      {.v = &layouts[0]} },
    { MODKEY,                       XK_f,      setlayout,      {.v = &layouts[1]} },
    { MODKEY,                       XK_m,      setlayout,      {.v = &layouts[2]} },
    { MODKEY,                       XK_space,  setlayout,      {0} },
    { MODKEY|ShiftMask,             XK_space,  togglefloating, {0} },
    { MODKEY,                       XK_0,      view,           {.ui = ~0 } },
    { MODKEY|ShiftMask,             XK_0,      tag,            {.ui = ~0 } },
    { MODKEY,                       XK_comma,  focusmon,       {.i = -1 } },
    { MODKEY,                       XK_period, focusmon,       {.i = +1 } },
    { MODKEY|ShiftMask,             XK_comma,  tagmon,         {.i = -1 } },
    { MODKEY|ShiftMask,             XK_period, tagmon,         {.i = +1 } },
    TAGKEYS(                        XK_1,                      0)
    TAGKEYS(                        XK_2,                      1)
    TAGKEYS(                        XK_3,                      2)
    TAGKEYS(                        XK_4,                      3)
    TAGKEYS(                        XK_5,                      4)
    TAGKEYS(                        XK_6,                      5)
    TAGKEYS(                        XK_7,                      6)
    TAGKEYS(                        XK_8,                      7)
    TAGKEYS(                        XK_9,                      8)
    { MODKEY|ShiftMask,             XK_q,      quit,           {0} },
    { Mod1Mask,                     XK_Tab,    spawn,          {.v = rofiswitcher } },
};

/* button definitions */
/* click can be ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */
static Button buttons[] = {
    /* click                event mask      button          function        argument */
    { ClkLtSymbol,          0,              Button1,        setlayout,      {0} },
    { ClkLtSymbol,          0,              Button3,        setlayout,      {.v = &layouts[2]} },
    { ClkWinTitle,          0,              Button2,        zoom,           {0} },
    { ClkStatusText,        0,              Button2,        spawn,          {.v = termcmd } },
    { ClkClientWin,         Mod1Mask,       Button1,        movemouse,      {0} },
    { ClkClientWin,         Mod1Mask,       Button2,        togglefloating, {0} },
    { ClkClientWin,         Mod1Mask,       Button3,        resizemouse,    {0} },
    { ClkTagBar,            0,              Button1,        view,           {0} },
    { ClkTagBar,            0,              Button3,        toggleview,     {0} },
    { ClkTagBar,            Mod1Mask,       Button1,        tag,            {0} },
    { ClkTagBar,            Mod1Mask,       Button3,        toggletag,      {0} },
};

Additionally, I also patched dwm.c to enable Click To Focus and make the taskbar a little higher:

--- src/dwm-6.1/dwm.c   2015-11-09 00:39:37.000000000 +0200
+++ .patches/dwm.c  2016-11-13 22:52:42.027370610 +0200
@@ -250,3 +250,3 @@
    [DestroyNotify] = destroynotify,
-   [EnterNotify] = enternotify,
+   //[EnterNotify] = enternotify,
    [Expose] = expose,
@@ -1562,3 +1562,3 @@
        die("no fonts could be loaded.\n");
-   bh = drw->fonts[0]->h + 2;
+   bh = drw->fonts[0]->h + 6;
    updategeom();

Next Post Previous Post