summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--calmwm.c23
-rw-r--r--calmwm.h52
-rw-r--r--conf.c291
-rw-r--r--cwm.1131
-rw-r--r--cwmrc28
-rw-r--r--cwmrc.5158
-rw-r--r--group.c6
-rw-r--r--kbfunc.c8
-rw-r--r--parse.y583
-rw-r--r--xevents.c5
11 files changed, 870 insertions, 422 deletions
diff --git a/Makefile b/Makefile
index 2af7f16..fbc60e6 100644
--- a/Makefile
+++ b/Makefile
@@ -8,16 +8,17 @@ PROG=		cwm
 
 SRCS=		calmwm.c screen.c xmalloc.c client.c grab.c search.c \
 		util.c xutil.c conf.c input.c xevents.c group.c \
-		kbfunc.c font.c
+		kbfunc.c font.c parse.y
 
-CPPFLAGS+=	-I${X11BASE}/include -I${X11BASE}/include/freetype2
+CPPFLAGS+=	-I${X11BASE}/include -I${X11BASE}/include/freetype2 -I${.CURDIR}
 
 LDADD+=		-L${X11BASE}/lib -lXft -lXrender -lX11 -lXau -lXdmcp \
 		-lfontconfig -lexpat -lfreetype -lz -lX11 -lXau -lXdmcp -lXext
 
 MANDIR=		${X11BASE}/man/cat
+MAN=		cwm.1 cwmrc.5
 
-CLEANFILES=	cwm.cat1
+CLEANFILES=	cwm.cat1 cwmrc.cat5
 
 obj: _xenocara_obj
 
diff --git a/calmwm.c b/calmwm.c
index 2048502..4f40107 100644
--- a/calmwm.c
+++ b/calmwm.c
@@ -38,8 +38,7 @@ struct client_ctx_q		 Clientq;
 int				 Doshape, Shape_ev;
 int				 Starting;
 struct conf			 Conf;
-struct fontdesc                 *DefaultFont;
-char                            *DefaultFontName;
+struct fontdesc                 *DefaultFont = NULL;
 
 /* From TWM */
 #define gray_width 2
@@ -53,23 +52,18 @@ int
 main(int argc, char **argv)
 {
 	int ch;
-	int conf_flags = 0;
+	const char *conffile = NULL;
 
 	char *display_name = NULL;
 
-	DefaultFontName = "sans-serif:pixelsize=14:bold";
-
-	while ((ch = getopt(argc, argv, "d:sf:")) != -1) {
+	while ((ch = getopt(argc, argv, "c:d:")) != -1) {
 		switch (ch) {
+		case 'c':
+			conffile = optarg;
+			break;
 		case 'd':
 			display_name = optarg;
 			break;
-		case 's':
-			conf_flags |= CONF_STICKY_GROUPS;
-			break;
-		case 'f':
-			DefaultFontName = xstrdup(optarg);
-			break;
 		default:
 			usage();
 		}
@@ -87,8 +81,7 @@ main(int argc, char **argv)
 	group_init();
 
 	Starting = 1;
-	conf_setup(&Conf);
-	Conf.flags |= conf_flags;
+	conf_setup(&Conf, conffile);
 	client_setup();
 	x_setup(display_name);
 	Starting = 0;
@@ -209,7 +202,7 @@ x_setupscreen(struct screen_ctx *sc, u_int which)
 	    GCLineWidth|GCSubwindowMode, &gv);
 
 	font_init(sc);
-	DefaultFont = font_getx(sc, DefaultFontName);
+	DefaultFont = font_getx(sc, Conf.DefaultFontName);
 
 	/*
 	 * XXX - this should *really* be in screen_init().  ordering
diff --git a/calmwm.h b/calmwm.h
index 309d2e3..5317f17 100644
--- a/calmwm.h
+++ b/calmwm.h
@@ -30,6 +30,8 @@
 #define MIN(x, y) ((x) < (y) ? (x) : (y))
 #define MAX(x, y) ((x) > (y) ? (x) : (y))
 
+#define	CONFFILE	".cwmrc"
+
 enum conftype {
 	CONF_BWIDTH, CONF_IGNORE,
 };
@@ -164,6 +166,12 @@ struct client_ctx {
 
 TAILQ_HEAD(client_ctx_q, client_ctx);
 
+static char *shortcut_to_name[] = {
+	"XXX", "one", "two", "three",
+	"four", "five", "six", "seven",
+	"eight", "nine"
+};
+
 struct group_ctx {
 	TAILQ_ENTRY(group_ctx) entry;
 	struct client_ctx_q  clients;
@@ -204,6 +212,20 @@ enum directions {
 	CWM_UP=0, CWM_DOWN, CWM_LEFT, CWM_RIGHT,
 };
 
+/*
+ * Match a window.
+ */
+#define CONF_MAX_WINTITLE 256
+#define CONF_IGNORECASE   0x01
+struct winmatch {
+	TAILQ_ENTRY(winmatch) entry;
+
+	char title[CONF_MAX_WINTITLE];
+	int  opts;
+};
+
+TAILQ_HEAD(winmatch_q, winmatch);
+
 /* for cwm_exec */
 #define	CWM_EXEC_PROGRAM	0x1
 #define	CWM_EXEC_WM		0x2
@@ -236,16 +258,20 @@ TAILQ_HEAD(cmd_q, cmd);
 
 /* Global configuration */
 struct conf {
-	struct keybinding_q keybindingq;
-	struct autogroupwin_q autogroupq;
-	char	      menu_path[MAXPATHLEN];
-	struct cmd_q  cmdq;
+	struct keybinding_q	 keybindingq;
+	struct autogroupwin_q	 autogroupq;
+	struct winmatch_q	 ignoreq;
+	char			 conf_path[MAXPATHLEN];
+	struct cmd_q		 cmdq;
 
-	int	      flags;
+	int			 flags;
 #define CONF_STICKY_GROUPS	0x0001
 
-	char          termpath[MAXPATHLEN];
-	char          lockpath[MAXPATHLEN];
+	char			 termpath[MAXPATHLEN];
+	char			 lockpath[MAXPATHLEN];
+
+#define	DEFAULTFONTNAME		"sans-serif:pixelsize=14:bold"
+	char			*DefaultFontName;
 };
 
 /* Menu stuff */
@@ -397,21 +423,15 @@ struct screen_ctx *screen_current(void);
 void               screen_updatestackingorder(void);
 void               screen_infomsg(char *);
 
-void  conf_setup(struct conf *);
+void  conf_setup(struct conf *, const char *);
 int   conf_get_int(struct client_ctx *, enum conftype);
 void  conf_client(struct client_ctx *);
 void  conf_bindkey(struct conf *, void (*)(struct client_ctx *, void *),
           int, int, int, void *);
 void  conf_bindname(struct conf *, char *, char *);
 void  conf_unbind(struct conf *, struct keybinding *);
-void  conf_parsekeys(struct conf *, char *);
-void  conf_parsesettings(struct conf *, char *);
-void  conf_parseignores(struct conf *, char *);
-void  conf_parseautogroups(struct conf *, char *);
-void  conf_cmd_clear(struct conf *);
-int   conf_cmd_changed(char *);
-void  conf_cmd_populate(struct conf *, char *);
-void  conf_cmd_refresh(struct conf *c);
+int   conf_changed(char *);
+void  conf_reload(struct conf *c);
 char *conf_get_str(struct client_ctx *, enum conftype);
 
 void kbfunc_client_lower(struct client_ctx *, void *);
diff --git a/conf.c b/conf.c
index 5371cec..7193d82 100644
--- a/conf.c
+++ b/conf.c
@@ -28,58 +28,7 @@
             ((tsp)->tv_sec cmp (usp)->tv_sec))
 #endif
 
-#define CONF_MAX_WINTITLE 256
-#define CONF_IGNORECASE   0x01
-
-
-/*
- * Match a window.
- */
-struct winmatch {
-	TAILQ_ENTRY(winmatch) entry;
-
-	char title[CONF_MAX_WINTITLE];
-	int  opts;
-};
-
-TAILQ_HEAD(winmatch_q, winmatch);
-struct winmatch_q ignoreq;
-
-/* XXX - until we get a real configuration parser. */
-#define WINMATCH_ADD(queue, str) do {			\
-	struct winmatch *wm;				\
-	XCALLOC(wm, struct winmatch);		\
-	strlcpy(wm->title, str, sizeof(wm->title));	\
-	wm->opts |= CONF_IGNORECASE;			\
-	TAILQ_INSERT_TAIL(queue, wm, entry);		\
-} while (0)
-
-/* Initializes the command menu */
-
-void
-conf_cmd_init(struct conf *c)
-{
-	TAILQ_INIT(&c->cmdq);
-}
-
-/* Removes all static entries */
-
-void
-conf_cmd_clear(struct conf *c)
-{
-	struct cmd *cmd, *next;
-
-	for (cmd = TAILQ_FIRST(&c->cmdq); cmd != NULL; cmd = next) {
-		next = TAILQ_NEXT(cmd, entry);
-
-		/* Do not remove static entries */
-		if (cmd->flags & CMD_STATIC)
-			continue;
-
-		TAILQ_REMOVE(&c->cmdq, cmd, entry);
-		free(cmd);
-	}
-}
+extern struct screen_ctx	*Curscreen;
 
 /* Add an command menu entry to the end of the menu */
 void
