summary refs log tree commit diff
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2014-09-07 15:47:44 +0000
committerChristian Neukirchen <chneukirchen@gmail.com>2014-09-07 15:47:44 +0000
commit9e560bd745c4ba9cb47bd242bc93a09b296cb779 (patch)
treefaf0c6a5fe0935f5947c46d4ddc33f2a3cc38eb3
parent870418ff684caae3b1dac508e6ab0103c8039a5b (diff)
parentf7af0ebd7267cdfd4e7b9c12bb910992f1a094e5 (diff)
downloadcwm-9e560bd745c4ba9cb47bd242bc93a09b296cb779.tar.gz
cwm-9e560bd745c4ba9cb47bd242bc93a09b296cb779.tar.xz
cwm-9e560bd745c4ba9cb47bd242bc93a09b296cb779.zip
cvsimport
-rw-r--r--calmwm.c23
-rw-r--r--calmwm.h24
-rw-r--r--client.c19
-rw-r--r--conf.c89
-rw-r--r--cwm.14
-rw-r--r--cwmrc.54
-rw-r--r--group.c241
-rw-r--r--kbfunc.c37
-rw-r--r--menu.c4
-rw-r--r--mousefunc.c34
-rw-r--r--screen.c9
-rw-r--r--search.c2
-rw-r--r--xevents.c2
-rw-r--r--xutil.c63
14 files changed, 271 insertions, 284 deletions
diff --git a/calmwm.c b/calmwm.c
index e78b912..3c76bb4 100644
--- a/calmwm.c
+++ b/calmwm.c
@@ -48,7 +48,7 @@ struct conf			 Conf;
 const char			*homedir;
 volatile sig_atomic_t		 cwm_status;
 
-static void	sigchld_cb(int);
+static void	sighdlr(int);
 static int	x_errorhandler(Display *, XErrorEvent *);
 static void	x_init(const char *);
 static void	x_restart(char **);
@@ -84,7 +84,7 @@ main(int argc, char **argv)
 	argc -= optind;
 	argv += optind;
 
-	if (signal(SIGCHLD, sigchld_cb) == SIG_ERR)
+	if (signal(SIGCHLD, sighdlr) == SIG_ERR)
 		err(1, "signal");
 
 	if ((homedir = getenv("HOME")) == NULL || *homedir == '\0') {
@@ -205,16 +205,19 @@ x_errorhandler(Display *dpy, XErrorEvent *e)
 }
 
 static void
-sigchld_cb(int which)
+sighdlr(int sig)
 {
 	pid_t	 pid;
-	int	 save_errno = errno;
-	int	 status;
-
-	/* Collect dead children. */
-	while ((pid = waitpid(-1, &status, WNOHANG)) > 0 ||
-	    (pid < 0 && errno == EINTR))
-		;
+	int	 save_errno = errno, status;
+
+	switch (sig) {
+	case SIGCHLD:
+		/* Collect dead children. */
+		while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0 ||
+		    (pid < 0 && errno == EINTR))
+			;
+		break;
+	}
 
 	errno = save_errno;
 }
