This morning I've been thinking about run time configuration of dwm. I've never especially liked the compile-time rules table in config.h. The fact that one or two programs I use happen to be broken and need a rule with isfloating = 1 doesn't seem like something that the dwm binary should be cluttered with. Clearly this is a matter of personal taste and your mileage will probably vary, but it definitely grates for me. I also have a couple of different screens and I'd like to be able to reconfigure fonts and colours (depending on the display dwm is managing) without keeping multiple dwm binaries in my home directory.
As an experiment, I've added support for customisation with X resources in a branch of my own tree. To avoid growing the code too much, I completely replaced the hard-coded rules table with a dynamic one populated from X resources, but all other settings use config.h values as defaults. Key bindings remain a matter for config.h. As far as I can see, configuring these at runtime would introduce a big, ugly symbol table into dwm for little or no gain. Doing all this grows dwm.c less than I was originally expecting, as can be seen from the patch. Most of the extra code is involved with constructing the tag and rule lists rather than just to add simpler config options like fonts and colours. I've ported my patch back to upstream dwm (hg tip) and attached it at the end of this message in case anyone else on the list is interested in experimenting along the same lines. A sample .Xresources file to merge (use xrdb -merge/-load) might be: Dwm.bar: top Dwm.font: -*-terminus-medium-r-*-*-12-*-*-*-*-*-iso10646-* Dwm.normal.border: #cccccc Dwm.normal.background: #ffffff Dwm.normal.background: #000000 Dwm.selected.border: #ff0000 Dwm.selected.background: #0000ff Dwm.selected.foreground: #ffffff Dwm.tags: 1 2 3 4 5 6 7 8 9 Dwm.rules: scratchpad browser Dwm.properties.scratchpad: URxvt:scratch Dwm.floating.scratchpad: True Dwm.properties.browser: Opera Dwm.tags.browser: 2 Cheers, Chris. diff -r 92c19c929a59 config.def.h --- a/config.def.h Sun Sep 23 18:50:04 2007 +0200 +++ b/config.def.h Mon Sep 24 14:28:26 2007 +0100 @@ -1,4 +1,8 @@ /* See LICENSE file for copyright and license details. */ + +/* resources */ +#define RESNAME "dwm" +#define RESCLASS "Dwm" /* appearance */ #define BARPOS BarTop /* BarBot, BarOff */ @@ -12,14 +16,7 @@ #define SELFGCOLOR "#ffffff" /* tagging */ -const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "www", NULL }; -Rule rules[] = { - /* class:instance:title regex tags regex isfloating */ - { "Firefox", "www", False }, - { "Gimp", NULL, True }, - { "MPlayer", NULL, True }, - { "Acroread", NULL, True }, -}; +#define TAGS "1 2 3 4 5 6 7 8 9" /* layout(s) */ #define ISTILE isarrange(tile) /* || isarrange(<custom>) */ diff -r 92c19c929a59 dwm.c --- a/dwm.c Sun Sep 23 18:50:04 2007 +0200 +++ b/dwm.c Mon Sep 24 14:28:26 2007 +0100 @@ -40,6 +40,7 @@ #include <X11/Xatom.h> #include <X11/Xlib.h> #include <X11/Xproto.h> +#include <X11/Xresource.h> #include <X11/Xutil.h> /* macros */ @@ -132,6 +133,8 @@ void *emallocz(unsigned int size); void *emallocz(unsigned int size); void enternotify(XEvent *e); void eprint(const char *errstr, ...); +void *erealloc(void *res, unsigned int size); +char *estrdup(const char *s); void expose(XEvent *e); void floating(void); /* default floating layout */ void focus(Client *c); @@ -139,7 +142,10 @@ void focusprev(const char *arg); void focusprev(const char *arg); Client *getclient(Window w); unsigned long getcolor(const char *colstr); +char *getresource(const char *resource, char *defval); +void getrules(void); long getstate(Window w); +void gettags(void); Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); void grabbuttons(Client *c, Bool focused); unsigned int idxoftag(const char *tag); @@ -190,11 +196,11 @@ void zoom(const char *arg); void zoom(const char *arg); /* variables */ -char stext[256]; +char stext[256], **tags; double mwfact; int screen, sx, sy, sw, sh, wax, way, waw, wah; int (*xerrorxlib)(Display *, XErrorEvent *); -unsigned int bh, bpos, ntags; +unsigned int bh, bpos, ntags = 0; unsigned int blw = 0; unsigned int ltidx = 0; /* default */ unsigned int nlayouts = 0; @@ -226,7 +232,9 @@ Display *dpy; Display *dpy; DC dc = {0}; Window barwin, root; +Rule *rules; Regs *regs = NULL; +XrmDatabase xrdb; /* configuration, allows nested code to access above variables */ #include "config.h" @@ -402,7 +410,6 @@ compileregs(void) { if(regs) return; - nrules = sizeof rules / sizeof rules[0]; regs = emallocz(nrules * sizeof(Regs)); for(i = 0; i < nrules; i++) { if(rules[i].prop) { @@ -638,6 +645,13 @@ emallocz(unsigned int size) { return res; } +void * +erealloc(void *res, unsigned int size) { + if (!(res = res ? realloc(res, size) : malloc(size))) + eprint("fatal: could not realloc() %u bytes\n", size); + return res; +} + void enternotify(XEvent *e) { Client *c; @@ -661,6 +675,15 @@ eprint(const char *errstr, ...) { vfprintf(stderr, errstr, ap); va_end(ap); exit(EXIT_FAILURE); +} + +char * +estrdup(const char *s) { + char *t; + t = strdup(s); + if (!t) + eprint("fatal: strdup failed\n"); + return t; } void @@ -755,6 +778,53 @@ getcolor(const char *colstr) { if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) eprint("error, cannot allocate color '%s'\n", colstr); return color.pixel; +} + +char * +getresource(const char *resource, char *defval) { + static char name[256], class[256], *type; + XrmValue value; + snprintf(name, sizeof(name), "%s.%s", RESNAME, resource); + snprintf(class, sizeof(class), "%s.%s", RESCLASS, resource); + XrmGetResource(xrdb, name, class, &type, &value); + if(value.addr) + return value.addr; + return defval; +} + +void +getrules(void) { + char *line, *p, *rule, *prop, *tags, *floating; + static char name[256], class[256], *type; + XrmValue value; + line = p = estrdup(getresource("rules", "")); + while(rule = strsep(&p, " \t\n")) { + if(!rule[0]) + continue; + snprintf(name, sizeof(name), "%s.properties.%s", RESNAME, rule); + snprintf(class, sizeof(class), "%s.properties.%s", RESCLASS, rule); + XrmGetResource(xrdb, name, class, &type, &value); + if (!value.addr || !value.addr[0]) + continue; + rules = erealloc(rules, sizeof(Rule) * (++nrules)); + rules[nrules - 1].prop = estrdup(value.addr); + snprintf(name, sizeof(name), "%s.tags.%s", RESNAME, rule); + snprintf(class, sizeof(class), "%s.tags.%s", RESCLASS, rule); + XrmGetResource(xrdb, name, class, &type, &value); + if (value.addr && value.addr[0] && strcasecmp(value.addr, "null")) + rules[nrules - 1].tags = estrdup(value.addr); + else + rules[nrules - 1].tags = NULL; + snprintf(name, sizeof(name), "%s.floating.%s", RESNAME, rule); + snprintf(class, sizeof(class), "%s.floating.%s", RESCLASS, rule); + XrmGetResource(xrdb, name, class, &type, &value); + rules[nrules - 1].isfloating = (value.addr && value.addr[0] + && strcasecmp(value.addr, "false") + && strcasecmp(value.addr, "no") + && strcasecmp(value.addr, "off") + && strcmp(value.addr, "0")); + } + free(line); } long @@ -773,6 +843,20 @@ getstate(Window w) { result = *p; XFree(p); return result; +} + +void +gettags(void) { + char *line, *tag; + line = estrdup(getresource("tags", TAGS)); + while(tag = strsep(&line, " \t\n")) { + if(!tag[0]) + continue; + tags = erealloc(tags, sizeof(char *) * ++ntags); + tags[ntags - 1] = tag; + } + if (ntags == 0) + eprint("dwm: no tags defined\n"); } Bool @@ -1420,6 +1504,7 @@ setup(void) { setup(void) { int d; unsigned int i, j, mask; + char *s; Window w; XModifierKeymap *modmap; XSetWindowAttributes wa; @@ -1461,23 +1546,32 @@ setup(void) { XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa); XSelectInput(dpy, root, wa.event_mask); + /* init resource database */ + XrmInitialize(); + s = XResourceManagerString(dpy); + xrdb = XrmGetStringDatabase(s); + free(s); + + /* init rules */ + getrules(); + + /* init tags */ + gettags(); + compileregs(); + seltags = emallocz(sizeof(Bool) * ntags); + seltags[0] = True; + /* grab keys */ keypress(NULL); - /* init tags */ - compileregs(); - for(ntags = 0; tags[ntags]; ntags++); - seltags = emallocz(sizeof(Bool) * ntags); - seltags[0] = True; - /* init appearance */ - dc.norm[ColBorder] = getcolor(NORMBORDERCOLOR); - dc.norm[ColBG] = getcolor(NORMBGCOLOR); - dc.norm[ColFG] = getcolor(NORMFGCOLOR); - dc.sel[ColBorder] = getcolor(SELBORDERCOLOR); - dc.sel[ColBG] = getcolor(SELBGCOLOR); - dc.sel[ColFG] = getcolor(SELFGCOLOR); - initfont(FONT); + dc.norm[ColBorder] = getcolor(getresource("normal.border", NORMBORDERCOLOR)); + dc.norm[ColBG] = getcolor(getresource("normal.background", NORMBGCOLOR)); + dc.norm[ColFG] = getcolor(getresource("normal.foreground", NORMFGCOLOR)); + dc.sel[ColBorder] = getcolor(getresource("selected.border", SELBORDERCOLOR)); + dc.sel[ColBG] = getcolor(getresource("selected.background", SELBGCOLOR)); + dc.sel[ColFG] = getcolor(getresource("selected.foreground", SELFGCOLOR)); + initfont(getresource("font", FONT)); dc.h = bh = dc.font.height + 2; /* init layouts */ @@ -1490,7 +1584,15 @@ setup(void) { } /* init bar */ - bpos = BARPOS; + s = getresource("bar", NULL); + if(!s || !s[0] || strcasecmp(s, "default")) + bpos = BARPOS; + else if(strcasecmp(s, "top") == 0) + bpos = BarTop; + else if(strcasecmp(s, "bottom") == 0) + bpos = BarBot; + else + bpos = BarOff; wa.override_redirect = 1; wa.background_pixmap = ParentRelative; wa.event_mask = ButtonPressMask | ExposureMask; @@ -1506,6 +1608,9 @@ setup(void) { XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter); if(!dc.font.set) XSetFont(dpy, dc.gc, dc.font.xfont->fid); + + /* free resource database */ + XrmDestroyDatabase(xrdb); /* multihead support */ selscreen = XQueryPointer(dpy, root, &w, &w, &d, &d, &d, &d, &mask);