@@ -102,94 +51,45 @@ conf_cmd_add(struct conf *c, char *image, char *label, int flags)
 }
 
 int
-conf_cmd_changed(char *path)
+conf_changed(char *path)
 {
-#ifdef __OpenBSD__
 	static struct timespec old_ts;
-#else
-	static time_t old_time;
-#endif
 	struct stat sb;
 	int changed;
 
-	/* If the directory does not exist we pretend that nothing changed */
-	if (stat(path, &sb) == -1 || !(sb.st_mode & S_IFDIR))
+	/* If the file does not exist we pretend that nothing changed */
+	if (stat(path, &sb) == -1 || !(sb.st_mode & S_IFREG))
 		return (0);
 
-#ifdef __OpenBSD__
 	changed = !timespeccmp(&sb.st_mtimespec, &old_ts, ==);
 	old_ts = sb.st_mtimespec;
-#else
-	changed = old_time != sb.st_mtime;
-	old_time = sb.st_mtime;
-#endif
 
 	return (changed);
 }
 
 void
-conf_cmd_populate(struct conf *c, char *path)
+conf_reload(struct conf *c)
 {
-	DIR *dir;
-	struct dirent *file;
-	char fullname[PATH_MAX];
-	int off;
-
-	if (strlen(path) >= sizeof (fullname) - 2)
-		errx(1, "directory name too long");
-
-	dir = opendir(path);
-	if (dir == NULL)
-		err(1, "opendir");
-
-	strlcpy(fullname, path, sizeof (fullname));
-	off = strlen(fullname);
-	if (fullname[off - 1] != '/') {
-		strlcat(fullname, "/", sizeof(fullname));
-		off++;
-	}
-
-	while ((file = readdir(dir)) != NULL) {
-		char *filename = file->d_name;
-                if (filename[0] == '.')
-			continue;
-
-		strlcpy(fullname + off, filename, sizeof(fullname) - off);
-
-		/* Add a dynamic entry to the command menu */
-		conf_cmd_add(c, fullname, filename, 0);
-	}
-
-	closedir(dir);
-
-}
+	if (!conf_changed(c->conf_path))
+		return;
 
-void
-conf_cmd_refresh(struct conf *c)
-{
-	if (!conf_cmd_changed(c->menu_path))
+	if (parse_config(c->conf_path, c) == -1) {
+		warnx("config file %s has errors, not reloading", c->conf_path);
 		return;
+	}
 
-	conf_cmd_clear(c);
-	conf_cmd_populate(c, c->menu_path);
+	DefaultFont = font_getx(Curscreen, c->DefaultFontName);
 }
 
 void