diff --git a/calmwm.h b/calmwm.h
index e5fa2c8..02f31d1 100644
--- a/calmwm.h
+++ b/calmwm.h
@@ -199,6 +199,7 @@ struct client_ctx {
 #define CLIENT_WM_TAKE_FOCUS		0x0200
 #define CLIENT_URGENCY			0x0400
 #define CLIENT_FULLSCREEN		0x0800
+#define CLIENT_STICKY			0x1000
 
 #define CLIENT_HIGHLIGHT		(CLIENT_GROUP | CLIENT_UNGROUP)
 #define CLIENT_MAXFLAGS			(CLIENT_VMAXIMIZED | CLIENT_HMAXIMIZED)
@@ -222,10 +223,8 @@ TAILQ_HEAD(cycle_entry_q, client_ctx);
 struct group_ctx {
 	TAILQ_ENTRY(group_ctx)	 entry;
 	struct client_ctx_q	 clients;
-	int			 shortcut;
-	int			 hidden;
-	int			 nhidden;
-	int			 highstack;
+	char			*name;
+	int			 num;
 };
 TAILQ_HEAD(group_ctx_q, group_ctx);
 
@@ -263,9 +262,7 @@ struct screen_ctx {
 	struct group_ctx	 groups[CALMWM_NGROUPS];
 	struct group_ctx_q	 groupq;
 	int			 group_hideall;
-	int			 group_nonames;
 	struct group_ctx	*group_active;
-	char 			**group_names;
 };
 TAILQ_HEAD(screen_ctx_q, screen_ctx);
 
@@ -316,8 +313,6 @@ struct conf {
 	int			 snapdist;
 	struct gap		 gap;
 	char			*color[CWM_COLOR_NITEMS];
-	char			 termpath[MAXPATHLEN];
-	char			 lockpath[MAXPATHLEN];
 	char			 known_hosts[MAXPATHLEN];
 #define	CONF_FONT			"sans-serif:pixelsize=14:bold"
 	char			*font;
@@ -371,7 +366,8 @@ enum {
 	_NET_WM_DESKTOP,
 	_NET_CLOSE_WINDOW,
 	_NET_WM_STATE,
-#define	_NET_WM_STATES_NITEMS	4
+#define	_NET_WM_STATES_NITEMS	5
+	_NET_WM_STATE_STICKY,
 	_NET_WM_STATE_MAXIMIZED_VERT,
 	_NET_WM_STATE_MAXIMIZED_HORZ,
 	_NET_WM_STATE_FULLSCREEN,
@@ -418,6 +414,7 @@ void			 client_set_wm_state(struct client_ctx *, long);
 void			 client_setactive(struct client_ctx *);
 void			 client_setname(struct client_ctx *);
 int			 client_snapcalc(int, int, int, int, int);
+void			 client_sticky(struct client_ctx *);
 void			 client_transient(struct client_ctx *);
 void			 client_unhide(struct client_ctx *);
 void			 client_urgency(struct client_ctx *);
@@ -429,12 +426,13 @@ void			 client_wm_hints(struct client_ctx *);
 void			 group_alltoggle(struct screen_ctx *);
 void			 group_autogroup(struct client_ctx *);
 void			 group_cycle(struct screen_ctx *, int);
+int			 group_hidden_state(struct group_ctx *);
+void			 group_hide(struct screen_ctx *, struct group_ctx *);
 void			 group_hidetoggle(struct screen_ctx *, int);
 void			 group_init(struct screen_ctx *);
-void			 group_menu(struct screen_ctx *);
 void			 group_movetogroup(struct client_ctx *, int);
 void			 group_only(struct screen_ctx *, int);
-void			 group_set_state(struct screen_ctx *);
+void			 group_show(struct screen_ctx *, struct group_ctx *);
 void			 group_sticky(struct client_ctx *);
 void			 group_sticky_toggle_enter(struct client_ctx *);
 void			 group_sticky_toggle_exit(struct client_ctx *);
@@ -487,6 +485,7 @@ void			 kbfunc_client_nogroup(struct client_ctx *,
 void			 kbfunc_client_raise(struct client_ctx *, union arg *);
 void			 kbfunc_client_rcycle(struct client_ctx *, union arg *);
 void			 kbfunc_client_search(struct client_ctx *, union arg *);
+void			 kbfunc_client_sticky(struct client_ctx *, union arg *);
 void			 kbfunc_client_vmaximize(struct client_ctx *,
 			     union arg *);
 void			 kbfunc_cmdexec(struct client_ctx *, union arg *);
@@ -564,8 +563,7 @@ void			 xu_ewmh_net_wm_number_of_desktops(struct screen_ctx *);
 void			 xu_ewmh_net_showing_desktop(struct screen_ctx *);
 void			 xu_ewmh_net_virtual_roots(struct screen_ctx *);
 void			 xu_ewmh_net_current_desktop(struct screen_ctx *, long);
-void			 xu_ewmh_net_desktop_names(struct screen_ctx *, char *,
-			     int);
+void			 xu_ewmh_net_desktop_names(struct screen_ctx *);
 
 void			 xu_ewmh_net_wm_desktop(struct client_ctx *);
 Atom 			*xu_ewmh_get_net_wm_state(struct client_ctx *, int *);
diff --git a/client.c b/client.c
index adc2f9c..6e5781c 100644
--- a/client.c
+++ b/client.c
@@ -239,6 +239,17 @@ client_freeze(struct client_ctx *cc)
 }
 
 void
+client_sticky(struct client_ctx *cc)
+{
+	if (cc->flags & CLIENT_STICKY)
+		cc->flags &= ~CLIENT_STICKY;
+	else
+		cc->flags |= CLIENT_STICKY;
+
+	xu_ewmh_set_net_wm_state(cc);
+}
+
+void
 client_fullscreen(struct client_ctx *cc)
 {
 	struct screen_ctx	*sc = cc->sc;
@@ -468,6 +479,9 @@ client_ptrsave(struct client_ctx *cc)
 void
 client_hide(struct client_ctx *cc)
 {
+	if (cc->flags & CLIENT_STICKY)
+		return;
+
 	XUnmapWindow(X_Dpy, cc->win);
 
 	cc->active = 0;
@@ -481,6 +495,9 @@ client_hide(struct client_ctx *cc)
 void
 client_unhide(struct client_ctx *cc)
 {
+	if (cc->flags & CLIENT_STICKY)
+		return;
+
 	XMapRaised(X_Dpy, cc->win);
 
 	cc->flags &= ~CLIENT_HIDDEN;
@@ -869,7 +886,7 @@ client_transient(struct client_ctx *cc)
 
 	if (XGetTransientForHint(X_Dpy, cc->win, &trans)) {
 		if ((tc = client_find(trans)) && tc->group) {
-			group_movetogroup(cc, tc->group->shortcut);
+			group_movetogroup(cc, tc->group->num);
 			if (tc->flags & CLIENT_IGNORE)
 				cc->flags |= CLIENT_IGNORE;
 		}
diff --git a/conf.c b/conf.c
index 8b3c5da..390368c 100644
--- a/conf.c
+++ b/conf.c
@@ -41,27 +41,19 @@ conf_cmd_add(struct conf *c, const char *name, const char *path)
 {
 	struct cmd	*cmd;
 
-	/* "term" and "lock" have special meanings. */
-	if (strcmp(name, "term") == 0) {
-		if (strlcpy(c->termpath, path, sizeof(c->termpath)) >=
-		    sizeof(c->termpath))
-			return (0);
-	} else if (strcmp(name, "lock") == 0) {
-		if (strlcpy(c->lockpath, path, sizeof(c->lockpath)) >=
-		    sizeof(c->lockpath))
-			return (0);
-	} else {
-		conf_cmd_remove(c, name);
-
-		cmd = xmalloc(sizeof(*cmd));
+	cmd = xmalloc(sizeof(*cmd));
 
-		cmd->name = xstrdup(name);
-		if (strlcpy(cmd->path, path, sizeof(cmd->path)) >=
-		    sizeof(cmd->path))
-			return (0);
-		TAILQ_INSERT_TAIL(&c->cmdq, cmd, entry);
+	cmd->name = xstrdup(name);
+	if (strlcpy(cmd->path, path, sizeof(cmd->path)) >= sizeof(cmd->path)) {
+		free(cmd->name);
+		free(cmd);
+		return(0);
 	}
-	return (1);
+
+	conf_cmd_remove(c, name);
+
+	TAILQ_INSERT_TAIL(&c->cmdq, cmd, entry);
+	return(1);
 }
 
 static void
@@ -78,7 +70,7 @@ conf_cmd_remove(struct conf *c, const char *name)
 	}
 }
 void
-conf_autogroup(struct conf *c, int no, const char *val)
+conf_autogroup(struct conf *c, int num, const char *val)
 {
 	struct autogroupwin	*aw;
 	char			*p;
@@ -93,7 +85,7 @@ conf_autogroup(struct conf *c, int no, const char *val)
 		aw->name = xstrdup(val);
 		aw->class = xstrdup(p);
 	}
-	aw->num = no;
+	aw->num = num;
 
 	TAILQ_INSERT_TAIL(&c->autogroupq, aw, entry);
 }
@@ -204,6 +196,7 @@ static const struct {
 	{ "CM-g",	"grouptoggle" },
 	{ "CM-f",	"fullscreen" },
 	{ "CM-m",	"maximize" },
+	{ "CM-s",	"sticky" },
 	{ "CM-equal",	"vmaximize" },
 	{ "CMS-equal",	"hmaximize" },
 	{ "CMS-f",	"freeze" },
@@ -250,8 +243,6 @@ conf_init(struct conf *c)
 {
 	unsigned int	i;
 
-	(void)memset(c, 0, sizeof(*c));
-
 	c->bwidth = CONF_BWIDTH;
 	c->mamount = CONF_MAMOUNT;
 	c->snapdist = CONF_SNAPDIST;
@@ -346,7 +337,7 @@ static const struct {
 	void		 (*handler)(struct client_ctx *, union arg *);
 	int		 flags;
 	union arg	 argument;
-} name_to_kbfunc[] = {
+} name_to_func[] = {
 	{ "lower", kbfunc_client_lower, CWM_WIN, {0} },
 	{ "raise", kbfunc_client_raise, CWM_WIN, {0} },
 	{ "search", kbfunc_client_search, 0, {0} },
@@ -391,6 +382,7 @@ static const struct {
 	{ "rcycleingroup", kbfunc_client_cycle, CWM_WIN,
 	    {.i = CWM_RCYCLE|CWM_INGROUP} },
 	{ "grouptoggle", kbfunc_client_grouptoggle, CWM_WIN, {0}},
+	{ "sticky", kbfunc_client_sticky, CWM_WIN, {0} },
 	{ "fullscreen", kbfunc_client_fullscreen, CWM_WIN, {0} },
 	{ "maximize", kbfunc_client_maximize, CWM_WIN, {0} },
 	{ "vmaximize", kbfunc_client_vmaximize, CWM_WIN, {0} },
@@ -453,6 +445,15 @@ static const struct {
 	    {.i = (CWM_RIGHT|CWM_PTRMOVE|CWM_BIGMOVE)} },
 	{ "htile", kbfunc_tile, CWM_WIN, {.i = CWM_TILE_HORIZ} },
 	{ "vtile", kbfunc_tile, CWM_WIN, {.i = CWM_TILE_VERT} },
+	{ "window_lower", kbfunc_client_lower, CWM_WIN, {0} },
+	{ "window_raise", kbfunc_client_raise, CWM_WIN, {0} },
+	{ "window_hide", kbfunc_client_hide, CWM_WIN, {0} },
+	{ "window_move", mousefunc_client_move, CWM_WIN, {0} },
+	{ "window_resize", mousefunc_client_resize, CWM_WIN, {0} },
+	{ "window_grouptoggle", mousefunc_client_grouptoggle, CWM_WIN, {0} },
+	{ "menu_group", mousefunc_menu_group, 0, {0} },
+	{ "menu_unhide", mousefunc_menu_unhide, 0, {0} },
+	{ "menu_cmd", mousefunc_menu_cmd, 0, {0} },
 };
 
 static const struct {
@@ -510,13 +511,13 @@ conf_bind_kbd(struct conf *c, const char *bind, const char *cmd)
 		return (1);
 	}
 
-	for (i = 0; i < nitems(name_to_kbfunc); i++) {
-		if (strcmp(name_to_kbfunc[i].tag, cmd) != 0)
+	for (i = 0; i < nitems(name_to_func); i++) {
+		if (strcmp(name_to_func[i].tag, cmd) != 0)
 			continue;
 
-		kb->callback = name_to_kbfunc[i].handler;
-		kb->flags = name_to_kbfunc[i].flags;
-		kb->argument = name_to_kbfunc[i].argument;
+		kb->callback = name_to_func[i].handler;
+		kb->flags = name_to_func[i].flags;
+		kb->argument = name_to_func[i].argument;
 		kb->argtype |= ARG_INT;
 		TAILQ_INSERT_TAIL(&c->keybindingq, kb, entry);
 		return (1);
@@ -548,25 +549,6 @@ conf_unbind_kbd(struct conf *c, struct binding *unbind)
 	}
 }
 
-static const struct {
-	const char	*tag;
-	void		 (*handler)(struct client_ctx *, union arg *);
-	int		 flags;
-	union arg	 argument;
-} name_to_mousefunc[] = {
-	{ "window_lower", kbfunc_client_lower, CWM_WIN, {0} },
-	{ "window_raise", kbfunc_client_raise, CWM_WIN, {0} },
-	{ "window_hide", kbfunc_client_hide, CWM_WIN, {0} },
-	{ "cyclegroup", kbfunc_client_cyclegroup, 0, {.i = CWM_CYCLE} },
-	{ "rcyclegroup", kbfunc_client_cyclegroup, 0, {.i = CWM_RCYCLE} },
-	{ "window_move", mousefunc_client_move, CWM_WIN, {0} },
-	{ "window_resize", mousefunc_client_resize, CWM_WIN, {0} },
-	{ "window_grouptoggle", mousefunc_client_grouptoggle, CWM_WIN, {0} },
-	{ "menu_group", mousefunc_menu_group, 0, {0} },
-	{ "menu_unhide", mousefunc_menu_unhide, 0, {0} },
-	{ "menu_cmd", mousefunc_menu_cmd, 0, {0} },
-};
-
 int
 conf_bind_mouse(struct conf *c, const char *bind, const char *cmd)
 {
@@ -593,13 +575,13 @@ conf_bind_mouse(struct conf *c, const char *bind, const char *cmd)
 		return (1);
 	}
 
-	for (i = 0; i < nitems(name_to_mousefunc); i++) {
-		if (strcmp(name_to_mousefunc[i].tag, cmd) != 0)
+	for (i = 0; i < nitems(name_to_func); i++) {
+		if (strcmp(name_to_func[i].tag, cmd) != 0)
 			continue;
 
-		mb->callback = name_to_mousefunc[i].handler;
-		mb->flags = name_to_mousefunc[i].flags;
-		mb->argument = name_to_mousefunc[i].argument;
+		mb->callback = name_to_func[i].handler;
+		mb->flags = name_to_func[i].flags;
+		mb->argument = name_to_func[i].argument;
 		TAILQ_INSERT_TAIL(&c->mousebindingq, mb, entry);
 		return (1);
 	}
@@ -690,6 +672,7 @@ static char *ewmhints[] = {
 	"_NET_WM_DESKTOP",
 	"_NET_CLOSE_WINDOW",
 	"_NET_WM_STATE",
+	"_NET_WM_STATE_STICKY",
 	"_NET_WM_STATE_MAXIMIZED_VERT",
 	"_NET_WM_STATE_MAXIMIZED_HORZ",
 	"_NET_WM_STATE_FULLSCREEN",
diff --git a/cwm.1 b/cwm.1
index b3a5c3d..eb6c9e7 100644
--- a/cwm.1
+++ b/cwm.1
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: December 16 2013 $
+.Dd $Mdocdate$
 .Dt CWM 1
 .Os
 .Sh NAME
@@ -90,6 +90,8 @@ Cycle through active groups.
 Reverse cycle through active groups.
 .It Ic CMS-f
 Toggle freezing geometry of current window.
+.It Ic CM-s
+Toggle stickiness of current window.
 .It Ic CM-f
 Toggle full-screen mode of current window.
 .It Ic CM-m
diff --git a/cwmrc.5 b/cwmrc.5
index bc6e9c2..a0b39d3 100644
--- a/cwmrc.5
+++ b/cwmrc.5
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: December 16 2013 $
+.Dd $Mdocdate$
 .Dt CWMRC 5
 .Os
 .Sh NAME
@@ -341,6 +341,8 @@ Raise current window.
 Label current window.
 .It freeze
 Freeze current window geometry.
+.It sticky
+Stick current window to all groups (same as assigning to nogroup).
 .It fullscreen
 Full-screen current window (gap + border removed).
 .It maximize
diff --git a/group.c b/group.c
index 8ad372e..cdc5912 100644
--- a/group.c
+++ b/group.c
@@ -33,13 +33,10 @@
 #include "calmwm.h"
 
 static void		 group_assign(struct group_ctx *, struct client_ctx *);
-static void		 group_hide(struct screen_ctx *, struct group_ctx *);
-static void		 group_show(struct screen_ctx *, struct group_ctx *);
-static void		 group_fix_hidden_state(struct group_ctx *);
+static void		 group_restack(struct screen_ctx *, struct group_ctx *);
 static void		 group_setactive(struct screen_ctx *, long);
-static void		 group_set_names(struct screen_ctx *);
 
-const char *shortcut_to_name[] = {
+const char *num_to_name[] = {
 	"nogroup", "one", "two", "three", "four", "five", "six",
 	"seven", "eight", "nine"
 };
@@ -47,63 +44,62 @@ const char *shortcut_to_name[] = {
 static void
 group_assign(struct group_ctx *gc, struct client_ctx *cc)
 {
-	if (gc == NULL)
-		gc = TAILQ_FIRST(&cc->sc->groupq);
-	if (cc->group == gc)
-		return;
-
 	if (cc->group != NULL)
 		TAILQ_REMOVE(&cc->group->clients, cc, group_entry);
 
-	TAILQ_INSERT_TAIL(&gc->clients, cc, group_entry);
 	cc->group = gc;
 
+	if (cc->group != NULL)
+		TAILQ_INSERT_TAIL(&gc->clients, cc, group_entry);
+
 	xu_ewmh_net_wm_desktop(cc);
 }
 
-static void
+void
 group_hide(struct screen_ctx *sc, struct group_ctx *gc)
 {
 	struct client_ctx	*cc;
 
 	screen_updatestackingorder(sc);
 
-	gc->nhidden = 0;
-	gc->highstack = 0;
-	TAILQ_FOREACH(cc, &gc->clients, group_entry) {
+	TAILQ_FOREACH(cc, &gc->clients, group_entry)
 		client_hide(cc);
-		gc->nhidden++;
-		if (cc->stackingorder > gc->highstack)
-			gc->highstack = cc->stackingorder;
-	}
-	gc->hidden = 1;		/* XXX: equivalent to gc->nhidden > 0 */
 }
 
-static void
+void
 group_show(struct screen_ctx *sc, struct group_ctx *gc)
 {
 	struct client_ctx	*cc;
+
+	TAILQ_FOREACH(cc, &gc->clients, group_entry)
+		client_unhide(cc);
+
+	group_restack(sc, gc);
+	group_setactive(sc, gc->num);
+}
+
+static void
+group_restack(struct screen_ctx *sc, struct group_ctx *gc)
+{
+	struct client_ctx	*cc;
 	Window			*winlist;
 	int			 i, lastempty = -1;
+	int			 nwins = 0, highstack = 0;
 
-	gc->highstack = 0;
 	TAILQ_FOREACH(cc, &gc->clients, group_entry) {
-		if (cc->stackingorder > gc->highstack)
-			gc->highstack = cc->stackingorder;
+		if (cc->stackingorder > highstack)
+			highstack = cc->stackingorder;
 	}
-	winlist = xcalloc((gc->highstack + 1), sizeof(*winlist));
+	winlist = xcalloc((highstack + 1), sizeof(*winlist));
 
-	/*
-	 * Invert the stacking order as XRestackWindows() expects them
-	 * top-to-bottom.
-	 */
+	/* Invert the stacking order for XRestackWindows(). */
 	TAILQ_FOREACH(cc, &gc->clients, group_entry) {
-		winlist[gc->highstack - cc->stackingorder] = cc->win;
-		client_unhide(cc);
+		winlist[highstack - cc->stackingorder] = cc->win;
+		nwins++;
 	}
 
 	/* Un-sparseify */
-	for (i = 0; i <= gc->highstack; i++) {
+	for (i = 0; i <= highstack; i++) {
 		if (!winlist[i] && lastempty == -1)
 			lastempty = i;
 		else if (winlist[i] && lastempty != -1) {
@@ -113,11 +109,8 @@ group_show(struct screen_ctx *sc, struct group_ctx *gc)
 		}
 	}
 
-	XRestackWindows(X_Dpy, winlist, gc->nhidden);
+	XRestackWindows(X_Dpy, winlist, nwins);
 	free(winlist);
-
-	gc->hidden = 0;
-	group_setactive(sc, gc->shortcut);
 }
 
 void
@@ -127,19 +120,15 @@ group_init(struct screen_ctx *sc)
 
 	TAILQ_INIT(&sc->groupq);
 	sc->group_hideall = 0;
-	/*
-	 * See if any group names have already been set and update the
-	 * property with ours if they'll have changed.
-	 */
-	group_update_names(sc);
 
 	for (i = 0; i < CALMWM_NGROUPS; i++) {
 		TAILQ_INIT(&sc->groups[i].clients);
-		sc->groups[i].hidden = 0;
-		sc->groups[i].shortcut = i;
+		sc->groups[i].name = xstrdup(num_to_name[i]);
+		sc->groups[i].num = i;
 		TAILQ_INSERT_TAIL(&sc->groupq, &sc->groups[i], entry);
 	}
 
+	xu_ewmh_net_desktop_names(sc);
 	xu_ewmh_net_wm_desktop_viewport(sc);
 	xu_ewmh_net_wm_number_of_desktops(sc);
 	xu_ewmh_net_showing_desktop(sc);
@@ -148,15 +137,6 @@ group_init(struct screen_ctx *sc)
 	group_setactive(sc, 1);
 }
 
-void
-group_set_state(struct screen_ctx *sc)
-{
-	struct group_ctx	*gc;
-
-	TAILQ_FOREACH(gc, &sc->groupq, entry)
-		group_fix_hidden_state(gc);
-}
-
 static void
 group_setactive(struct screen_ctx *sc, long idx)
 {
@@ -175,12 +155,11 @@ group_movetogroup(struct client_ctx *cc, int idx)
 		errx(1, "group_movetogroup: index out of range (%d)", idx);
 
 	gc = &sc->groups[idx];
+
 	if (cc->group == gc)
 		return;
-	if (gc->hidden) {
+	if (group_hidden_state(gc))
 		client_hide(cc);
-		gc->nhidden++;
-	}
 	group_assign(gc, cc);
 }
 
@@ -212,21 +191,25 @@ group_sticky_toggle_exit(struct client_ctx *cc)
 }
 
 /*
- * if group_hidetoggle would produce no effect, toggle the group's hidden state
+ * If all clients in a group are hidden, then the group state is hidden.
  */
-static void
-group_fix_hidden_state(struct group_ctx *gc)
+int
+group_hidden_state(struct group_ctx *gc)
 {
 	struct client_ctx	*cc;
-	int			 same = 0;
+	int			 hidden = 0, same = 0;
 
 	TAILQ_FOREACH(cc, &gc->clients, group_entry) {
-		if (gc->hidden == ((cc->flags & CLIENT_HIDDEN) ? 1 : 0))
+		if (cc->flags & CLIENT_STICKY)
+			continue;
+		if (hidden == ((cc->flags & CLIENT_HIDDEN) ? 1 : 0))
 			same++;
 	}
 
 	if (same == 0)
-		gc->hidden = !gc->hidden;
+		hidden = !hidden;
+
+	return(hidden);
 }
 
 void
@@ -238,9 +221,8 @@ group_hidetoggle(struct screen_ctx *sc, int idx)
 		errx(1, "group_hidetoggle: index out of range (%d)", idx);
 
 	gc = &sc->groups[idx];
-	group_fix_hidden_state(gc);
 
-	if (gc->hidden)
+	if (group_hidden_state(gc))
 		group_show(sc, gc);
 	else {
 		group_hide(sc, gc);
@@ -259,7 +241,7 @@ group_only(struct screen_ctx *sc, int idx)
 		errx(1, "group_only: index out of range (%d)", idx);
 
 	TAILQ_FOREACH(gc, &sc->groupq, entry) {
-		if (gc->shortcut == idx)
+		if (gc->num == idx)
 			group_show(sc, gc);
 		else
 			group_hide(sc, gc);
@@ -288,7 +270,7 @@ group_cycle(struct screen_ctx *sc, int flags)
 
 		if (!TAILQ_EMPTY(&gc->clients) && showgroup == NULL)
 			showgroup = gc;
-		else if (!gc->hidden)
+		else if (!group_hidden_state(gc))
 			group_hide(sc, gc);
 	}
 
@@ -297,39 +279,10 @@ group_cycle(struct screen_ctx *sc, int flags)
 
 	group_hide(sc, sc->group_active);
 
-	if (showgroup->hidden)
+	if (group_hidden_state(showgroup))
 		group_show(sc, showgroup);
 	else
-		group_setactive(sc, showgroup->shortcut);
-}
-
-void
-group_menu(struct screen_ctx *sc)
-{
-	struct group_ctx	*gc;
-	struct menu		*mi;
-	struct menu_q		 menuq;
-
-	TAILQ_INIT(&menuq);
-
-	TAILQ_FOREACH(gc, &sc->groupq, entry) {
-		if (TAILQ_EMPTY(&gc->clients))
-			continue;
-
-		menuq_add(&menuq, gc, gc->hidden ? "%d: [%s]" : "%d: %s",
-		    gc->shortcut, sc->group_names[gc->shortcut]);
-	}
-
-	if (TAILQ_EMPTY(&menuq))
-		return;
-
-	mi = menu_filter(sc, &menuq, NULL, NULL, 0, NULL, NULL);
-	if (mi != NULL && mi->ctx != NULL) {
-		gc = (struct group_ctx *)mi->ctx;
-		(gc->hidden) ? group_show(sc, gc) : group_hide(sc, gc);
-	}
-
-	menuq_clear(&menuq);
+		group_setactive(sc, showgroup->num);
 }
 
 void
@@ -352,36 +305,38 @@ group_autogroup(struct client_ctx *cc)
 	struct screen_ctx	*sc = cc->sc;
 	struct autogroupwin	*aw;
 	struct group_ctx	*gc;
-	int			 no = -1, both_match = 0;
-	long			*grpno;
+	int			 num = -2, both_match = 0;
+	long			*grpnum;
 
 	if (cc->ch.res_class == NULL || cc->ch.res_name == NULL)
 		return;
 
 	if (xu_getprop(cc->win, ewmh[_NET_WM_DESKTOP],
-	    XA_CARDINAL, 1, (unsigned char **)&grpno) > 0) {
-		if (*grpno == -1)
-			no = 0;
-		else if (*grpno > CALMWM_NGROUPS || *grpno < 0)
-			no = CALMWM_NGROUPS - 1;
-		else
-			no = *grpno;
-		XFree(grpno);
+	    XA_CARDINAL, 1, (unsigned char **)&grpnum) > 0) {
+		num = *grpnum;
+		if (num > CALMWM_NGROUPS || num < -1)
+			num = CALMWM_NGROUPS - 1;
+		XFree(grpnum);
 	} else {
 		TAILQ_FOREACH(aw, &Conf.autogroupq, entry) {
 			if (strcmp(aw->class, cc->ch.res_class) == 0) {
 				if ((aw->name != NULL) &&
 				    (strcmp(aw->name, cc->ch.res_name) == 0)) {
-					no = aw->num;
+					num = aw->num;
 					both_match = 1;
 				} else if (aw->name == NULL && !both_match)
-					no = aw->num;
+					num = aw->num;
 			}
 		}
 	}
 
+	if ((num == -1) || (num == 0)) {
+		group_assign(NULL, cc);
+		return;
+	}
+
 	TAILQ_FOREACH(gc, &sc->groupq, entry) {
-		if (gc->shortcut == no) {
+		if (gc->num == num) {
 			group_assign(gc, cc);
 			return;
 		}
@@ -392,71 +347,3 @@ group_autogroup(struct client_ctx *cc)
 	else
 		group_assign(NULL, cc);
 }
-
-void
-group_update_names(struct screen_ctx *sc)
-{
-	char		**strings, *p;
-	unsigned char	*prop_ret;
-	int		 i = 0, j = 0, nstrings = 0, n = 0, setnames = 0;
-
-	if ((j = xu_getprop(sc->rootwin, ewmh[_NET_DESKTOP_NAMES],
-	    cwmh[UTF8_STRING], 0xffffff, (unsigned char **)&prop_ret)) > 0) {
-		prop_ret[j - 1] = '\0'; /* paranoia */
-		while (i < j) {
-			if (prop_ret[i++] == '\0')
-				nstrings++;
-		}
-	}
-
-	strings = xcalloc((nstrings < CALMWM_NGROUPS ? CALMWM_NGROUPS :
-	    nstrings), sizeof(*strings));
-
-	p = (char *)prop_ret;
-	while (n < nstrings) {
-		strings[n++] = xstrdup(p);
-		p += strlen(p) + 1;
-	}
-	/*
-	 * make sure we always set our defaults if nothing is there to
-	 * replace them.
-	 */
-	if (n < CALMWM_NGROUPS) {
-		setnames = 1;
-		i = 0;
-		while (n < CALMWM_NGROUPS)
-			strings[n++] = xstrdup(shortcut_to_name[i++]);
-	}
-
-	if (prop_ret != NULL)
-		XFree(prop_ret);
-	if (sc->group_nonames != 0)
-		free(sc->group_names);
-
-	sc->group_names = strings;
-	sc->group_nonames = n;
-	if (setnames)
-		group_set_names(sc);
-}
-
-static void
-group_set_names(struct screen_ctx *sc)
-{
-	char		*p, *q;
-	size_t		 len = 0, tlen, slen;
-	int		 i;
-
-	for (i = 0; i < sc->group_nonames; i++)
-		len += strlen(sc->group_names[i]) + 1;
-	q = p = xcalloc(len, sizeof(*p));
-
-	tlen = len;
-	for (i = 0; i < sc->group_nonames; i++) {
-		slen = strlen(sc->group_names[i]) + 1;
-		(void)strlcpy(q, sc->group_names[i], tlen);
-		tlen -= slen;
-		q += slen;
-	}
-
-	xu_ewmh_net_desktop_names(sc, p, len);
-}
diff --git a/kbfunc.c b/kbfunc.c
index 513c81c..78cb9fd 100644
--- a/kbfunc.c
+++ b/kbfunc.c
@@ -159,7 +159,6 @@ kbfunc_client_search(struct client_ctx *cc, union arg *arg)
 		cc = (struct client_ctx *)mi->ctx;
 		if (cc->flags & CLIENT_HIDDEN)
 			client_unhide(cc);
-
 		if (old_cc)
 			client_ptrsave(old_cc);
 		client_ptrwarp(cc);
@@ -214,13 +213,23 @@ kbfunc_cmdexec(struct client_ctx *cc, union arg *arg)
 void
 kbfunc_term(struct client_ctx *cc, union arg *arg)
 {
-	u_spawn(Conf.termpath);
+	struct cmd *cmd;
+
+	TAILQ_FOREACH(cmd, &Conf.cmdq, entry) {
+		if (strcmp(cmd->name, "term") == 0)
+			u_spawn(cmd->path);
+	}
 }
 
 void
 kbfunc_lock(struct client_ctx *cc, union arg *arg)
 {
-	u_spawn(Conf.lockpath);
+	struct cmd *cmd;
+
+	TAILQ_FOREACH(cmd, &Conf.cmdq, entry) {
+		if (strcmp(cmd->name, "lock") == 0)
+			u_spawn(cmd->path);
+	}
 }
 
 void
@@ -310,12 +319,13 @@ void
 kbfunc_ssh(struct client_ctx *cc, union arg *arg)
 {
 	struct screen_ctx	*sc = cc->sc;
+	struct cmd		*cmd;
 	struct menu		*mi;
 	struct menu_q		 menuq;
 	FILE			*fp;
 	char			*buf, *lbuf, *p;
 	char			 hostbuf[MAXHOSTNAMELEN];
-	char			 cmd[256];
+	char			 path[MAXPATHLEN];
 	int			 l;
 	size_t			 len;
 
@@ -324,6 +334,11 @@ kbfunc_ssh(struct client_ctx *cc, union arg *arg)
 		return;
 	}
 
+	TAILQ_FOREACH(cmd, &Conf.cmdq, entry) {
+		if (strcmp(cmd->name, "term") == 0)
+			break;
+	}
+
 	TAILQ_INIT(&menuq);
 
 	lbuf = NULL;
@@ -356,10 +371,10 @@ kbfunc_ssh(struct client_ctx *cc, union arg *arg)
 	    search_match_exec, NULL)) != NULL) {
 		if (mi->text[0] == '\0')
 			goto out;
-		l = snprintf(cmd, sizeof(cmd), "%s -T '[ssh] %s' -e ssh %s",
-		    Conf.termpath, mi->text, mi->text);
-		if (l != -1 && l < sizeof(cmd))
-			u_spawn(cmd);
+		l = snprintf(path, sizeof(path), "%s -T '[ssh] %s' -e ssh %s",
+		    cmd->path, mi->text, mi->text);
+		if (l != -1 && l < sizeof(path))
+			u_spawn(path);
 	}
 out:
 	if (mi != NULL && mi->dummy)
@@ -433,6 +448,12 @@ kbfunc_client_movetogroup(struct client_ctx *cc, union arg *arg)
 }
 
 void
+kbfunc_client_sticky(struct client_ctx *cc, union arg *arg)
+{
+	client_sticky(cc);
+}
+
+void
 kbfunc_client_fullscreen(struct client_ctx *cc, union arg *arg)
 {
 	client_fullscreen(cc);
diff --git a/menu.c b/menu.c
index ebf1cbd..efbf756 100644
--- a/menu.c
+++ b/menu.c
@@ -260,7 +260,7 @@ menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
 		 * even if dummy is zero, we need to return something.
 		 */
 		if ((mi = TAILQ_FIRST(resultq)) == NULL) {
-			mi = xmalloc(sizeof *mi);
+			mi = xmalloc(sizeof(*mi));
 			(void)strlcpy(mi->text,
 			    mc->searchstr, sizeof(mi->text));
 			mi->dummy = 1;
@@ -302,7 +302,7 @@ menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
 		mc->list = !mc->list;
 		break;
 	case CTL_ABORT:
-		mi = xmalloc(sizeof *mi);
+		mi = xmalloc(sizeof(*mi));
 		mi->text[0] = '\0';
 		mi->dummy = 1;
 		mi->abort = 1;
diff --git a/mousefunc.c b/mousefunc.c
index 9db0aa6..3442f67 100644
--- a/mousefunc.c
+++ b/mousefunc.c
@@ -181,7 +181,30 @@ mousefunc_client_grouptoggle(struct client_ctx *cc, union arg *arg)
 void
 mousefunc_menu_group(struct client_ctx *cc, union arg *arg)
 {
-	group_menu(cc->sc);
+	struct screen_ctx	*sc = cc->sc;
+	struct group_ctx	*gc;
+	struct menu		*mi;
+	struct menu_q		 menuq;
+
+	TAILQ_INIT(&menuq);
+	TAILQ_FOREACH(gc, &sc->groupq, entry) {
+		if (TAILQ_EMPTY(&gc->clients))
+			continue;
+		menuq_add(&menuq, gc,
+		    group_hidden_state(gc) ? "%d: [%s]" : "%d: %s",
+		    gc->num, gc->name);
+	}
+	if (TAILQ_EMPTY(&menuq))
+		return;
+
+	if ((mi = menu_filter(sc, &menuq, NULL, NULL, 0,
+	    NULL, NULL)) != NULL) {
+		gc = (struct group_ctx *)mi->ctx;
+		(group_hidden_state(gc)) ?
+		    group_show(sc, gc) : group_hide(sc, gc);
+	}
+
+	menuq_clear(&menuq);
 }
 
 void
@@ -196,16 +219,15 @@ mousefunc_menu_unhide(struct client_ctx *cc, union arg *arg)
 	old_cc = client_current();
 
 	TAILQ_INIT(&menuq);
-	TAILQ_FOREACH(cc, &Clientq, entry)
+	TAILQ_FOREACH(cc, &Clientq, entry) {
 		if (cc->flags & CLIENT_HIDDEN) {
 			wname = (cc->label) ? cc->label : cc->name;
 			if (wname == NULL)
 				continue;
-
 			menuq_add(&menuq, cc, "(%d) %s",
-			    cc->group->shortcut, wname);
+			    cc->group ? cc->group->num : 0, wname);
 		}
-
+	}
 	if (TAILQ_EMPTY(&menuq))
 		return;
 
@@ -213,7 +235,6 @@ mousefunc_menu_unhide(struct client_ctx *cc, union arg *arg)
 	    NULL, NULL)) != NULL) {
 		cc = (struct client_ctx *)mi->ctx;
 		client_unhide(cc);
-
 		if (old_cc != NULL)
 			client_ptrsave(old_cc);
 		client_ptrwarp(cc);
@@ -233,7 +254,6 @@ mousefunc_menu_cmd(struct client_ctx *cc, union arg *arg)
 	TAILQ_INIT(&menuq);
 	TAILQ_FOREACH(cmd, &Conf.cmdq, entry)
 		menuq_add(&menuq, cmd, "%s", cmd->name);
-
 	if (TAILQ_EMPTY(&menuq))
 		return;
 
diff --git a/screen.c b/screen.c
index 59fa992..02aefcc 100644
--- a/screen.c
+++ b/screen.c
@@ -69,7 +69,6 @@ screen_init(int which)
 		XFree(wins);
 	}
 	screen_updatestackingorder(sc);
-	group_set_state(sc);
 
 	if (HasRandr)
 		XRRSelectInput(X_Dpy, sc->rootwin, RRScreenChangeNotifyMask);
@@ -105,7 +104,7 @@ screen_updatestackingorder(struct screen_ctx *sc)
 			if ((cc = client_find(wins[i])) == NULL ||
 			    cc->flags & CLIENT_HIDDEN)
 				continue;
-	
+
 			cc->stackingorder = s++;
 		}
 		XFree(wins);
@@ -142,7 +141,7 @@ screen_update_geometry(struct screen_ctx *sc)
 {
 	XineramaScreenInfo	*info = NULL;
 	struct region_ctx	*region;
-	int			 info_no = 0, i;
+	int			 info_num = 0, i;
 
 	sc->view.x = 0;
 	sc->view.y = 0;
@@ -156,13 +155,13 @@ screen_update_geometry(struct screen_ctx *sc)
 
 	/* RandR event may have a CTRC added or removed. */
 	if (XineramaIsActive(X_Dpy))
-		info = XineramaQueryScreens(X_Dpy, &info_no);
+		info = XineramaQueryScreens(X_Dpy, &info_num);
 
 	while ((region = TAILQ_FIRST(&sc->regionq)) != NULL) {
 		TAILQ_REMOVE(&sc->regionq, region, entry);
 		free(region);
 	}
-	for (i = 0; i < info_no; i++) {
+	for (i = 0; i < info_num; i++) {
 		region = xmalloc(sizeof(*region));
 		region->num = i;
 		region->area.x = info[i].x_org;
diff --git a/search.c b/search.c
index 980cb54..4012400 100644
--- a/search.c
+++ b/search.c
@@ -143,7 +143,7 @@ search_print_client(struct menu *mi, int list)
 		cc->matchname = cc->name;
 
 	(void)snprintf(mi->print, sizeof(mi->print), "(%d) %c%s",
-	    cc->group->shortcut, flag, cc->matchname);
+	    cc->group ? cc->group->num : 0, flag, cc->matchname);
 
 	if (!list && cc->matchname != cc->name &&
 	    strlen(mi->print) < sizeof(mi->print) - 1) {
diff --git a/xevents.c b/xevents.c
index 027f52a..c72bd0c 100644
--- a/xevents.c
+++ b/xevents.c
@@ -196,7 +196,7 @@ xev_handle_propertynotify(XEvent *ee)
 		TAILQ_FOREACH(sc, &Screenq, entry) {
 			if (sc->rootwin == e->window) {
 				if (e->atom == ewmh[_NET_DESKTOP_NAMES])
-					group_update_names(sc);
+					xu_ewmh_net_desktop_names(sc);
 			}
 		}
 	}
diff --git a/xutil.c b/xutil.c
index 7d39ee4..dd5bbc1 100644
--- a/xutil.c
+++ b/xutil.c
@@ -281,20 +281,67 @@ xu_ewmh_net_current_desktop(struct screen_ctx *sc, long idx)
 }
 
 void
-xu_ewmh_net_desktop_names(struct screen_ctx *sc, char *data, int n)
+xu_ewmh_net_desktop_names(struct screen_ctx *sc)
 {
+	struct group_ctx	*gc;
+	char			*p, *q;
+	unsigned char		*prop_ret;
+	int			 i = 0, j = 0, nstrings = 0, n = 0;
+	size_t			 len = 0, tlen, slen;
+
+	/* Let group names be overwritten if _NET_DESKTOP_NAMES is set. */
+
+	if ((j = xu_getprop(sc->rootwin, ewmh[_NET_DESKTOP_NAMES],
+	    cwmh[UTF8_STRING], 0xffffff, (unsigned char **)&prop_ret)) > 0) {
+		prop_ret[j - 1] = '\0'; /* paranoia */
+		while (i < j) {
+			if (prop_ret[i++] == '\0')
+				nstrings++;
+		}
+	}
+
+	p = (char *)prop_ret;
+	while (n < nstrings) {
+		TAILQ_FOREACH(gc, &sc->groupq, entry) {
+			if (gc->num == n) {
+				free(gc->name);
+				gc->name = xstrdup(p);
+				p += strlen(p) + 1;
+				break;
+			}
+		}
+		n++;
+	}
+	if (prop_ret != NULL)
+		XFree(prop_ret);
+
+	TAILQ_FOREACH(gc, &sc->groupq, entry)
+		len += strlen(gc->name) + 1;
+	q = p = xcalloc(len, sizeof(*p));
+
+	tlen = len;
+	TAILQ_FOREACH(gc, &sc->groupq, entry) {
+		slen = strlen(gc->name) + 1;
+		(void)strlcpy(q, gc->name, tlen);
+		tlen -= slen;
+		q += slen;
+	}
+
 	XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_DESKTOP_NAMES],
-	    cwmh[UTF8_STRING], 8, PropModeReplace, (unsigned char *)data, n);
+	    cwmh[UTF8_STRING], 8, PropModeReplace, (unsigned char *)p, len);
 }
 
 /* Application Window Properties */
 void
 xu_ewmh_net_wm_desktop(struct client_ctx *cc)
 {
-	long	 no = cc->group->shortcut;
+	long	 num = 0xffffffff;
+
+	if (cc->group)
+		num = cc->group->num;
 
 	XChangeProperty(X_Dpy, cc->win, ewmh[_NET_WM_DESKTOP],
-	    XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&no, 1);
+	    XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num, 1);
 }
 
 Atom *
@@ -323,6 +370,9 @@ xu_ewmh_handle_net_wm_state_msg(struct client_ctx *cc, int action,
 		int property;
 		void (*toggle)(struct client_ctx *);
 	} handlers[] = {
+		{ _NET_WM_STATE_STICKY,
+			CLIENT_STICKY,
+			client_sticky },
 		{ _NET_WM_STATE_MAXIMIZED_VERT,
 			CLIENT_VMAXIMIZED,
 			client_vmaximize },
@@ -364,6 +414,8 @@ xu_ewmh_restore_net_wm_state(struct client_ctx *cc)
 
 	atoms = xu_ewmh_get_net_wm_state(cc, &n);
 	for (i = 0; i < n; i++) {
+		if (atoms[i] == ewmh[_NET_WM_STATE_STICKY])
+			client_sticky(cc);
 		if (atoms[i] == ewmh[_NET_WM_STATE_MAXIMIZED_HORZ])
 			client_hmaximize(cc);
 		if (atoms[i] == ewmh[_NET_WM_STATE_MAXIMIZED_VERT])
@@ -388,10 +440,13 @@ xu_ewmh_set_net_wm_state(struct client_ctx *cc)
 		if (oatoms[i] != ewmh[_NET_WM_STATE_MAXIMIZED_HORZ] &&
 		    oatoms[i] != ewmh[_NET_WM_STATE_MAXIMIZED_VERT] &&
 		    oatoms[i] != ewmh[_NET_WM_STATE_FULLSCREEN] &&
+		    oatoms[i] != ewmh[_NET_WM_STATE_STICKY] &&
 		    oatoms[i] != ewmh[_NET_WM_STATE_DEMANDS_ATTENTION])
 			atoms[j++] = oatoms[i];
 	}
 	free(oatoms);
+	if (cc->flags & CLIENT_STICKY)
+		atoms[j++] = ewmh[_NET_WM_STATE_STICKY];
 	if (cc->flags & CLIENT_FULLSCREEN)
 		atoms[j++] = ewmh[_NET_WM_STATE_FULLSCREEN];
 	else {