summary refs log tree commit diff
diff options
context:
space:
mode:
authorsimon <simon>2008-03-23 15:09:21 +0000
committersimon <simon>2008-03-23 15:09:21 +0000
commitc3aa344e7836f4214f6cff6e2fa7c0297e7da16e (patch)
tree2c07ad69a106a4c5f33744f0001e97d731969e21
parent38ff7a904ede4e6412e5d89844e62b60e2c90fdb (diff)
downloadcwm-c3aa344e7836f4214f6cff6e2fa7c0297e7da16e.tar.gz
cwm-c3aa344e7836f4214f6cff6e2fa7c0297e7da16e.tar.xz
cwm-c3aa344e7836f4214f6cff6e2fa7c0297e7da16e.zip
Replace the symlink configuration scheme with a simple yacc parser as
found in other places of the tree.  Remove sticky and font commandline
options and add another one for alternative config locations.
Split off cwmrc(5) from cwm(1), nuke #ifdef __OpenBSD__ while there.

tested by various kind people, feedback from oga@ and okan@ - thanks!
ok oga@, jasper@, okan@
-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));