-conf_setup(struct conf *c)
+conf_init(struct conf *c)
 {
- 	char dir_keydefs[MAXPATHLEN];
- 	char dir_settings[MAXPATHLEN];
- 	char dir_ignored[MAXPATHLEN];
-	char dir_autogroup[MAXPATHLEN];
-	char *home = getenv("HOME");
-
-	if (home == NULL)
-		errx(1, "No HOME directory.");
-	snprintf(c->menu_path, sizeof(c->menu_path), "%s/.calmwm", home);
-
-	conf_cmd_init(c);
+	c->flags = 0;
 
+	TAILQ_INIT(&c->ignoreq);
+	TAILQ_INIT(&c->cmdq);
         TAILQ_INIT(&c->keybindingq);
+	TAILQ_INIT(&c->autogroupq);
 
 	conf_bindname(c, "CM-Return", "terminal");
 	conf_bindname(c, "CM-Delete", "lock");
@@ -247,39 +147,31 @@ conf_setup(struct conf *c)
 	conf_bindname(c, "CS-Up", "bigptrmoveup");
 	conf_bindname(c, "CS-Right", "bigptrmoveright");
 
-        snprintf(dir_keydefs, sizeof(dir_keydefs), "%s/.calmwm/.keys", home);
-        if (dirent_isdir(dir_keydefs))
-                conf_parsekeys(c, dir_keydefs);
-
- 	snprintf(dir_settings, sizeof(dir_settings),
-	    "%s/.calmwm/.settings", home);
- 	if (dirent_isdir(dir_settings))
- 		conf_parsesettings(c, dir_settings);
-
-	TAILQ_INIT(&ignoreq);
-
-	snprintf(dir_ignored, sizeof(dir_ignored), "%s/.calmwm/.ignore", home);
-	if (dirent_isdir(dir_ignored))
-		conf_parseignores(c, dir_ignored);
-	else {
-		WINMATCH_ADD(&ignoreq, "XMMS");
-		WINMATCH_ADD(&ignoreq, "xwi");
-		WINMATCH_ADD(&ignoreq, "xapm");
-		WINMATCH_ADD(&ignoreq, "xclock");
-	}
+	/* Default term/lock */
+	strlcpy(c->termpath, "xterm", sizeof(c->termpath));
+	strlcpy(c->lockpath, "xlock", sizeof(c->lockpath));
 
-	TAILQ_INIT(&c->autogroupq);
+	c->DefaultFontName = DEFAULTFONTNAME;
+}
+
+void
+conf_setup(struct conf *c, const char *conffile)
+{
+	if (conffile == NULL) {
+		char *home = getenv("HOME");
 
-	snprintf(dir_autogroup, sizeof(dir_autogroup),
-	    "%s/.calmwm/.autogroup", home);
-	if (dirent_isdir(dir_autogroup))
-		conf_parseautogroups(c, dir_autogroup);
+		if (home == NULL)
+			errx(1, "No HOME directory.");
 
-	c->flags = 0;
+		snprintf(c->conf_path, sizeof(c->conf_path), "%s/%s", home,
+		    CONFFILE);
+	}
+	else
+		snprintf(c->conf_path, sizeof(c->conf_path), "%s", conffile);
 
-	/* Default term/lock */
-	strlcpy(Conf.termpath, "xterm", sizeof(Conf.termpath));
-	strlcpy(Conf.lockpath, "xlock", sizeof(Conf.lockpath));
+	conf_init(c);
+
+	(void)parse_config(c->conf_path, c);
 }
 
 int
@@ -294,7 +186,7 @@ conf_get_int(struct client_ctx *cc, enum conftype ctype)
 	/* Can wname be NULL? */
 
 	if (wname != NULL) {
-		TAILQ_FOREACH(wm, &ignoreq, entry) {
+		TAILQ_FOREACH(wm, &Conf.ignoreq, entry) {
 			int (*cmpfun)(const char *, const char *, size_t) =
 			    wm->opts & CONF_IGNORECASE ? strncasecmp : strncmp;
 			if ((*cmpfun)(wm->title, wname, strlen(wm->title)) == 0) {
@@ -393,37 +285,6 @@ struct {
 };
 
 void
-conf_parsekeys(struct conf *c, char *filename)
-{
-	DIR *dir;
-	struct dirent *ent;
-	char buffer[MAXPATHLEN];
-	char current_file[MAXPATHLEN];
-
-	dir = opendir(filename);
-	while ((ent = readdir(dir)) != NULL) {
-		if (ent->d_name[0] == '.')
-			continue;
-
-		snprintf(current_file, sizeof(current_file),
-		    "%s/%s", filename, ent->d_name);
-		if (strchr(ent->d_name, '-') == NULL && ent->d_name[0] != '[')
-			continue;
-		if (!dirent_islink(current_file))
-			continue;
-
-
-		memset(buffer, 0, MAXPATHLEN);
-		if (readlink(current_file, buffer, MAXPATHLEN) < 0)
-			continue;
-
-		conf_bindname(c, ent->d_name, buffer);
-	}
-
-	closedir(dir);
-}
-
-void
 conf_bindname(struct conf *c, char *name, char *binding)
 {
 	int iter;
@@ -518,79 +379,3 @@ void conf_unbind(struct conf *c, struct keybinding *unbind)
 			TAILQ_REMOVE(&c->keybindingq, key, entry);
 	}
 }
-
-void
-conf_parsesettings(struct conf *c, char *filename)
-{
-	DIR *dir;
-	struct dirent *ent;
-
-	dir = opendir(filename);
-	while ((ent = readdir(dir)) != NULL) {
-		if (ent->d_name[0] == '.')
-			continue;
-		if (strncmp(ent->d_name, "sticky", 7)==0)
-			Conf.flags |= CONF_STICKY_GROUPS;
-	}
-	closedir(dir);
-}
-
-void
-conf_parseignores(struct conf *c, char *filename)
-{
-	DIR *dir;
-	struct dirent *ent;
-
-	dir = opendir(filename);
-	while ((ent = readdir(dir)) != NULL) {
-		if (ent->d_name[0] == '.')
-			continue;
-		WINMATCH_ADD(&ignoreq, ent->d_name);
-	}
-
-	closedir(dir);
-}
-
-void
-conf_parseautogroups(struct conf *c, char *filename)
-{
-	DIR *dir;
-	struct dirent *ent;
-	struct autogroupwin *aw;
-	char current_file[MAXPATHLEN], *p;
-	char group[CALMWM_MAXNAMELEN];
-	int len;
-
-	dir = opendir(filename);
-	while ((ent = readdir(dir)) != NULL) {
-		if (ent->d_name[0] == '.')
-			continue;
-
-		snprintf(current_file, sizeof(current_file),
-		    "%s/%s", filename, ent->d_name);
-		if (!dirent_islink(current_file))
-			continue;
-
-		if ((len = readlink(current_file,
-			    group, sizeof(group) - 1)) < 0)
-			continue;
-		group[len] = '\0';
-
-		XCALLOC(aw, struct autogroupwin);
-		
-		if ((p = strchr(ent->d_name, ',')) == NULL) {
-			aw->name = NULL;
-			aw->class = xstrdup(ent->d_name);
-		} else {
-			*(p++) = '\0';
-			aw->name = xstrdup(ent->d_name);
-			aw->class = xstrdup(p);
-		}
-		aw->group = xstrdup(group);
-
-		TAILQ_INSERT_TAIL(&c->autogroupq, aw, entry);
-	}
-
-	closedir(dir);
-
-}
diff --git a/cwm.1 b/cwm.1
index 5f2a01e..91509cb 100644
--- a/cwm.1
+++ b/cwm.1
@@ -15,7 +15,7 @@
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
 .\" The following requests are required for all man pages.
-.Dd June 29, 2007
+.Dd $Mdocdate$
 .Dt CWM 1
 .Os
 .Sh NAME
@@ -24,9 +24,8 @@
 .Sh SYNOPSIS
 .\" For a program:  program [-abc] file ...
 .Nm cwm
-.Op Fl s
 .Op Fl d Ar display
-.Op Fl f Ar fontname
+.Op Fl c Ar file
 .Sh DESCRIPTION
 .Nm
 is a window manager for X11 which contains many features that
@@ -132,17 +131,9 @@ are as follows:
 .Bl -tag -width Ds
 .It Fl d Ar display
 Specify the display to use.
-.It Fl f Ar fontname
-Makes the
-.Xr Xft 3
-font string
-.Ar fontname
-the default font.
-.It Fl s
-Set sticky group mode on.
-The default behavior for new windows is to not assign any group.
-This changes the default behavior to assigning the currently selected
-group to any newly created windows.
+.It Fl c Ar file
+Specify the config file to use.  Defaults to
+.Pa ~/.cwmrc .
 .El
 .Sh POINTER MOVEMENT
 The pointer can be moved with the use of the keyboard through bindings.
@@ -205,7 +196,7 @@ perform operations on the entire group instead of just one window.
 Currently, the only operation that is supported is to hide and unhide
 the grouped windows.
 Together with the
-.Fl s
+.Pa sticky
 option, this can be used to emulate virtual desktops.
 .Pp
 To edit groups, use the group selection commands to toggle membership
@@ -224,7 +215,7 @@ Show list of currently defined groups.
 Clicking on an item will hide/unhide that group.
 .It M3
 Show list of applications as defined in
-.Pa ~/.calmwm .
+.Pa ~/.cwmrc .
 Clicking on an item will spawn that application.
 .El
 .Sh ENVIRONMENT
@@ -237,111 +228,9 @@ option is given.
 .El
 .Sh FILES
 .Bl -tag -width Ds
-.It Pa ~/.calmwm
-Any directory entries here are shown in the application menu.
-When it is selected, the image is executed with
-.Xr execve 2 .
-One use of this is to create symbolic links for your favorite
-applications in this directory using
-.Xr ln 1 .
-.Pp
-The entries
-.Nm term
-and
-.Nm lock
-have a special meaning.
-When they exist they point to the terminal program and screen locking
-programs used by the keybindings specified above.
-The defaults for these are
-.Xr xterm 1
-and
-.Xr xlock 1 ,
-respectively.
-.It Pa ~/.calmwm/.autogroup
-Symlinks in this directory are read upon startup and control the
-automatic grouping feature, which is based on the window name and class
-properties.
-To obtain the name and class of a window, use
-.Ql xprop WM_CLASS ,
-then click on the window.
-The first quoted string is the window name; the second one is the
-window class.
-.Pp
-The name of a link can be the window class, or the window class and name
-separated by a comma.
-The link target is a group name (one, two, \&..., nine).
-For example, to make all windows in the
-.Xr xterm 1
-class go to the third group:
-.Bd -literal -offset indent
-$ ln -s three ~/.calmwm/.autogroup/XTerm
-.Ed
-.It Pa ~/.calmwm/.settings
-Files in this directory cause various configuration options to be
-set or unset.
-Currently the only setting availiable is whether or not sticky groups
-are activated.
-To activate sticky groups create a file in this directory with the name
-``sticky''.
-.It Pa ~/.calmwm/.ignore
-Any files in this directory cause
-.Nm
-to ignore programs by that name by not drawing borders around them.
-For example the command
-.Bd -literal -offset indent
-$ ln -s three ~/.calmwm/.ignore/xclock
-.Ed
-will cause any instances of
-.Xr xclock 1
-to not have borders.
-.It Pa ~/.calmwm/.keys
-Symlinks in this directory cause the creation of keyboard shortcuts.
-The default shortcuts will always be created. In case of conflict, 
-user-defined shortcuts take precidence.
-The name of a link here is first the modifier keys, followed by a ``-''.
-The following modifiers are recognised:
-.Bl -tag -width Ds
-.It Pa C
-The Control key.
-.It Pa M
-The Meta key.
-.It Pa S
-The Shift key.
-.It Pa 2
-The Mod2 key.
-.It Pa 3
-The Mod3 key.
-.It Pa 4
-The Mod4 key (normally the windows key).
-.El
-The ``-'' should be followed by either a keysym name, taken from
-.Pa /usr/X11R6/include/X11/keysymdef.h ,
-or a numerical keycode value enclosed in ``[]''.
-The target of the link should be either the name of a task from the
-``name_to_kbfunc''
-structure in
-.Pa conf.c ,
-or, alternatively it should be the commandline that is wished to be executed.
-A special case is the ``unmap'' keyword, which causes any bindings using the 
-named shortcut to be removed. This can be used to remove a binding which conflicts
-with an application.
-For example, to cause
-.Ic C-M-r
-to add a label to a window:
-.Bd -literal -offset indent
-$ ln -s "label" ~/.calmwm/.keys/CM-r
-.Ed
-Launch an xterm running
-.Xr top 1
-with C-S-Enter:
-.Bd -literal -offset indent
-$ ln -s "/usr/X11R6/bin/xterm -e top" ~/.calmwm/.keys/CS-Return
-.Ed
-Remove a keybinding for Mod4-o
-.Bd -literal -offset indent
-$ ln -s "unmap" 4-o
-.Ed
-.El
+.It Pa ~/.cwmrc
+.Sh SEE ALSO
+.Xr cwmrc 5
 .Sh AUTHORS
 .An -nosplit
 .Pp
diff --git a/cwmrc b/cwmrc
new file mode 100644
index 0000000..f3c7e65
--- /dev/null
+++ b/cwmrc
@@ -0,0 +1,28 @@
+# $OpenBSD$
+
+# Makes the Xft(3) font string fontname the default font
+#fontname "sans-serif:pixelsize=14:bold"
+
+# Set sticky group mode on
+#sticky no
+
+# Any entry here is shown in the application menu
+#command firefox	firefox
+#command xmms		xmms
+#command top		"xterm -e top"
+
+# Autogroup definition
+#autogroup 3 "aterm,XTerm"
+#autogroup 3 "xterm,XTerm"
+
+# Cause cwm to ignore programs by that name by not drawing borders around them.
+#ignore XMMS
+#ignore xwi
+#ignore xapm
+#ignore xclock
+
+# Keys
+#bind CM-r	"label"
+#bind CS-Return	"xterm -e top"
+#bind 4-o	"unmap"
+
diff --git a/cwmrc.5 b/cwmrc.5
new file mode 100644
index 0000000..5695057
--- /dev/null
+++ b/cwmrc.5
@@ -0,0 +1,158 @@
+.\"	$OpenBSD$
+.\"
+.\" Copyright (c) 2004,2005 Marius Aamodt Eriksen <marius@monkey.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" The following requests are required for all man pages.
+.Dd $Mdocdate$
+.Dt CWMRC 1
+.Os
+.Sh NAME
+.Nm cwmrc
+.Nd calm window manager configuration file
+.Sh DESCRIPTION
+The
+.Xr cwm 1
+window manager configuration file format.
+.Sh OPTIONS
+There are quite a few settings that affect the operation of
+.Xr cwm 1 .
+.Pp
+The following options are accepted in the configuration file:
+.Pp
+.Bl -tag -width Ds
+.It Ic fontname Ar font
+Makes the
+.Xr Xft 3
+font string
+.Ar font
+the default font.
+.It Ic sticky Ic yes Ns \&| Ns Ic no
+Set sticky group mode on.
+The default behavior for new windows is to not assign any group.
+This changes the default behavior to assigning the currrently selected
+group to any newly created windows.
+.It Ic command Ar name Ar path
+Every command entry is shown in the application menu.
+When it is selected, the image is executed with
+.Xr execve 2 .
+.Pp
+The entries
+.Nm term
+and
+.Nm lock
+have a special meaning.
+When they exist they point to the terminal program and screen locking
+programs used by the keybindings specified above.
+The defaults for these are
+.Xr xterm 1
+and
+.Xr xlock 1 ,
+respectively.
+.It Ic autogroup Ar group Dq windowclass
+.It Ic autogroup Ar group Dq windowclass,windowname
+Autogroups are read upon startup and control the
+automatic grouping feature, which is based on the window name and class
+properties.
+The group is a number between 1 and 9.
+.Pp
+To obtain the name and class of a window, use
+.Ql xprop WM_CLASS ,
+then click on the window.
+The first quoted string is the window name; the second one is the
+window class.
+.Pp
+For example, to make all windows in the
+.Xr xterm 1
+class go to the third group:
+.Bd -literal -offset indent
+autogroup 3 XTerm
+.Ed
+.It Ic ignore Ar program
+Ignore programs by that name by not drawing borders around them.
+For example the command
+.Bd -literal -offset indent
+ignore xclock
+.Ed
+will cause any instances of
+.Xr xclock 1
+to not have borders.
+.It Ic bind Ar keys Ar command
+Cause the creation of keyboard shortcuts.
+The default shortcuts will always be created. In case of conflict, 
+user-defined shortcuts take precidence.
+The modifier keys come first, followed by a ``-''.
+The following modifiers are recognised:
+.Bl -tag -width Ds
+.It Pa C
+The Control key.
+.It Pa M
+The Meta key.
+.It Pa S
+The Shift key.
+.It Pa 2
+The Mod2 key.
+.It Pa 3
+The Mod3 key.
+.It Pa 4
+The Mod4 key (normally the windows key).
+.El
+The ``-'' should be followed by either a keysym name, taken from
+.Pa /usr/X11R6/include/X11/keysymdef.h ,
+or a numerical keycode value enclosed in ``[]''.
+The command should be either the name of a task from the
+``name_to_kbfunc''
+structure in
+.Pa conf.c ,
+or, alternatively it should be the commandline that is wished to be executed.
+A special case is the ``unmap'' keyword, which causes any bindings using the 
+named shortcut to be removed. This can be used to remove a binding which conflicts
+with an application.
+.Pp
+For example, to cause
+.Ic C-M-r
+to add a label to a window:
+.Bd -literal -offset indent
+bind CM-r "label"
+.Ed
+.Pp
+Launch an xterm running
+.Xr top 1
+with C-S-Enter:
+.Bd -literal -offset indent
+bind CS-Return "/usr/X11R6/bin/xterm -e top"
+.Ed
+.Pp
+Remove a keybinding for Mod4-o
+.Bd -literal -offset indent
+bind 4-o "unmap"
+.Ed
+.El
+.Sh SEE ALSO
+.Xr cwm 1
+.Sh AUTHORS
+.An -nosplit
+.Pp
+.Nm
+was initially written by
+.An Marius Aamodt Eriksen Aq marius@monkey.org
+with contributions from
+.An Andy Adamson Aq dros@monkey.org ,
+.An Niels Provos Aq provos@monkey.org ,
+and
+.An Antti Nykänen Aq aon@iki.fi .
+.Sh HISTORY
+.Nm
+first appeared in
+.Ox 4.4 .
diff --git a/group.c b/group.c
index 744ca69..913ad2e 100644
--- a/group.c
+++ b/group.c
@@ -31,12 +31,6 @@ char                Group_name[256];
 int                 Grouphideall = 0;
 struct group_ctx_q  Groupq;
 
-static char *shortcut_to_name[] = {
-	"XXX", "one", "two", "three",
-	"four", "five", "six", "seven",
-	"eight", "nine",
-};
-
 static void
 _group_add(struct group_ctx *gc, struct client_ctx *cc)
 {
diff --git a/kbfunc.c b/kbfunc.c
index b71c8fd..8192cae 100644
--- a/kbfunc.c
+++ b/kbfunc.c
@@ -204,7 +204,7 @@ kbfunc_menu_search(struct client_ctx *scratch, void *arg)
 
 	TAILQ_INIT(&menuq);
 
-	conf_cmd_refresh(&Conf);
+	conf_reload(&Conf);
 	TAILQ_FOREACH(cmd, &Conf.cmdq, entry) {
 		XCALLOC(mi, struct menu);
 		strlcpy(mi->text, cmd->label, sizeof(mi->text));
@@ -249,14 +249,14 @@ kbfunc_cmdexec(struct client_ctx *cc, void *arg)
 void
 kbfunc_term(struct client_ctx *cc, void *arg)
 {
-	conf_cmd_refresh(&Conf);
+	conf_reload(&Conf);
 	u_spawn(Conf.termpath);
 }
 
 void
 kbfunc_lock(struct client_ctx *cc, void *arg)
 {
-	conf_cmd_refresh(&Conf);
+	conf_reload(&Conf);
 	u_spawn(Conf.lockpath);
 }
 
@@ -423,7 +423,7 @@ kbfunc_ssh(struct client_ctx *scratch, void *arg)
 
 	if ((mi = search_start(&menuq,
 		    search_match_exec, NULL, "ssh", 1)) != NULL) {
-		conf_cmd_refresh(&Conf);
+		conf_reload(&Conf);
 		l = snprintf(cmd, sizeof(cmd), "%s -e ssh %s", Conf.termpath,
 		    mi->text);
 		if (l != -1 && l < sizeof(cmd))
diff --git a/parse.y b/parse.y
new file mode 100644
index 0000000..bb5e44c
--- /dev/null
+++ b/parse.y
@@ -0,0 +1,583 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "headers.h"
+#include "calmwm.h"
+
+TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+	TAILQ_ENTRY(file)	 entry;
+	FILE			*stream;
+	char			*name;
+	int			 lineno;
+	int			 errors;
+} *file;
+struct file	*pushfile(const char *);
+int		 popfile(void);
+int		 yyparse(void);
+int		 yylex(void);
+int		 yyerror(const char *, ...);
+int		 kw_cmp(const void *, const void *);
+int		 lookup(char *);
+int		 lgetc(int);
+int		 lungetc(int);
+int		 findeol(void);
+
+static struct conf	*conf;
+
+extern char		*shortcut_to_name[];
+
+typedef struct {
+	union {
+		int64_t			 number;
+		char			*string;
+	} v;
+	int lineno;
+} YYSTYPE;
+
+%}
+
+%token	FONTNAME STICKY
+%token	AUTOGROUP BIND COMMAND IGNORE
+%token	YES NO
+%token	ERROR
+%token	<v.string>		STRING
+%token	<v.number>		NUMBER
+%type	<v.number>		yesno
+%type	<v.string>		string
+%%
+
+grammar		: /* empty */
+		| grammar '\n'
+		| grammar main '\n'
+		| grammar error '\n'		{ file->errors++; }
+		;
+
+string		: string STRING			{
+			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+				free($1);
+				free($2);
+				yyerror("string: asprintf");
+				YYERROR;
+			}
+			free($1);
+			free($2);
+		}
+		| STRING
+		;
+
+yesno		: YES				{ $$ = 1; }
+		| NO				{ $$ = 0; }
+		;
+  
+main		: FONTNAME STRING		{
+			if (conf->DefaultFontName != NULL &&
+			    conf->DefaultFontName != DEFAULTFONTNAME)
+				free(conf->DefaultFontName);
+			if ((conf->DefaultFontName = xstrdup($2)) == NULL) {
+				free($2);
+				yyerror("string: asprintf");
+				YYERROR;
+			}
+			free($2);
+		}
+		| STICKY yesno {
+			if ($2 == 0)
+				conf->flags &= ~CONF_STICKY_GROUPS;
+			else
+				conf->flags |= CONF_STICKY_GROUPS;
+		}
+		| COMMAND STRING string		{
+			conf_cmd_add(conf, $3, $2, 0);
+			free($2);
+			free($3);
+		}
+		| AUTOGROUP NUMBER STRING	{
+			struct autogroupwin *aw;
+			char *p;
+
+			if ($2 < 1 || $2 > 9) {
+				free($3);
+				yyerror("autogroup number out of range: %d", $2);
+				YYERROR;
+			}
+
+			XCALLOC(aw, struct autogroupwin);
+
+			if ((p = strchr($3, ',')) == NULL) {
+				aw->name = NULL;
+				aw->class = xstrdup($3);
+			} else {
+				*(p++) = '\0';
+				aw->name = xstrdup($3);
+				aw->class = xstrdup(p);
+			}
+			aw->group = xstrdup(shortcut_to_name[$2]);
+
+			TAILQ_INSERT_TAIL(&conf->autogroupq, aw, entry);
+
+			free($3);
+		}
+		| IGNORE STRING {
+			struct winmatch	*wm;
+
+			XCALLOC(wm, struct winmatch);
+			strlcpy(wm->title, $2, sizeof(wm->title));
+			wm->opts |= CONF_IGNORECASE;
+			TAILQ_INSERT_TAIL(&conf->ignoreq, wm, entry);
+
+			free($2);
+		}
+		| BIND STRING string		{
+			conf_bindname(conf, $2, $3);
+			free($2);
+			free($3);
+		}
+		;
+
+%%
+
+struct keywords {
+	const char	*k_name;
+	int		 k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+	va_list          ap;
+                         
+	file->errors++;         
+	va_start(ap, fmt);
+	fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+	return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+	return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+	/* this has to be sorted always */
+	static const struct keywords keywords[] = {
+		{ "autogroup",		AUTOGROUP},
+		{ "bind",		BIND},
+		{ "command",		COMMAND},
+		{ "fontname",		FONTNAME},
+		{ "ignore",		IGNORE},
+		{ "no",			NO},
+		{ "sticky",		STICKY},
+		{ "yes",		YES}
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+	    sizeof(keywords[0]), kw_cmp);
+
+	if (p)
+		return (p->k_val);
+	else
+		return (STRING);
+}
+
+#define MAXPUSHBACK	128
+
+char	*parsebuf;
+int	 parseindex;
+char	 pushback_buffer[MAXPUSHBACK];
+int	 pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+	int		c, next;
+
+	if (parsebuf) {
+		/* Read character from the parsebuffer instead of input. */
+		if (parseindex >= 0) {
+			c = parsebuf[parseindex++];
+			if (c != '\0')
+				return (c);
+			parsebuf = NULL;
+		} else
+			parseindex++;
+	}
+
+	if (pushback_index)
+		return (pushback_buffer[--pushback_index]);
+
+	if (quotec) {
+		if ((c = getc(file->stream)) == EOF) {
+			yyerror("reached end of file while parsing quoted string");
+			if (popfile() == EOF)
+				return (EOF);
+			return (quotec);
+		}
+		return (c);
+	}
+
+	while ((c = getc(file->stream)) == '\\') {
+		next = getc(file->stream);
+		if (next != '\n') {
+			c = next;
+			break;
+		}
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+
+	while (c == EOF) {
+		if (popfile() == EOF)
+			return (EOF);
+		c = getc(file->stream);
+	}
+	return (c);
+}
+
+int
+lungetc(int c)
+{
+	if (c == EOF)
+		return (EOF);
+	if (parsebuf) {
+		parseindex--;
+		if (parseindex >= 0)
+			return (c);
+	}
+	if (pushback_index < MAXPUSHBACK-1)
+		return (pushback_buffer[pushback_index++] = c);
+	else
+		return (EOF);
+}
+
+int
+findeol(void)
+{
+	int	c;
+
+	parsebuf = NULL;
+	pushback_index = 0;
+
+	/* skip to either EOF or the first real EOL */
+	while (1) {
+		c = lgetc(0);
+		if (c == '\n') {
+			file->lineno++;
+			break;
+		}
+		if (c == EOF)
+			break;
+	}
+	return (ERROR);
+}
+
+int
+yylex(void)
+{
+	char	 buf[8096];
+	char	*p;
+	int	 quotec, next, c;
+	int	 token;
+
+	p = buf;
+	while ((c = lgetc(0)) == ' ' || c == '\t')
+		; /* nothing */
+
+	yylval.lineno = file->lineno;
+	if (c == '#')
+		while ((c = lgetc(0)) != '\n' && c != EOF)
+			; /* nothing */
+
+	switch (c) {
+	case '\'':
+	case '"':
+		quotec = c;
+		while (1) {
+			if ((c = lgetc(quotec)) == EOF)
+				return (0);
+			if (c == '\n') {
+				file->lineno++;
+				continue;
+			} else if (c == '\\') {
+				if ((next = lgetc(quotec)) == EOF)
+					return (0);
+				if (next == quotec || c == ' ' || c == '\t')
+					c = next;
+				else if (next == '\n')
+					continue;
+				else
+					lungetc(next);
+			} else if (c == quotec) {
+				*p = '\0';
+				break;
+			}
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return (findeol());
+			}
+			*p++ = (char)c;
+		}
+		yylval.v.string = strdup(buf);
+		if (yylval.v.string == NULL)
+			err(1, "yylex: strdup");
+		return (STRING);
+	}
+
+#define allowed_to_end_number(x) \
+	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+	if (c == '-' || isdigit(c)) {
+		do {
+			*p++ = c;
+			if ((unsigned)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && isdigit(c));
+		lungetc(c);
+		if (p == buf + 1 && buf[0] == '-')
+			goto nodigits;
+		if (c == EOF || allowed_to_end_number(c)) {
+			const char *errstr = NULL;
+
+			*p = '\0';
+			yylval.v.number = strtonum(buf, LLONG_MIN,
+			    LLONG_MAX, &errstr);
+			if (errstr) {
+				yyerror("\"%s\" invalid number: %s",
+				    buf, errstr);
+				return (findeol());
+			}
+			return (NUMBER);
+		} else {
+nodigits:
+			while (p > buf + 1)
+				lungetc(*--p);
+			c = *--p;
+			if (c == '-')
+				return (c);
+		}
+	}
+
+#define allowed_in_string(x) \
+	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+	x != '{' && x != '}' && x != '<' && x != '>' && \
+	x != '!' && x != '=' && x != '/' && x != '#' && \
+	x != ','))
+
+	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
+		do {
+			*p++ = c;
+			if ((unsigned)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+		lungetc(c);
+		*p = '\0';
+		if ((token = lookup(buf)) == STRING)
+			if ((yylval.v.string = strdup(buf)) == NULL)
+				err(1, "yylex: strdup");
+		return (token);
+	}
+	if (c == '\n') {
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == EOF)
+		return (0);
+	return (c);
+}
+
+struct file *
+pushfile(const char *name)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
+	    (nfile->name = strdup(name)) == NULL) {
+		warn("malloc");
+		return (NULL);
+	}
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		warn("%s", nfile->name);
+		free(nfile->name);
+		free(nfile);
+		return (NULL);
+	}
+	nfile->lineno = 1;
+	TAILQ_INSERT_TAIL(&files, nfile, entry);
+	return (nfile);
+}
+
+int
+popfile(void)
+{
+	struct file	*prev;
+
+	if ((prev = TAILQ_PREV(file, files, entry)) != NULL) {
+		prev->errors += file->errors;
+		TAILQ_REMOVE(&files, file, entry);
+		fclose(file->stream);
+		free(file->name);
+		free(file);
+		file = prev;
+		return (0);
+	}
+	return (EOF);
+}
+
+void
+conf_clear(struct conf *c)
+{
+	struct autogroupwin	*ag, *agnext;
+	struct keybinding	*kb, *kbnext;
+	struct winmatch		*wm, *wmnext;
+	struct cmd		*cmd, *cmdnext;
+
+	for (cmd = TAILQ_FIRST(&c->cmdq); cmd != NULL; cmd = cmdnext) {
+		cmdnext = TAILQ_NEXT(cmd, entry);
+
+		TAILQ_REMOVE(&c->cmdq, cmd, entry);
+		free(cmd);
+	}
+
+	for (kb = TAILQ_FIRST(&c->keybindingq); kb != NULL; kb = kbnext) {
+		kbnext = TAILQ_NEXT(kb, entry);
+
+		TAILQ_REMOVE(&c->keybindingq, kb, entry);
+		free(kb);
+	}
+
+	for (ag = TAILQ_FIRST(&c->autogroupq); ag != NULL; ag = agnext) {
+		agnext = TAILQ_NEXT(ag, entry);
+
+		TAILQ_REMOVE(&c->autogroupq, ag, entry);
+		free(ag->class);
+		if (ag->name)
+			free(ag->name);
+		free(ag->group);
+		free(ag);
+	}
+
+	for (wm = TAILQ_FIRST(&c->ignoreq); wm != NULL; wm = wmnext) {
+		wmnext = TAILQ_NEXT(wm, entry);
+
+		TAILQ_REMOVE(&c->ignoreq, wm, entry);
+		free(wm);
+	}
+
+	if (c->DefaultFontName != NULL &&
+	    c->DefaultFontName != DEFAULTFONTNAME)
+		free(c->DefaultFontName);
+}
+
+
+int
+parse_config(const char *filename, struct conf *xconf)
+{
+	int			 errors = 0;
+
+	if ((conf = malloc(sizeof(struct conf))) == NULL)
+		return (-1);
+
+	if ((file = pushfile(filename)) == NULL) {
+		free(conf);
+		return (-1);
+	}
+
+	strlcpy(conf->conf_path, filename, sizeof(conf->conf_path));
+
+	conf_init(conf);
+
+	yyparse();
+	errors = file->errors;
+	file->errors = 0;
+	popfile();
+
+	if (errors) {
+		conf_clear(conf);
+	}
+	else {
+		struct autogroupwin	*ag, *agnext;
+		struct keybinding	*kb, *kbnext;
+		struct winmatch		*wm, *wmnext;
+		struct cmd		*cmd, *cmdnext;
+
+		conf_clear(xconf);
+
+		xconf->flags = conf->flags;
+
+		for (cmd = TAILQ_FIRST(&conf->cmdq); cmd != NULL; cmd = cmdnext) {
+			cmdnext = TAILQ_NEXT(cmd, entry);
+
+			TAILQ_REMOVE(&conf->cmdq, cmd, entry);
+			TAILQ_INSERT_TAIL(&xconf->cmdq, cmd, entry);
+		}
+
+		for (kb = TAILQ_FIRST(&conf->keybindingq); kb != NULL; kb = kbnext) {
+			kbnext = TAILQ_NEXT(kb, entry);
+
+			TAILQ_REMOVE(&conf->keybindingq, kb, entry);
+			TAILQ_INSERT_TAIL(&xconf->keybindingq, kb, entry);
+		}
+
+		for (ag = TAILQ_FIRST(&conf->autogroupq); ag != NULL; ag = agnext) {
+			agnext = TAILQ_NEXT(ag, entry);
+
+			TAILQ_REMOVE(&conf->autogroupq, ag, entry);
+			TAILQ_INSERT_TAIL(&xconf->autogroupq, ag, entry);
+		}
+
+		for (wm = TAILQ_FIRST(&conf->ignoreq); wm != NULL; wm = wmnext) {
+			wmnext = TAILQ_NEXT(wm, entry);
+
+			TAILQ_REMOVE(&conf->ignoreq, wm, entry);
+			TAILQ_INSERT_TAIL(&xconf->ignoreq, wm, entry);
+		}
+
+		strlcpy(xconf->termpath, conf->termpath, sizeof(xconf->termpath));
+		strlcpy(xconf->lockpath, conf->lockpath, sizeof(xconf->lockpath));
+
+		xconf->DefaultFontName = conf->DefaultFontName;
+	}
+
+	free(conf);
+
+	return (errors ? -1 : 0);
+}
diff --git a/xevents.c b/xevents.c
index 0729b49..a0f3538 100644
--- a/xevents.c
+++ b/xevents.c
@@ -275,10 +275,7 @@ xev_handle_buttonpress(struct xevent *xev, XEvent *ee)
 			break;
 		case Button3: {
 			struct cmd *cmd;
-			if (conf_cmd_changed(Conf.menu_path)) {
-				conf_cmd_clear(&Conf);
-				conf_cmd_populate(&Conf, Conf.menu_path);
-			}
+			conf_reload(&Conf);
 			TAILQ_FOREACH(cmd, &Conf.cmdq, entry) {
 				XCALLOC(mi, struct menu);
 				strlcpy(mi->text, cmd->label, sizeof(mi->text));