]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Fix man errors
[spectrwm] / spectrwm.c
index 822d1f5d3d5feca94a448866b817914f99452956..285bac959e8cdee7b8b29241543dc0e0ab14d1dc 100644 (file)
@@ -1,14 +1,15 @@
 /*
- * Copyright (c) 2009-2012 Marco Peereboom <marco@peereboom.us>
+ * Copyright (c) 2009-2015 Marco Peereboom <marco@peereboom.us>
  * Copyright (c) 2009-2011 Ryan McBride <mcbride@countersiege.com>
  * Copyright (c) 2009 Darrin Chandler <dwchandler@stilyagin.com>
  * Copyright (c) 2009 Pierre-Yves Ritschard <pyr@spootnik.org>
  * Copyright (c) 2010 Tuukka Kataja <stuge@xor.fi>
  * Copyright (c) 2011 Jason L. Wright <jason@thought.net>
- * Copyright (c) 2011-2013 Reginald Kennedy <rk@rejii.com>
+ * Copyright (c) 2011-2016 Reginald Kennedy <rk@rejii.com>
  * Copyright (c) 2011-2012 Lawrence Teo <lteo@lteo.net>
  * Copyright (c) 2011-2012 Tiago Cunha <tcunha@gmx.com>
- * Copyright (c) 2012-2013 David Hill <dhill@mindcry.org>
+ * Copyright (c) 2012-2015 David Hill <dhill@mindcry.org>
+ * Copyright (c) 2014-2015 Yuri D'Elia <yuri.delia@eurac.edu>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-/*
- * Much code and ideas taken from dwm under the following license:
- * MIT/X Consortium License
- *
- * 2006-2008 Anselm R Garbe <garbeam at gmail dot com>
- * 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
- * 2006-2007 Jukka Salmi <jukka at salmi dot ch>
- * 2007 Premysl Hruby <dfenze at gmail dot com>
- * 2007 Szabolcs Nagy <nszabolcs at gmail dot com>
- * 2007 Christof Musik <christof at sendfax dot de>
- * 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
- * 2007-2008 Peter Hartlich <sgkkr at hartlich dot com>
- * 2008 Martin Hurton <martin dot hurton at gmail dot com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
 
 /* kernel includes */
 #include <sys/types.h>
 #include <sys/time.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
+#ifdef __OSX__
+#include "queue.h"
+#else
 #include <sys/queue.h>
+#endif
 #include <sys/param.h>
 #include <sys/select.h>
 #if defined(__linux__)
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
+#include <poll.h>
 #include <fcntl.h>
 #include <locale.h>
 #include <paths.h>
 #include <pwd.h>
 #include <regex.h>
 #include <signal.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -94,6 +69,7 @@
 #include <X11/Xcursor/Xcursor.h>
 #include <X11/Xft/Xft.h>
 #include <X11/Xlib-xcb.h>
+#include <xcb/xcb.h>
 #include <xcb/xcb_atom.h>
 #include <xcb/xcb_aux.h>
 #include <xcb/xcb_event.h>
@@ -114,9 +90,9 @@ static const char    *buildstr = SPECTRWM_BUILDSTR;
 static const char      *buildstr = SPECTRWM_VERSION;
 #endif
 
-#if !defined(__CYGWIN__) /* cygwin chokes on xrandr stuff */
+#if !defined(__CYGWIN__) /* cygwin chokes on randr stuff */
 #  if RANDR_MAJOR < 1
-#    error XRandR versions less than 1.0 are not supported
+#    error RandR versions less than 1.0 are not supported
 #endif
 
 #  if RANDR_MAJOR >= 1
@@ -232,19 +208,21 @@ uint32_t          swm_debug = 0
 
 #define LENGTH(x)              (int)(sizeof (x) / sizeof (x)[0])
 #define MODKEY                 XCB_MOD_MASK_1
-#define CLEANMASK(mask)                ((mask) & ~(numlockmask | XCB_MOD_MASK_LOCK))
+#define ANYMOD                 XCB_MOD_MASK_ANY
+#define CLEANMASK(mask)                ((mask) & (XCB_KEY_BUT_MASK_SHIFT |     \
+    XCB_KEY_BUT_MASK_CONTROL | XCB_KEY_BUT_MASK_MOD_1 |                        \
+    XCB_KEY_BUT_MASK_MOD_2 | XCB_KEY_BUT_MASK_MOD_3 |                  \
+    XCB_KEY_BUT_MASK_MOD_4 | XCB_KEY_BUT_MASK_MOD_5) & ~(numlockmask))
 #define BUTTONMASK             (XCB_EVENT_MASK_BUTTON_PRESS |          \
     XCB_EVENT_MASK_BUTTON_RELEASE)
 #define MOUSEMASK              (BUTTONMASK|XCB_EVENT_MASK_POINTER_MOTION)
 #define SWM_PROPLEN            (16)
 #define SWM_FUNCNAME_LEN       (32)
-#define SWM_KEYS_LEN           (255)
 #define SWM_QUIRK_LEN          (64)
 #define X(r)                   ((r)->g.x)
 #define Y(r)                   ((r)->g.y)
 #define WIDTH(r)               ((r)->g.w)
 #define HEIGHT(r)              ((r)->g.h)
-#define BORDER(w)              ((w)->bordered ? border_width : 0)
 #define MAX_X(r)               ((r)->g.x + (r)->g.w)
 #define MAX_Y(r)               ((r)->g.y + (r)->g.h)
 #define SH_MIN(w)              ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)
@@ -258,6 +236,8 @@ uint32_t            swm_debug = 0
 #define SH_INC_H(w)            ((w)->sh.height_inc)
 #define SWM_MAX_FONT_STEPS     (3)
 #define WINID(w)               ((w) ? (w)->id : XCB_WINDOW_NONE)
+#define ACCEPTS_FOCUS(w)       (!((w)->hints.flags & XCB_ICCCM_WM_HINT_INPUT) \
+    || ((w)->hints.input))
 #define WS_FOCUSED(ws)         ((ws)->r && (ws)->r->s->r_focus == (ws)->r)
 #define YESNO(x)               ((x) ? "yes" : "no")
 #define ICONIC(w)              ((w)->ewmh_flags & EWMH_F_HIDDEN)
@@ -285,17 +265,12 @@ uint32_t          swm_debug = 0
 #define SWM_FOCUS_FOLLOW       (1)
 #define SWM_FOCUS_MANUAL       (2)
 
-#define SWM_CK_NONE            (0)
 #define SWM_CK_ALL             (0xf)
 #define SWM_CK_FOCUS           (0x1)
 #define SWM_CK_POINTER         (0x2)
 #define SWM_CK_FALLBACK                (0x4)
 #define SWM_CK_REGION          (0x8)
 
-#define SWM_G_ALL              (0xf)
-#define SWM_G_SIZE             (0x1)
-#define SWM_G_POS              (0x2)
-
 #define SWM_CONF_DEFAULT       (0)
 #define SWM_CONF_KEYMAPPING    (1)
 
@@ -307,6 +282,7 @@ char                        **start_argv;
 xcb_atom_t             a_state;
 xcb_atom_t             a_prot;
 xcb_atom_t             a_delete;
+xcb_atom_t             a_net_frame_extents;
 xcb_atom_t             a_net_wm_check;
 xcb_atom_t             a_net_supported;
 xcb_atom_t             a_takefocus;
@@ -316,9 +292,8 @@ volatile sig_atomic_t   running = 1;
 volatile sig_atomic_t   restart_wm = 0;
 xcb_timestamp_t                last_event_time = 0;
 int                    outputs = 0;
-int                    other_wm;
-int                    xrandr_support;
-int                    xrandr_eventbase;
+bool                   randr_support;
+int                    randr_eventbase;
 unsigned int           numlockmask = 0;
 
 Display                        *display;
@@ -326,11 +301,14 @@ xcb_connection_t  *conn;
 xcb_key_symbols_t      *syms;
 
 int                    boundary_width = 50;
-int                    cycle_empty = 0;
-int                    cycle_visible = 0;
+bool                   cycle_empty = false;
+bool                   cycle_visible = false;
 int                    term_width = 0;
 int                    font_adjusted = 0;
-unsigned int           mod_key = MODKEY;
+uint16_t               mod_key = MODKEY;
+bool                   warp_focus = false;
+bool                   warp_pointer = false;
+bool                   workspace_clamp = false;
 
 /* dmenu search */
 struct swm_region      *search_r;
@@ -389,44 +367,48 @@ int                bar_pipe[2];
 char            bar_ext[SWM_BAR_MAX];
 char            bar_ext_buf[SWM_BAR_MAX];
 char            bar_vertext[SWM_BAR_MAX];
-int             bar_version = 0;
-int             bar_enabled = 1;
+bool            bar_version = false;
+bool            bar_enabled = true;
 int             bar_border_width = 1;
-int             bar_at_bottom = 0;
-int             bar_extra = 0;
-int             bar_verbose = 1;
+bool            bar_at_bottom = false;
+bool            bar_extra = false;
 int             bar_height = 0;
 int             bar_justify = SWM_BAR_JUSTIFY_LEFT;
-char            *bar_format = NULL;
-int             stack_enabled = 1;
-int             clock_enabled = 1;
-int             iconic_enabled = 0;
-int             urgent_enabled = 0;
+char           *bar_format = NULL;
+bool            stack_enabled = true;
+bool            clock_enabled = true;
+bool            iconic_enabled = false;
+bool            maximize_hide_bar = false;
+bool            urgent_enabled = false;
+bool            urgent_collapse = false;
 char           *clock_format = NULL;
-int             window_class_enabled = 0;
-int             window_instance_enabled = 0;
-int             window_name_enabled = 0;
+bool            window_class_enabled = false;
+bool            window_instance_enabled = false;
+bool            window_name_enabled = false;
 int             focus_mode = SWM_FOCUS_DEFAULT;
 int             focus_close = SWM_STACK_BELOW;
-int             focus_close_wrap = 1;
+bool            focus_close_wrap = true;
 int             focus_default = SWM_STACK_TOP;
 int             spawn_position = SWM_STACK_TOP;
-int             disable_border = 0;
+bool            disable_border = false;
 int             border_width = 1;
 int             region_padding = 0;
 int             tile_gap = 0;
-int             java_workaround = 1;
-int             verbose_layout = 0;
+bool            java_workaround = true;
+bool            verbose_layout = false;
+#ifdef SWM_DEBUG
+bool            debug_enabled;
 time_t          time_started;
+#endif
 pid_t           bar_pid;
-XFontSet        bar_fs;
+XFontSet        bar_fs = NULL;
 XFontSetExtents        *bar_fs_extents;
-XftFont                *bar_font;
-int             bar_font_legacy = 1;
-char           *bar_fonts;
+XftFont                *bar_font = NULL;
+bool            bar_font_legacy = true;
+char           *bar_fonts = NULL;
 XftColor        bar_font_color;
-struct passwd  *pwd;
-char           *startup_exception;
+XftColor        search_font_color;
+char           *startup_exception = NULL;
 unsigned int    nr_exceptions = 0;
 
 /* layout manager data */
@@ -444,6 +426,7 @@ struct swm_bar {
        xcb_window_t            id;
        xcb_pixmap_t            buffer;
        struct swm_geometry     g;
+       struct swm_region       *r;     /* Associated region. */
 };
 
 /* virtual "screens" */
@@ -458,30 +441,43 @@ struct swm_region {
 };
 TAILQ_HEAD(swm_region_list, swm_region);
 
+enum {
+       SWM_WIN_STATE_REPARENTING,
+       SWM_WIN_STATE_REPARENTED,
+       SWM_WIN_STATE_UNPARENTING,
+       SWM_WIN_STATE_UNPARENTED,
+};
+
 struct ws_win {
        TAILQ_ENTRY(ws_win)     entry;
        TAILQ_ENTRY(ws_win)     stack_entry;
        xcb_window_t            id;
+       xcb_window_t            frame;
        xcb_window_t            transient;
        struct ws_win           *focus_child;   /* focus on child transient */
        struct swm_geometry     g;              /* current geometry */
+       struct swm_geometry     g_prev;         /* prev configured geometry */
        struct swm_geometry     g_float;        /* region coordinates */
-       int                     g_floatvalid;   /* g_float geometry validity */
-       int                     mapped;
-       int                     bordered;
+       bool                    g_floatvalid;   /* g_float geometry validity */
+       bool                    mapped;
+       uint8_t                 state;
+       bool                    bordered;
        uint32_t                ewmh_flags;
        int                     font_size_boundary[SWM_MAX_FONT_STEPS];
        int                     font_steps;
        int                     last_inc;
-       int                     can_delete;
-       int                     take_focus;
-       int                     java;
-       unsigned long           quirks;
+       bool                    can_delete;
+       bool                    take_focus;
+       bool                    java;
+       uint32_t                quirks;
        struct workspace        *ws;    /* always valid */
        struct swm_screen       *s;     /* always valid, never changes */
        xcb_size_hints_t        sh;
        xcb_icccm_get_wm_class_reply_t  ch;
        xcb_icccm_wm_hints_t    hints;
+#ifdef SWM_DEBUG
+       xcb_window_t            debug;
+#endif
 };
 TAILQ_HEAD(ws_win_list, ws_win);
 TAILQ_HEAD(ws_win_stack, ws_win);
@@ -496,7 +492,7 @@ TAILQ_HEAD(pid_list, pid_e);
 struct pid_list                        pidlist = TAILQ_HEAD_INITIALIZER(pidlist);
 
 /* layout handlers */
-void   stack(void);
+void   stack(struct swm_region *);
 void   vertical_config(struct workspace *, int);
 void   vertical_stack(struct workspace *, struct swm_geometry *);
 void   horizontal_config(struct workspace *, int);
@@ -533,12 +529,13 @@ struct layout {
 struct workspace {
        int                     idx;            /* workspace index */
        char                    *name;          /* workspace name */
-       int                     always_raise;   /* raise windows on focus */
-       int                     bar_enabled;    /* bar visibility */
+       bool                    always_raise;   /* raise windows on focus */
+       bool                    bar_enabled;    /* bar visibility */
        struct layout           *cur_layout;    /* current layout handlers */
        struct ws_win           *focus;         /* may be NULL */
        struct ws_win           *focus_prev;    /* may be NULL */
        struct ws_win           *focus_pending; /* may be NULL */
+       struct ws_win           *focus_raise;           /* may be NULL */
        struct swm_region       *r;             /* may be NULL */
        struct swm_region       *old_r;         /* may be NULL */
        struct ws_win_list      winlist;        /* list of windows in ws */
@@ -552,11 +549,11 @@ struct workspace {
                                int horizontal_msize;
                                int horizontal_mwin;
                                int horizontal_stacks;
-                               int horizontal_flip;
+                               bool horizontal_flip;
                                int vertical_msize;
                                int vertical_mwin;
                                int vertical_stacks;
-                               int vertical_flip;
+                               bool vertical_flip;
        } l_state;
 };
 
@@ -620,6 +617,7 @@ union arg {
 #define SWM_ARG_ID_FLIPLAYOUT  (24)
 #define SWM_ARG_ID_STACKRESET  (30)
 #define SWM_ARG_ID_STACKINIT   (31)
+#define SWM_ARG_ID_STACKBALANCE        (32)
 #define SWM_ARG_ID_CYCLEWS_UP  (40)
 #define SWM_ARG_ID_CYCLEWS_DOWN        (41)
 #define SWM_ARG_ID_CYCLERG_UP  (42)
@@ -630,8 +628,6 @@ union arg {
 #define SWM_ARG_ID_CYCLEWS_MOVE_DOWN   (47)
 #define SWM_ARG_ID_STACKINC    (50)
 #define SWM_ARG_ID_STACKDEC    (51)
-#define SWM_ARG_ID_SS_ALL      (60)
-#define SWM_ARG_ID_SS_WINDOW   (61)
 #define SWM_ARG_ID_DONTCENTER  (70)
 #define SWM_ARG_ID_CENTER      (71)
 #define SWM_ARG_ID_KILLWINDOW  (80)
@@ -644,10 +640,10 @@ union arg {
 #define SWM_ARG_ID_MOVEDOWN    (101)
 #define SWM_ARG_ID_MOVELEFT    (102)
 #define SWM_ARG_ID_MOVERIGHT   (103)
-#define SWM_ARG_ID_RAISE       (105)
-#define SWM_ARG_ID_LOWER       (106)
 #define SWM_ARG_ID_BAR_TOGGLE  (110)
 #define SWM_ARG_ID_BAR_TOGGLE_WS       (111)
+#define SWM_ARG_ID_CYCLERG_MOVE_UP     (112)
+#define SWM_ARG_ID_CYCLERG_MOVE_DOWN   (113)
        char                    **argv;
 };
 
@@ -660,15 +656,21 @@ struct quirk {
        regex_t                 regex_class;
        regex_t                 regex_instance;
        regex_t                 regex_name;
-       unsigned long           quirk;
-#define SWM_Q_FLOAT            (1<<0)  /* float this window */
-#define SWM_Q_TRANSSZ          (1<<1)  /* transiend window size too small */
-#define SWM_Q_ANYWHERE         (1<<2)  /* don't position this window */
-#define SWM_Q_XTERM_FONTADJ    (1<<3)  /* adjust xterm fonts when resizing */
-#define SWM_Q_FULLSCREEN       (1<<4)  /* remove border */
-#define SWM_Q_FOCUSPREV                (1<<5)  /* focus on caller */
+       uint32_t                quirk;
+       int                     ws;             /* Initial workspace. */
+#define SWM_Q_FLOAT            (1<<0)  /* Float this window. */
+#define SWM_Q_TRANSSZ          (1<<1)  /* Transient window size too small. */
+#define SWM_Q_ANYWHERE         (1<<2)  /* Don't position this window */
+#define SWM_Q_XTERM_FONTADJ    (1<<3)  /* Adjust xterm fonts when resizing. */
+#define SWM_Q_FULLSCREEN       (1<<4)  /* Remove border when fullscreen. */
+#define SWM_Q_FOCUSPREV                (1<<5)  /* Focus on caller. */
 #define SWM_Q_NOFOCUSONMAP     (1<<6)  /* Don't focus on window when mapped. */
 #define SWM_Q_FOCUSONMAP_SINGLE        (1<<7)  /* Only focus if single win of type. */
+#define SWM_Q_OBEYAPPFOCUSREQ  (1<<8)  /* Focus when applications ask. */
+#define SWM_Q_IGNOREPID                (1<<9)  /* Ignore PID when determining ws. */
+#define SWM_Q_IGNORESPAWNWS    (1<<10) /* Ignore _SWM_WS when managing win. */
+#define SWM_Q_NOFOCUSCYCLE     (1<<11) /* Remove from normal focus cycle. */
+#define SWM_Q_MINIMALBORDER    (1<<12) /* No border when floating/unfocused. */
 };
 TAILQ_HEAD(quirk_list, quirk);
 struct quirk_list              quirks = TAILQ_HEAD_INITIALIZER(quirks);
@@ -687,6 +689,7 @@ enum {
        _NET_DESKTOP_VIEWPORT,
        _NET_MOVERESIZE_WINDOW,
        _NET_NUMBER_OF_DESKTOPS,
+       _NET_REQUEST_FRAME_EXTENTS,
        _NET_RESTACK_WINDOW,
        _NET_WM_ACTION_ABOVE,
        _NET_WM_ACTION_CLOSE,
@@ -730,6 +733,7 @@ struct ewmh_hint {
     {"_NET_DESKTOP_VIEWPORT", XCB_ATOM_NONE},
     {"_NET_MOVERESIZE_WINDOW", XCB_ATOM_NONE},
     {"_NET_NUMBER_OF_DESKTOPS", XCB_ATOM_NONE},
+    {"_NET_REQUEST_FRAME_EXTENTS", XCB_ATOM_NONE},
     {"_NET_RESTACK_WINDOW", XCB_ATOM_NONE},
     {"_NET_WM_ACTION_ABOVE", XCB_ATOM_NONE},
     {"_NET_WM_ACTION_CLOSE", XCB_ATOM_NONE},
@@ -804,134 +808,163 @@ struct spawn_prog {
 TAILQ_HEAD(spawn_list, spawn_prog);
 struct spawn_list              spawns = TAILQ_HEAD_INITIALIZER(spawns);
 
-/* user/key callable function IDs */
-enum keyfuncid {
-       KF_BAR_TOGGLE,
-       KF_BAR_TOGGLE_WS,
-       KF_BUTTON2,
-       KF_CYCLE_LAYOUT,
-       KF_FLIP_LAYOUT,
-       KF_FLOAT_TOGGLE,
-       KF_FOCUS_MAIN,
-       KF_FOCUS_NEXT,
-       KF_FOCUS_PREV,
-       KF_FOCUS_URGENT,
-       KF_MAXIMIZE_TOGGLE,
-       KF_HEIGHT_GROW,
-       KF_HEIGHT_SHRINK,
-       KF_ICONIFY,
-       KF_MASTER_SHRINK,
-       KF_MASTER_GROW,
-       KF_MASTER_ADD,
-       KF_MASTER_DEL,
-       KF_MOVE_DOWN,
-       KF_MOVE_LEFT,
-       KF_MOVE_RIGHT,
-       KF_MOVE_UP,
-       KF_MVRG_1,
-       KF_MVRG_2,
-       KF_MVRG_3,
-       KF_MVRG_4,
-       KF_MVRG_5,
-       KF_MVRG_6,
-       KF_MVRG_7,
-       KF_MVRG_8,
-       KF_MVRG_9,
-       KF_MVWS_1,
-       KF_MVWS_2,
-       KF_MVWS_3,
-       KF_MVWS_4,
-       KF_MVWS_5,
-       KF_MVWS_6,
-       KF_MVWS_7,
-       KF_MVWS_8,
-       KF_MVWS_9,
-       KF_MVWS_10,
-       KF_MVWS_11,
-       KF_MVWS_12,
-       KF_MVWS_13,
-       KF_MVWS_14,
-       KF_MVWS_15,
-       KF_MVWS_16,
-       KF_MVWS_17,
-       KF_MVWS_18,
-       KF_MVWS_19,
-       KF_MVWS_20,
-       KF_MVWS_21,
-       KF_MVWS_22,
-       KF_NAME_WORKSPACE,
-       KF_QUIT,
-       KF_RAISE_TOGGLE,
-       KF_RESTART,
-       KF_RG_1,
-       KF_RG_2,
-       KF_RG_3,
-       KF_RG_4,
-       KF_RG_5,
-       KF_RG_6,
-       KF_RG_7,
-       KF_RG_8,
-       KF_RG_9,
-       KF_RG_NEXT,
-       KF_RG_PREV,
-       KF_SCREEN_NEXT,
-       KF_SCREEN_PREV,
-       KF_SEARCH_WIN,
-       KF_SEARCH_WORKSPACE,
-       KF_SPAWN_CUSTOM,
-       KF_STACK_INC,
-       KF_STACK_DEC,
-       KF_STACK_RESET,
-       KF_SWAP_MAIN,
-       KF_SWAP_NEXT,
-       KF_SWAP_PREV,
-       KF_UNICONIFY,
-       KF_VERSION,
-       KF_WIDTH_GROW,
-       KF_WIDTH_SHRINK,
-       KF_WIND_DEL,
-       KF_WIND_KILL,
-       KF_WS_1,
-       KF_WS_2,
-       KF_WS_3,
-       KF_WS_4,
-       KF_WS_5,
-       KF_WS_6,
-       KF_WS_7,
-       KF_WS_8,
-       KF_WS_9,
-       KF_WS_10,
-       KF_WS_11,
-       KF_WS_12,
-       KF_WS_13,
-       KF_WS_14,
-       KF_WS_15,
-       KF_WS_16,
-       KF_WS_17,
-       KF_WS_18,
-       KF_WS_19,
-       KF_WS_20,
-       KF_WS_21,
-       KF_WS_22,
-       KF_WS_NEXT,
-       KF_WS_NEXT_ALL,
-       KF_WS_NEXT_MOVE,
-       KF_WS_PREV,
-       KF_WS_PREV_ALL,
-       KF_WS_PREV_MOVE,
-       KF_WS_PRIOR,
-       KF_DUMPWINS, /* MUST BE LAST */
-       KF_INVALID
+enum {
+       FN_F_NOREPLAY = 0x1,
+};
+
+/* User callable function IDs. */
+enum actionid {
+       FN_BAR_TOGGLE,
+       FN_BAR_TOGGLE_WS,
+       FN_BUTTON2,
+       FN_CYCLE_LAYOUT,
+       FN_FLIP_LAYOUT,
+       FN_FLOAT_TOGGLE,
+       FN_FOCUS,
+       FN_FOCUS_MAIN,
+       FN_FOCUS_NEXT,
+       FN_FOCUS_PREV,
+       FN_FOCUS_URGENT,
+       FN_FULLSCREEN_TOGGLE,
+       FN_MAXIMIZE_TOGGLE,
+       FN_HEIGHT_GROW,
+       FN_HEIGHT_SHRINK,
+       FN_ICONIFY,
+       FN_MASTER_SHRINK,
+       FN_MASTER_GROW,
+       FN_MASTER_ADD,
+       FN_MASTER_DEL,
+       FN_MOVE,
+       FN_MOVE_DOWN,
+       FN_MOVE_LEFT,
+       FN_MOVE_RIGHT,
+       FN_MOVE_UP,
+       FN_MVRG_1,
+       FN_MVRG_2,
+       FN_MVRG_3,
+       FN_MVRG_4,
+       FN_MVRG_5,
+       FN_MVRG_6,
+       FN_MVRG_7,
+       FN_MVRG_8,
+       FN_MVRG_9,
+       KF_MVRG_NEXT,
+       KF_MVRG_PREV,
+       FN_MVWS_1,
+       FN_MVWS_2,
+       FN_MVWS_3,
+       FN_MVWS_4,
+       FN_MVWS_5,
+       FN_MVWS_6,
+       FN_MVWS_7,
+       FN_MVWS_8,
+       FN_MVWS_9,
+       FN_MVWS_10,
+       FN_MVWS_11,
+       FN_MVWS_12,
+       FN_MVWS_13,
+       FN_MVWS_14,
+       FN_MVWS_15,
+       FN_MVWS_16,
+       FN_MVWS_17,
+       FN_MVWS_18,
+       FN_MVWS_19,
+       FN_MVWS_20,
+       FN_MVWS_21,
+       FN_MVWS_22,
+       FN_NAME_WORKSPACE,
+       FN_QUIT,
+       FN_RAISE,
+       FN_RAISE_TOGGLE,
+       FN_RESIZE,
+       FN_RESIZE_CENTERED,
+       FN_RESTART,
+       FN_RG_1,
+       FN_RG_2,
+       FN_RG_3,
+       FN_RG_4,
+       FN_RG_5,
+       FN_RG_6,
+       FN_RG_7,
+       FN_RG_8,
+       FN_RG_9,
+       FN_RG_MOVE_NEXT,
+       FN_RG_MOVE_PREV,
+       FN_RG_NEXT,
+       FN_RG_PREV,
+       FN_SCREEN_NEXT,
+       FN_SCREEN_PREV,
+       FN_SEARCH_WIN,
+       FN_SEARCH_WORKSPACE,
+       FN_SPAWN_CUSTOM,
+       FN_STACK_BALANCE,
+       FN_STACK_INC,
+       FN_STACK_DEC,
+       FN_STACK_RESET,
+       FN_SWAP_MAIN,
+       FN_SWAP_NEXT,
+       FN_SWAP_PREV,
+       FN_UNICONIFY,
+       FN_VERSION,
+       FN_WIDTH_GROW,
+       FN_WIDTH_SHRINK,
+       FN_WIND_DEL,
+       FN_WIND_KILL,
+       FN_WS_1,
+       FN_WS_2,
+       FN_WS_3,
+       FN_WS_4,
+       FN_WS_5,
+       FN_WS_6,
+       FN_WS_7,
+       FN_WS_8,
+       FN_WS_9,
+       FN_WS_10,
+       FN_WS_11,
+       FN_WS_12,
+       FN_WS_13,
+       FN_WS_14,
+       FN_WS_15,
+       FN_WS_16,
+       FN_WS_17,
+       FN_WS_18,
+       FN_WS_19,
+       FN_WS_20,
+       FN_WS_21,
+       FN_WS_22,
+       FN_WS_NEXT,
+       FN_WS_NEXT_ALL,
+       FN_WS_NEXT_MOVE,
+       FN_WS_PREV,
+       FN_WS_PREV_ALL,
+       FN_WS_PREV_MOVE,
+       FN_WS_PRIOR,
+       /* SWM_DEBUG actions MUST be here: */
+       FN_DEBUG_TOGGLE,
+       FN_DUMPWINS,
+       /* ALWAYS last: */
+       FN_INVALID
+};
+
+enum binding_type {
+       KEYBIND,
+       BTNBIND
+};
+
+enum {
+       BINDING_F_REPLAY = 0x1,
 };
 
-struct key {
-        RB_ENTRY(key)           entry;
-        unsigned int            mod;
-        KeySym                  keysym;
-        enum keyfuncid          funcid;
-        char                    *spawn_name;
+struct binding {
+       RB_ENTRY(binding)       entry;
+       uint16_t                mod;            /* Modifier Mask. */
+       enum binding_type       type;           /* Key or Button. */
+       uint32_t                value;          /* KeySym or Button Index. */
+       enum actionid           action;         /* Action Identifier. */
+       uint32_t                flags;
+       char                    *spawn_name;
 };
-RB_HEAD(key_tree, key);
+RB_HEAD(binding_tree, binding);
 
 /* function prototypes */
 void    adjust_font(struct ws_win *);
@@ -942,14 +975,14 @@ void       bar_extra_stop(void);
 int     bar_extra_update(void);
 void    bar_fmt(const char *, char *, struct swm_region *, size_t);
 void    bar_fmt_expand(char *, size_t);
-void    bar_draw(void);
+void    bar_draw(struct swm_bar *);
 void    bar_print(struct swm_region *, const char *);
 void    bar_print_legacy(struct swm_region *, const char *);
 void    bar_replace(char *, char *, struct swm_region *, size_t);
 void    bar_replace_pad(char *, int *, size_t);
 char   *bar_replace_seq(char *, char *, struct swm_region *, size_t *, size_t);
 void    bar_setup(struct swm_region *);
-void    bar_toggle(struct swm_region *, union arg *);
+void    bar_toggle(struct binding *, struct swm_region *, union arg *);
 void    bar_urgent(char *, size_t);
 void    bar_window_class(char *, size_t, struct swm_region *);
 void    bar_window_class_instance(char *, size_t, struct swm_region *);
@@ -958,10 +991,20 @@ void       bar_window_instance(char *, size_t, struct swm_region *);
 void    bar_window_name(char *, size_t, struct swm_region *);
 void    bar_window_state(char *, size_t, struct swm_region *);
 void    bar_workspace_name(char *, size_t, struct swm_region *);
+int     binding_cmp(struct binding *, struct binding *);
+void    binding_insert(uint16_t, enum binding_type, uint32_t, enum actionid,
+            uint32_t, const char *);
+struct binding *binding_lookup(uint16_t, enum binding_type, uint32_t);
+void    binding_remove(struct binding *);
 void    buttonpress(xcb_button_press_event_t *);
+void    buttonrelease(xcb_button_release_event_t *);
+void    center_pointer(struct swm_region *);
 void    check_conn(void);
-void    clear_keys(void);
+void    clear_bindings(void);
+void    clear_keybindings(void);
 int     clear_maximized(struct workspace *);
+void    clear_quirks(void);
+void    clear_spawns(void);
 void    clientmessage(xcb_client_message_event_t *);
 void    client_msg(struct ws_win *, xcb_atom_t, xcb_timestamp_t);
 int     conf_load(const char *, int);
@@ -969,15 +1012,19 @@ void      configurenotify(xcb_configure_notify_event_t *);
 void    configurerequest(xcb_configure_request_event_t *);
 void    config_win(struct ws_win *, xcb_configure_request_event_t *);
 void    constrain_window(struct ws_win *, struct swm_geometry *, int *);
-int     count_win(struct workspace *, int);
+int     count_win(struct workspace *, bool);
 void    cursors_cleanup(void);
 void    cursors_load(void);
 void    custom_region(const char *);
-void    cyclerg(struct swm_region *, union arg *);
-void    cyclews(struct swm_region *, union arg *);
-void    cycle_layout(struct swm_region *, union arg *);
+void    cycle_layout(struct binding *, struct swm_region *, union arg *);
+void    cyclerg(struct binding *, struct swm_region *, union arg *);
+void    cyclews(struct binding *, struct swm_region *, union arg *);
+#ifdef SWM_DEBUG
+void    debug_refresh(struct ws_win *);
+#endif
+void    debug_toggle(struct binding *, struct swm_region *, union arg *);
 void    destroynotify(xcb_destroy_notify_event_t *);
-void    dumpwins(struct swm_region *, union arg *);
+void    dumpwins(struct binding *, struct swm_region *, union arg *);
 int     enable_wm(void);
 void    enternotify(xcb_enter_notify_event_t *);
 void    event_drain(uint8_t);
@@ -997,27 +1044,33 @@ void      ewmh_update_wm_state(struct ws_win *);
 char   *expand_tilde(const char *);
 void    expose(xcb_expose_event_t *);
 void    fake_keypress(struct ws_win *, xcb_keysym_t, uint16_t);
+struct swm_bar *find_bar(xcb_window_t);
+struct ws_win  *find_frame_window(xcb_window_t);
 struct pid_e   *find_pid(pid_t);
+struct swm_region      *find_region(xcb_window_t);
 struct ws_win  *find_unmanaged_window(xcb_window_t);
 struct ws_win  *find_window(xcb_window_t);
-void    floating_toggle(struct swm_region *, union arg *);
-void    focus(struct swm_region *, union arg *);
-#ifdef SWM_DEBUG
-void    focusin(xcb_focus_in_event_t *);
-void    focusout(xcb_focus_out_event_t *);
-#endif
+void    floating_toggle(struct binding *, struct swm_region *, union arg *);
+void    focus(struct binding *, struct swm_region *, union arg *);
 void    focus_flush(void);
+void    focus_pointer(struct binding *, struct swm_region *, union arg *);
 void    focus_region(struct swm_region *);
-void    focusrg(struct swm_region *, union arg *);
 void    focus_win(struct ws_win *);
+void    focusin(xcb_focus_in_event_t *);
+#ifdef SWM_DEBUG
+void    focusout(xcb_focus_out_event_t *);
+#endif
+void    focusrg(struct binding *, struct swm_region *, union arg *);
 void    fontset_init(void);
 void    free_window(struct ws_win *);
+void    fullscreen_toggle(struct binding *, struct swm_region *, union arg *);
 xcb_atom_t get_atom_from_string(const char *);
 #ifdef SWM_DEBUG
 char   *get_atom_name(xcb_atom_t);
 #endif
 struct ws_win   *get_focus_magic(struct ws_win *);
 struct ws_win   *get_focus_prev(struct ws_win *);
+xcb_generic_event_t    *get_next_event(bool);
 #ifdef SWM_DEBUG
 char   *get_notify_detail_label(uint8_t);
 char   *get_notify_mode_label(uint8_t);
@@ -1028,81 +1081,93 @@ int      get_region_index(struct swm_region *);
 xcb_screen_t   *get_screen(int);
 int     get_screen_count(void);
 #ifdef SWM_DEBUG
+char   *get_source_type_label(uint32_t);
 char   *get_stack_mode_name(uint8_t);
+char   *get_state_mask_label(uint16_t);
 #endif
 int32_t         get_swm_ws(xcb_window_t);
+bool    get_urgent(struct ws_win *);
+#ifdef SWM_DEBUG
+char   *get_win_input_model(struct ws_win *);
+#endif
 char   *get_win_name(xcb_window_t);
 uint8_t         get_win_state(xcb_window_t);
 void    get_wm_protocols(struct ws_win *);
-int     get_ws_idx(xcb_window_t);
-void    grabbuttons(struct ws_win *);
-void    grabkeys(void);
+int     get_ws_idx(struct ws_win *);
 void    grab_windows(void);
-void    iconify(struct swm_region *, union arg *);
-int     isxlfd(char *);
+void    grabbuttons(void);
+void    grabkeys(void);
+void    iconify(struct binding *, struct swm_region *, union arg *);
+bool    isxlfd(char *);
+bool    keybindreleased(struct binding *, xcb_key_release_event_t *);
 void    keypress(xcb_key_press_event_t *);
-int     key_cmp(struct key *, struct key *);
-void    key_insert(unsigned int, KeySym, enum keyfuncid, const char *);
-struct key     *key_lookup(unsigned int, KeySym);
-void    key_remove(struct key *);
-void    key_replace(struct key *, unsigned int, KeySym, enum keyfuncid,
-            const char *);
+void    keyrelease(xcb_key_release_event_t *);
+bool    keyrepeating(xcb_key_release_event_t *);
 void    kill_bar_extra_atexit(void);
 void    kill_refs(struct ws_win *);
 #ifdef SWM_DEBUG
 void    leavenotify(xcb_leave_notify_event_t *);
 #endif
 void    load_float_geom(struct ws_win *);
-struct ws_win  *manage_window(xcb_window_t, int);
+void    lower_window(struct ws_win *);
+struct ws_win  *manage_window(xcb_window_t, int, bool);
 void    map_window(struct ws_win *);
 void    mapnotify(xcb_map_notify_event_t *);
 void    mappingnotify(xcb_mapping_notify_event_t *);
 void    maprequest(xcb_map_request_event_t *);
-void    maximize_toggle(struct swm_region *, union arg *);
+void    maximize_toggle(struct binding *, struct swm_region *, union arg *);
 void    motionnotify(xcb_motion_notify_event_t *);
-void    move(struct ws_win *, union arg *);
-void    move_step(struct swm_region *, union arg *);
+void    move(struct binding *, struct swm_region *, union arg *);
+void    move_win(struct ws_win *, struct binding *, int);
 uint32_t name_to_pixel(int, const char *);
-void    name_workspace(struct swm_region *, union arg *);
+void    name_workspace(struct binding *, struct swm_region *, union arg *);
 void    new_region(struct swm_screen *, int, int, int, int);
-int     parsekeys(const char *, unsigned int, unsigned int *, KeySym *);
-int     parsequirks(const char *, unsigned long *);
 int     parse_rgb(const char *, uint16_t *, uint16_t *, uint16_t *);
-void    pressbutton(struct swm_region *, union arg *);
-void    priorws(struct swm_region *, union arg *);
+int     parsebinding(const char *, uint16_t *, enum binding_type *, uint32_t *,
+            uint32_t *);
+int     parsequirks(const char *, uint32_t *, int *);
+void    pressbutton(struct binding *, struct swm_region *, union arg *);
+void    priorws(struct binding *, struct swm_region *, union arg *);
 #ifdef SWM_DEBUG
 void    print_win_geom(xcb_window_t);
 #endif
 void    propertynotify(xcb_property_notify_event_t *);
+void    put_back_event(xcb_generic_event_t *);
 void    quirk_free(struct quirk *);
-void    quirk_insert(const char *, const char *, const char *,unsigned long);
+void    quirk_insert(const char *, const char *, const char *, uint32_t, int);
 void    quirk_remove(struct quirk *);
 void    quirk_replace(struct quirk *, const char *, const char *, const char *,
-            unsigned long);
-void    quit(struct swm_region *, union arg *);
-void    raise_toggle(struct swm_region *, union arg *);
+            uint32_t, int);
+void    quit(struct binding *, struct swm_region *, union arg *);
+void    raise_focus(struct binding *, struct swm_region *, union arg *);
+void    raise_toggle(struct binding *, struct swm_region *, union arg *);
 void    raise_window(struct ws_win *);
 void    region_containment(struct ws_win *, struct swm_region *, int);
 struct swm_region      *region_under(struct swm_screen *, int, int);
 void    regionize(struct ws_win *, int, int);
-void    resize(struct ws_win *, union arg *);
-void    resize_step(struct swm_region *, union arg *);
-void    restart(struct swm_region *, union arg *);
+void    reparent_window(struct ws_win *);
+void    reparentnotify(xcb_reparent_notify_event_t *);
+void    resize(struct binding *, struct swm_region *, union arg *);
+void    resize_win(struct ws_win *, struct binding *, int);
+void    restart(struct binding *, struct swm_region *, union arg *);
 struct swm_region      *root_to_region(xcb_window_t, int);
 void    screenchange(xcb_randr_screen_change_notify_event_t *);
-void    scan_xrandr(int);
+void    scan_randr(int);
 void    search_do_resp(void);
 void    search_resp_name_workspace(const char *, size_t);
 void    search_resp_search_window(const char *);
 void    search_resp_search_workspace(const char *);
 void    search_resp_uniconify(const char *, size_t);
-void    search_win(struct swm_region *, union arg *);
+void    search_win(struct binding *, struct swm_region *, union arg *);
 void    search_win_cleanup(void);
-void    search_workspace(struct swm_region *, union arg *);
-void    send_to_rg(struct swm_region *, union arg *);
-void    send_to_ws(struct swm_region *, union arg *);
+void    search_workspace(struct binding *, struct swm_region *, union arg *);
+void    send_to_rg(struct binding *, struct swm_region *, union arg *);
+void    send_to_rg_relative(struct binding *, struct swm_region *, union arg *);
+void    send_to_ws(struct binding *, struct swm_region *, union arg *);
 void    set_region(struct swm_region *);
 int     setautorun(const char *, const char *, int);
+void    setbinding(uint16_t, enum binding_type, uint32_t, enum actionid,
+            uint32_t, const char *);
 int     setconfbinding(const char *, const char *, int);
 int     setconfcolor(const char *, const char *, int);
 int     setconfmodkey(const char *, const char *, int);
@@ -1110,15 +1175,15 @@ int      setconfquirk(const char *, const char *, int);
 int     setconfregion(const char *, const char *, int);
 int     setconfspawn(const char *, const char *, int);
 int     setconfvalue(const char *, const char *, int);
-void    setkeybinding(unsigned int, KeySym, enum keyfuncid, const char *);
 int     setkeymapping(const char *, const char *, int);
 int     setlayout(const char *, const char *, int);
-void    setquirk(const char *, const char *, const char *,unsigned long);
+void    setquirk(const char *, const char *, const char *, uint32_t, int);
 void    setscreencolor(const char *, int, int);
 void    setspawn(const char *, const char *, int);
+void    setup_btnbindings(void);
 void    setup_ewmh(void);
 void    setup_globals(void);
-void    setup_keys(void);
+void    setup_keybindings(void);
 void    setup_quirks(void);
 void    setup_screens(void);
 void    setup_spawn(void);
@@ -1128,7 +1193,7 @@ void       shutdown_cleanup(void);
 void    sighdlr(int);
 void    socket_setnonblock(int);
 void    sort_windows(struct ws_win_list *);
-void    spawn(int, union arg *, int);
+void    spawn(int, union arg *, bool);
 void    spawn_custom(struct swm_region *, union arg *, const char *);
 int     spawn_expand(struct swm_region *, union arg *, const char *, char ***);
 void    spawn_insert(const char *, const char *, int);
@@ -1136,42 +1201,43 @@ struct spawn_prog       *spawn_find(const char *);
 void    spawn_remove(struct spawn_prog *);
 void    spawn_replace(struct spawn_prog *, const char *, const char *, int);
 void    spawn_select(struct swm_region *, union arg *, const char *, int *);
-void    stack_config(struct swm_region *, union arg *);
-void    stack_master(struct workspace *, struct swm_geometry *, int, int);
+void    stack_config(struct binding *, struct swm_region *, union arg *);
+void    stack_master(struct workspace *, struct swm_geometry *, int, bool);
 void    store_float_geom(struct ws_win *);
 char   *strdupsafe(const char *);
-void    swapwin(struct swm_region *, union arg *);
-void    switchws(struct swm_region *, union arg *);
+void    swapwin(struct binding *, struct swm_region *, union arg *);
+void    switchws(struct binding *, struct swm_region *, union arg *);
 void    teardown_ewmh(void);
 void    unescape_selector(char *);
 void    unfocus_win(struct ws_win *);
-void    uniconify(struct swm_region *, union arg *);
+void    uniconify(struct binding *, struct swm_region *, union arg *);
 void    unmanage_window(struct ws_win *);
-void    unmapnotify(xcb_unmap_notify_event_t *);
 void    unmap_all(void);
 void    unmap_window(struct ws_win *);
-void    updatenumlockmask(void);
+void    unmapnotify(xcb_unmap_notify_event_t *);
+void    unparent_window(struct ws_win *);
 void    update_floater(struct ws_win *);
-void    update_modkey(unsigned int);
+void    update_modkey(uint16_t);
 void    update_win_stacking(struct ws_win *);
 void    update_window(struct ws_win *);
-void    update_window_color(struct ws_win *);
+void    draw_frame(struct ws_win *);
 void    update_wm_state(struct  ws_win *win);
+void    updatenumlockmask(void);
 void    validate_spawns(void);
 int     validate_win(struct ws_win *);
 int     validate_ws(struct workspace *);
-void    version(struct swm_region *, union arg *);
-void    win_to_ws(struct ws_win *, int, int);
+void    version(struct binding *, struct swm_region *, union arg *);
+void    win_to_ws(struct ws_win *, int, bool);
 pid_t   window_get_pid(xcb_window_t);
-void    wkill(struct swm_region *, union arg *);
+void    wkill(struct binding *, struct swm_region *, union arg *);
 void    update_ws_stack(struct workspace *);
 void    xft_init(struct swm_region *);
 void    _add_startup_exception(const char *, va_list);
 void    add_startup_exception(const char *, ...);
 
-RB_PROTOTYPE(key_tree, key, entry, key_cmp);
-RB_GENERATE(key_tree, key, entry, key_cmp);
-struct key_tree                 keys;
+RB_PROTOTYPE(binding_tree, binding, entry, binding_cmp);
+RB_GENERATE(binding_tree, binding, entry, binding_cmp);
+struct binding_tree                 bindings;
 
 void
 cursors_load(void)
@@ -1364,9 +1430,9 @@ get_wm_protocols(struct ws_win *win) {
            &wpr, NULL)) {
                for (i = 0; i < (int)wpr.atoms_len; i++) {
                        if (wpr.atoms[i] == a_takefocus)
-                               win->take_focus = 1;
+                               win->take_focus = true;
                        if (wpr.atoms[i] == a_delete)
-                               win->can_delete = 1;
+                               win->can_delete = true;
                }
                xcb_icccm_get_wm_protocols_reply_wipe(&wpr);
        }
@@ -1396,18 +1462,9 @@ setup_ewmh(void)
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
                    a_net_wm_check, XCB_ATOM_WINDOW, 32, 1, &win);
 
-               /*
-                * Impersonate LG3D non-reparenting WM, written by Sun, to
-                * workaround a Java GUI rendering issue.
-                */
-               if (java_workaround)
-                       xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
-                           ewmh[_NET_WM_NAME].atom, a_utf8_string,
-                           8, strlen("LG3D"), "LG3D");
-               else
-                       xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
-                           ewmh[_NET_WM_NAME].atom, a_utf8_string,
-                           8, strlen("spectrwm"), "spectrwm");
+               xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
+                   ewmh[_NET_WM_NAME].atom, a_utf8_string,
+                   8, strlen("spectrwm"), "spectrwm");
 
                /* Report supported atoms */
                xcb_delete_property(conn, root, a_net_supported);
@@ -1637,7 +1694,7 @@ ewmh_apply_flags(struct ws_win *win, uint32_t pending)
                        }
                }
 
-               update_window_color(win);
+               draw_frame(win);
                raise_window(win);
        }
 
@@ -1720,7 +1777,7 @@ ewmh_get_wm_state(struct ws_win *win)
 /* events */
 #ifdef SWM_DEBUG
 void
-dumpwins(struct swm_region *r, union arg *args)
+dumpwins(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win                           *w;
        uint32_t                                state;
@@ -1728,6 +1785,7 @@ dumpwins(struct swm_region *r, union arg *args)
        xcb_get_window_attributes_reply_t       *wa;
 
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)args;
 
        if (r->ws == NULL) {
@@ -1741,8 +1799,8 @@ dumpwins(struct swm_region *r, union arg *args)
                c = xcb_get_window_attributes(conn, w->id);
                wa = xcb_get_window_attributes_reply(conn, c, NULL);
                if (wa) {
-                       DPRINTF("win %#x, map_state: %d, state: %u, "
-                           "transient: %#x\n", w->id, wa->map_state,
+                       DPRINTF("win %#x (%#x), map_state: %d, state: %u, "
+                           "transient: %#x\n", w->frame, w->id, wa->map_state,
                            state, w->transient);
                        free(wa);
                } else
@@ -1752,8 +1810,8 @@ dumpwins(struct swm_region *r, union arg *args)
 
        DPRINTF("=== stacking order (top down) === \n");
        TAILQ_FOREACH(w, &r->ws->stack, stack_entry) {
-               DPRINTF("win %#x, fs: %s, maximized: %s, above: %s, "
-                   "iconic: %s\n", w->id, YESNO(FULLSCREEN(w)),
+               DPRINTF("win %#x (%#x), fs: %s, maximized: %s, above: %s, "
+                   "iconic: %s\n", w->frame, w->id, YESNO(FULLSCREEN(w)),
                    YESNO(MAXIMIZED(w)), YESNO(ABOVE(w)), YESNO(ICONIC(w)));
        }
 
@@ -1774,10 +1832,169 @@ dumpwins(struct swm_region *r, union arg *args)
 
        DPRINTF("=================================\n");
 }
+
+void
+debug_toggle(struct binding *b, struct swm_region *r, union arg *s)
+{
+       struct ws_win           *win;
+       int                     num_screens, i, j;
+
+       /* Suppress warnings. */
+       (void)b;
+       (void)r;
+       (void)s;
+
+       DNPRINTF(SWM_D_MISC, "debug_toggle\n");
+
+       debug_enabled = !debug_enabled;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               for (j = 0; j < workspace_limit; j++)
+                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
+                               debug_refresh(win);
+
+       xcb_flush(conn);
+}
+
+void
+debug_refresh(struct ws_win *win)
+{
+       struct ws_win           *w;
+       XftDraw                 *draw;
+       XGlyphInfo              info;
+       GC                      l_draw;
+       XGCValues               l_gcv;
+       XRectangle              l_ibox, l_lbox;
+       xcb_rectangle_t         rect;
+       size_t                  len;
+       uint32_t                wc[4], mask, width, height, gcv[1];
+       int                     widx, sidx;
+       char                    *s;
+       xcb_screen_t            *screen;
+
+       if (debug_enabled) {
+               /* Create debug window if it doesn't exist. */
+               if (win->debug == XCB_WINDOW_NONE) {
+                       if ((screen = get_screen(win->s->idx)) == NULL)
+                               errx(1, "ERROR: can't get screen %d.",
+                                   win->s->idx);
+
+                       win->debug = xcb_generate_id(conn);
+                       wc[0] = win->s->c[SWM_S_COLOR_BAR].pixel;
+                       wc[1] = win->s->c[SWM_S_COLOR_BAR_BORDER].pixel;
+                       wc[2] = screen->default_colormap;
+
+                       xcb_create_window(conn, screen->root_depth, win->debug,
+                           win->frame, 0, 0, 10, 10, 1,
+                           XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
+                           XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
+                           XCB_CW_COLORMAP, wc);
+
+                       xcb_map_window(conn, win->debug);
+               }
+
+               /* Determine workspace window list index. */
+               widx = 0;
+               TAILQ_FOREACH(w, &win->ws->winlist, entry) {
+                       ++widx;
+                       if (w == win)
+                               break;
+               }
+
+               /* Determine stacking index (top down). */
+               sidx = 0;
+               TAILQ_FOREACH(w, &win->ws->stack, stack_entry) {
+                       ++sidx;
+                       if (w == win)
+                               break;
+               }
+
+               if (asprintf(&s, "%#x f:%#x wl:%d s:%d im:%s", win->id,
+                   win->frame, widx, sidx, get_win_input_model(win)) == -1)
+                       return;
+
+               len = strlen(s);
+
+               /* Update window to an appropriate dimension. */
+               if (bar_font_legacy) {
+                       XmbTextExtents(bar_fs, s, len, &l_ibox, &l_lbox);
+                       width = l_lbox.width + 4;
+                       height = bar_fs_extents->max_logical_extent.height + 4;
+               } else {
+                       XftTextExtentsUtf8(display, bar_font, (FcChar8 *)s, len,
+                           &info);
+                       width = info.width + 4;
+                       height = bar_font->height + 4;
+               }
+
+               mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
+                   XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+               if (win->bordered)
+                       wc[0] = wc[1] = border_width;
+               else
+                       wc[0] = wc[1] = 0;
+
+               wc[2] = width;
+               wc[3] = height;
+
+               xcb_configure_window(conn, win->debug, mask, wc);
+
+               /* Draw a filled rectangle to 'clear' window. */
+               rect.x = 0;
+               rect.y = 0;
+               rect.width = width;
+               rect.height = height;
+
+               gcv[0] = win->s->c[SWM_S_COLOR_BAR].pixel;
+               xcb_change_gc(conn, win->s->bar_gc, XCB_GC_FOREGROUND, gcv);
+               xcb_poly_fill_rectangle(conn, win->debug, win->s->bar_gc, 1,
+                   &rect);
+
+               /* Draw text. */
+               if (bar_font_legacy) {
+                       l_gcv.graphics_exposures = 0;
+                       l_draw = XCreateGC(display, win->debug, 0, &l_gcv);
+
+                       XSetForeground(display, l_draw,
+                               win->s->c[SWM_S_COLOR_BAR_FONT].pixel);
+
+                       DRAWSTRING(display, win->debug, bar_fs, l_draw, 2,
+                           (bar_fs_extents->max_logical_extent.height -
+                           l_lbox.height) / 2 - l_lbox.y, s, len);
+
+                       XFreeGC(display, l_draw);
+               } else {
+                       draw = XftDrawCreate(display, win->debug,
+                           DefaultVisual(display, win->s->idx),
+                           DefaultColormap(display, win->s->idx));
+
+                       XftDrawStringUtf8(draw, &bar_font_color, bar_font, 2,
+                           (bar_height + bar_font->height) / 2 -
+                           bar_font->descent, (FcChar8 *)s, len);
+
+                       XftDrawDestroy(draw);
+               }
+
+               free(s);
+       } else if (win->debug != XCB_WINDOW_NONE) {
+                       xcb_destroy_window(conn, win->debug);
+                       win->debug = XCB_WINDOW_NONE;
+       }
+}
 #else
 void
-dumpwins(struct swm_region *r, union arg *s)
+dumpwins(struct binding *b, struct swm_region *r, union arg *s)
+{
+       (void)b;
+       (void)r;
+       (void)s;
+}
+
+void
+debug_toggle(struct binding *b, struct swm_region *r, union arg *s)
 {
+       (void)b;
        (void)r;
        (void)s;
 }
@@ -1900,24 +2117,13 @@ name_to_pixel(int sidx, const char *colorname)
 void
 setscreencolor(const char *val, int i, int c)
 {
-       int     num_screens;
+       if (i < 0 || i >= get_screen_count())
+               return;
 
-       num_screens = get_screen_count();
-       if (i > 0 && i <= num_screens) {
-               screens[i - 1].c[c].pixel = name_to_pixel(i - 1, val);
-               free(screens[i - 1].c[c].name);
-               if ((screens[i - 1].c[c].name = strdup(val)) == NULL)
-                       err(1, "strdup");
-       } else if (i == -1) {
-               for (i = 0; i < num_screens; i++) {
-                       screens[i].c[c].pixel = name_to_pixel(0, val);
-                       free(screens[i].c[c].name);
-                       if ((screens[i].c[c].name = strdup(val)) == NULL)
-                               err(1, "strdup");
-               }
-       } else
-               errx(1, "invalid screen index: %d out of bounds (maximum %d)",
-                   i, num_screens);
+       screens[i].c[c].pixel = name_to_pixel(i, val);
+       free(screens[i].c[c].name);
+       if ((screens[i].c[c].name = strdup(val)) == NULL)
+               err(1, "strdup");
 }
 
 void
@@ -1953,6 +2159,8 @@ custom_region(const char *val)
        int                             sidx, num_screens;
        xcb_screen_t                    *screen;
 
+       DNPRINTF(SWM_D_CONF, "custom_region: %s\n", val);
+
        num_screens = get_screen_count();
        if (sscanf(val, "screen[%d]:%ux%u+%u+%u", &sidx, &w, &h, &x, &y) != 5)
                errx(1, "invalid custom region, "
@@ -2113,7 +2321,7 @@ bar_extra_stop(void)
                bar_pid = 0;
        }
        strlcpy(bar_ext, "", sizeof bar_ext);
-       bar_extra = 0;
+       bar_extra = false;
 }
 
 void
@@ -2169,38 +2377,50 @@ bar_window_name(char *s, size_t sz, struct swm_region *r)
        free(title);
 }
 
+bool
+get_urgent(struct ws_win *win)
+{
+       xcb_icccm_wm_hints_t            hints;
+       xcb_get_property_cookie_t       c;
+       bool                            urgent = false;
+
+       if (win) {
+               c = xcb_icccm_get_wm_hints(conn, win->id);
+               if (xcb_icccm_get_wm_hints_reply(conn, c, &hints, NULL))
+                       urgent = xcb_icccm_wm_hints_get_urgency(&hints);
+       }
+
+       return urgent;
+}
+
 void
 bar_urgent(char *s, size_t sz)
 {
        struct ws_win           *win;
        int                     i, j, num_screens;
-       int                     urgent[SWM_WS_MAX];
+       bool                    urgent[SWM_WS_MAX];
        char                    b[8];
-       xcb_get_property_cookie_t       c;
-       xcb_icccm_wm_hints_t    hints;
 
        for (i = 0; i < workspace_limit; i++)
-               urgent[i] = 0;
+               urgent[i] = false;
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
-                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry) {
-                               c = xcb_icccm_get_wm_hints(conn, win->id);
-                               if (xcb_icccm_get_wm_hints_reply(conn, c,
-                                   &hints, NULL) == 0)
-                                       continue;
-                               if (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY)
-                                       urgent[j] = 1;
-                       }
+                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
+                               if (get_urgent(win))
+                                       urgent[j] = true;
 
        for (i = 0; i < workspace_limit; i++) {
-               if (urgent[i])
+               if (urgent[i]) {
                        snprintf(b, sizeof b, "%d ", i + 1);
-               else
-                       snprintf(b, sizeof b, "- ");
-               strlcat(s, b, sz);
+                       strlcat(s, b, sz);
+               } else if (!urgent_collapse) {
+                       strlcat(s, "- ", sz);
+               }
        }
+       if (urgent_collapse && s[0])
+               s[strlen(s) - 1] = 0;
 }
 
 void
@@ -2253,7 +2473,7 @@ bar_fmt(const char *fmtexp, char *fmtnew, struct swm_region *r, size_t sz)
 
        /* bar_urgent already adds the space before the last asterisk */
        if (urgent_enabled)
-               strlcat(fmtnew, "* +U*+4<", sz);
+               strlcat(fmtnew, (urgent_collapse ? "*+U*+4<" : "* +U*+4<"), sz);
 
        if (window_class_enabled) {
                strlcat(fmtnew, "+C", sz);
@@ -2440,46 +2660,43 @@ bar_fmt_expand(char *fmtexp, size_t sz)
 #endif
 }
 
-/* Redraws the bar; need to follow with xcb_flush() or focus_flush(). */
+/* Redraws a region bar; need to follow with xcb_flush() or focus_flush(). */
 void
-bar_draw(void)
+bar_draw(struct swm_bar *bar)
 {
+       struct swm_region       *r;
        char                    fmtexp[SWM_BAR_MAX], fmtnew[SWM_BAR_MAX];
        char                    fmtrep[SWM_BAR_MAX];
-       int                     i, num_screens;
-       struct swm_region       *r;
 
        /* expand the format by first passing it through strftime(3) */
        bar_fmt_expand(fmtexp, sizeof fmtexp);
 
-       num_screens = get_screen_count();
-       for (i = 0; i < num_screens; i++) {
-               TAILQ_FOREACH(r, &screens[i].rl, entry) {
-                       if (r->bar == NULL)
-                               continue;
+       if (bar == NULL)
+               return;
 
-                       if (bar_enabled && r->ws->bar_enabled)
-                               xcb_map_window(conn, r->bar->id);
-                       else {
-                               xcb_unmap_window(conn, r->bar->id);
-                               continue;
-                       }
+       r = bar->r;
 
-                       if (startup_exception)
-                               snprintf(fmtrep, sizeof fmtrep, "total "
-                                   "exceptions: %d, first exception: %s",
-                                   nr_exceptions,
-                                   startup_exception);
-                       else {
-                               bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew);
-                               bar_replace(fmtnew, fmtrep, r, sizeof fmtrep);
-                       }
-                       if (bar_font_legacy)
-                               bar_print_legacy(r, fmtrep);
-                       else
-                               bar_print(r, fmtrep);
-               }
+       if (bar_enabled && r->ws->bar_enabled)
+               xcb_map_window(conn, bar->id);
+       else {
+               xcb_unmap_window(conn, bar->id);
+               return;
+       }
+
+       if (startup_exception)
+               snprintf(fmtrep, sizeof fmtrep, "total "
+                   "exceptions: %d, first exception: %s",
+                   nr_exceptions,
+                   startup_exception);
+       else {
+               bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew);
+               bar_replace(fmtnew, fmtrep, r, sizeof fmtrep);
        }
+
+       if (bar_font_legacy)
+               bar_print_legacy(r, fmtrep);
+       else
+               bar_print(r, fmtrep);
 }
 
 /*
@@ -2491,7 +2708,7 @@ bar_extra_update(void)
 {
        size_t          len;
        char            b[SWM_BAR_MAX];
-       int             changed = 0;
+       bool            changed = false;
 
        if (!bar_extra)
                return changed;
@@ -2513,7 +2730,7 @@ bar_extra_update(void)
                                /* Append new output to bar. */
                                strlcat(bar_ext, b, sizeof(bar_ext));
 
-                               changed = 1;
+                               changed = true;
                        } else {
                                /* Buffer output. */
                                strlcat(bar_ext_buf, b, sizeof(bar_ext_buf));
@@ -2524,19 +2741,20 @@ bar_extra_update(void)
        if (errno != EAGAIN) {
                warn("bar_action failed");
                bar_extra_stop();
-               changed = 1;
+               changed = true;
        }
 
        return changed;
 }
 
 void
-bar_toggle(struct swm_region *r, union arg *args)
+bar_toggle(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct swm_region       *tmpr;
        int                     i, num_screens;
 
        /* suppress unused warnings since vars are needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -2548,7 +2766,7 @@ bar_toggle(struct swm_region *r, union arg *args)
                if (bar_enabled)
                        r->ws->bar_enabled = !r->ws->bar_enabled;
                else
-                       bar_enabled = r->ws->bar_enabled = 1;
+                       bar_enabled = r->ws->bar_enabled = true;
                break;
        case SWM_ARG_ID_BAR_TOGGLE:
                bar_enabled = !bar_enabled;
@@ -2566,10 +2784,13 @@ bar_toggle(struct swm_region *r, union arg *args)
                                        xcb_unmap_window(conn, tmpr->bar->id);
                        }
 
-       stack();
-
-       /* must be after stack */
-       bar_draw();
+       /* Restack all regions and redraw bar. */
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(tmpr, &screens[i].rl, entry) {
+                       stack(tmpr);
+                       bar_draw(tmpr->bar);
+               }
 
        focus_flush();
 }
@@ -2580,7 +2801,7 @@ bar_extra_setup(void)
        /* do this here because the conf file is in memory */
        if (!bar_extra && bar_argv[0]) {
                /* launch external status app */
-               bar_extra = 1;
+               bar_extra = true;
                if (pipe(bar_pipe) == -1)
                        err(1, "pipe error");
                socket_setnonblock(bar_pipe[0]);
@@ -2621,7 +2842,7 @@ kill_bar_extra_atexit(void)
                kill(bar_pid, SIGTERM);
 }
 
-int
+bool
 isxlfd(char *s)
 {
        int      count = 0;
@@ -2647,7 +2868,8 @@ fontset_init(void)
                bar_fs = NULL;
        }
 
-       DNPRINTF(SWM_D_INIT, "fontset_init: loading bar_fonts: %s\n", bar_fonts);
+       DNPRINTF(SWM_D_INIT, "fontset_init: loading bar_fonts: %s\n",
+           bar_fonts);
 
        bar_fs = XCreateFontSet(display, bar_fonts, &missing_charsets,
            &num_missing_charsets, &default_string);
@@ -2682,13 +2904,13 @@ fontset_init(void)
 void
 xft_init(struct swm_region *r)
 {
-       char                    *font, *d, *search;
+       char                    *font, *str, *search;
        XRenderColor            color;
 
        if (bar_font == NULL) {
-               if ((d = strdup(bar_fonts)) == NULL)
+               if ((search = str = strdup(bar_fonts)) == NULL)
                        errx(1, "insufficient memory.");
-               search = d;
+
                while ((font = strsep(&search, ",")) != NULL) {
                        if (*font == '\0')
                                continue;
@@ -2712,7 +2934,7 @@ xft_init(struct swm_region *r)
                                break;
                        }
                }
-               free(d);
+               free(str);
        }
 
        if (bar_font == NULL)
@@ -2724,6 +2946,12 @@ xft_init(struct swm_region *r)
            DefaultColormap(display, r->s->idx), &color, &bar_font_color))
                warn("Xft error: unable to allocate color.");
 
+       PIXEL_TO_XRENDERCOLOR(r->s->c[SWM_S_COLOR_BAR].pixel, color);
+
+       if (!XftColorAllocValue(display, DefaultVisual(display, r->s->idx),
+           DefaultColormap(display, r->s->idx), &color, &search_font_color))
+               warn("Xft error: unable to allocate color.");
+
        bar_height = bar_font->height + 2 * bar_border_width;
 
        if (bar_height < 1)
@@ -2753,6 +2981,7 @@ bar_setup(struct swm_region *r)
        else
                xft_init(r);
 
+       r->bar->r = r;
        X(r->bar) = X(r);
        Y(r->bar) = bar_at_bottom ? (Y(r) + HEIGHT(r) - bar_height) : Y(r);
        WIDTH(r->bar) = WIDTH(r) - 2 * bar_border_width;
@@ -2771,11 +3000,18 @@ bar_setup(struct swm_region *r)
            XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL
            | XCB_CW_EVENT_MASK, wa);
 
+       /* Stack bar window above region window to start. */
+       wa[0] = r->id;
+       wa[1] = XCB_STACK_MODE_ABOVE;
+
+       xcb_configure_window(conn, r->bar->id, XCB_CONFIG_WINDOW_SIBLING |
+           XCB_CONFIG_WINDOW_STACK_MODE, wa);
+
        r->bar->buffer = xcb_generate_id(conn);
        xcb_create_pixmap(conn, screen->root_depth, r->bar->buffer, r->bar->id,
            WIDTH(r->bar), HEIGHT(r->bar));
 
-       if (xrandr_support)
+       if (randr_support)
                xcb_randr_select_input(conn, r->bar->id,
                    XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE);
 
@@ -2806,7 +3042,7 @@ set_win_state(struct ws_win *win, uint8_t state)
        uint16_t                data[2] = { state, XCB_ATOM_NONE };
 
        DNPRINTF(SWM_D_EVENT, "set_win_state: win %#x, state: %u\n",
-           win->id, state);
+           WINID(win), state);
 
        if (win == NULL)
                return;
@@ -2836,9 +3072,13 @@ get_win_state(xcb_window_t w)
 }
 
 void
-version(struct swm_region *r, union arg *args)
+version(struct binding *bp, struct swm_region *r, union arg *args)
 {
+       struct swm_region       *tmpr;
+       int                     i, num_screens;
+
        /* suppress unused warnings since vars are needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -2849,8 +3089,13 @@ version(struct swm_region *r, union arg *args)
        else
                strlcpy(bar_vertext, "", sizeof bar_vertext);
 
-       bar_draw();
-       xcb_flush(conn);
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++) {
+               TAILQ_FOREACH(tmpr, &screens[i].rl, entry) {
+                       bar_draw(tmpr->bar);
+                       xcb_flush(conn);
+               }
+       }
 }
 
 void
@@ -2899,13 +3144,13 @@ config_win(struct ws_win *win, xcb_configure_request_event_t *ev)
        ce.y = Y(win);
        ce.width = WIDTH(win);
        ce.height = HEIGHT(win);
+       ce.border_width = 0;
        ce.override_redirect = 0;
 
        if (ev == NULL) {
                /* EWMH */
                ce.event = win->id;
                ce.window = win->id;
-               ce.border_width = BORDER(win);
                ce.above_sibling = XCB_WINDOW_NONE;
        } else {
                /* normal */
@@ -2950,9 +3195,9 @@ config_win(struct ws_win *win, xcb_configure_request_event_t *ev)
                }
 
                /* adjust x and y for requested border_width. */
-               ce.x += BORDER(win) - ev->border_width;
-               ce.y += BORDER(win) - ev->border_width;
-               ce.border_width = ev->border_width;
+               ce.x += ev->border_width;
+               ce.y += ev->border_width;
+
                ce.above_sibling = ev->sibling;
        }
 
@@ -2965,7 +3210,7 @@ config_win(struct ws_win *win, xcb_configure_request_event_t *ev)
 }
 
 int
-count_win(struct workspace *ws, int count_transient)
+count_win(struct workspace *ws, bool count_transient)
 {
        struct ws_win           *win;
        int                     count = 0;
@@ -2983,9 +3228,10 @@ count_win(struct workspace *ws, int count_transient)
 }
 
 void
-quit(struct swm_region *r, union arg *args)
+quit(struct binding *bp, struct swm_region *r, union arg *args)
 {
        /* suppress unused warnings since vars are needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -2994,51 +3240,56 @@ quit(struct swm_region *r, union arg *args)
 }
 
 void
-raise_window(struct ws_win *win)
+lower_window(struct ws_win *win)
 {
        struct ws_win           *target = NULL;
-       struct swm_region       *r;
        struct workspace        *ws;
 
-       if (win == NULL || (r = win->ws->r) == NULL)
+       DNPRINTF(SWM_D_EVENT, "lower_window: win %#x\n", WINID(win));
+
+       if (win == NULL)
                return;
-       ws = win->ws;
 
-       DNPRINTF(SWM_D_EVENT, "raise_window: win %#x\n", win->id);
+       ws = win->ws;
 
        TAILQ_FOREACH(target, &ws->stack, stack_entry) {
                if (target == win || ICONIC(target))
                        continue;
                if (ws->cur_layout == &layouts[SWM_MAX_STACK])
                        break;
-               if (TRANS(win) && (win->transient == target->transient ||
-                   win->transient == target->id))
-                       break;
+               if (TRANS(win)) {
+                       if (win->transient == target->transient)
+                               continue;
+                       if (win->transient == target->id)
+                               break;
+               }
+               if (FULLSCREEN(target))
+                       continue;
                if (FULLSCREEN(win))
                        break;
-               if (FULLSCREEN(target))
+               if (MAXIMIZED(target))
                        continue;
                if (MAXIMIZED(win))
                        break;
-               if (MAXIMIZED(target))
+               if (ABOVE(target) || TRANS(target))
                        continue;
                if (ABOVE(win) || TRANS(win))
                        break;
-               if (!ABOVE(target) && !TRANS(target))
-                       break;
        }
 
-       if (target != NULL) {
-               /* Change stack position. */
-               TAILQ_REMOVE(&ws->stack, win, stack_entry);
+       /* Change stack position. */
+       TAILQ_REMOVE(&ws->stack, win, stack_entry);
+       if (target)
                TAILQ_INSERT_BEFORE(target, win, stack_entry);
-               update_win_stacking(win);
-       }
+       else
+               TAILQ_INSERT_TAIL(&ws->stack, win, stack_entry);
+
+       update_win_stacking(win);
 
 #ifdef SWM_DEBUG
        if (swm_debug & SWM_D_STACK) {
                DPRINTF("=== stacking order (top down) === \n");
-               TAILQ_FOREACH(target, &r->ws->stack, stack_entry) {
+               TAILQ_FOREACH(target, &ws->stack, stack_entry) {
                        DPRINTF("win %#x, fs: %s, maximized: %s, above: %s, "
                            "iconic: %s\n", target->id, YESNO(FULLSCREEN(target)),
                            YESNO(MAXIMIZED(target)), YESNO(ABOVE(target)),
@@ -3046,50 +3297,127 @@ raise_window(struct ws_win *win)
                }
        }
 #endif
-       DNPRINTF(SWM_D_EVENT, "raise_window: done\n");
+       DNPRINTF(SWM_D_EVENT, "lower_window: done\n");
 }
 
 void
-update_win_stacking(struct ws_win *win)
+raise_window(struct ws_win *win)
 {
-       struct ws_win           *sibling;
-       struct swm_region       *r;
-       uint32_t                val[2];
-
-       if (win == NULL || (r = win->ws->r) == NULL)
-               return;
-
-       sibling = TAILQ_NEXT(win, stack_entry);
-       if (sibling != NULL && FLOATING(win) == FLOATING(sibling))
-               val[0] = sibling->id;
-       else
-               val[0] = FLOATING(win) ? r->bar->id : r->id;
-
-       DNPRINTF(SWM_D_EVENT, "update_win_stacking: %#x, sibling %#x\n",
-           win->id, val[0]);
-
-       val[1] = XCB_STACK_MODE_ABOVE;
+       struct ws_win           *target = NULL;
+       struct workspace        *ws;
 
-       xcb_configure_window(conn, win->id, XCB_CONFIG_WINDOW_SIBLING |
-           XCB_CONFIG_WINDOW_STACK_MODE, val);
-}
+       DNPRINTF(SWM_D_EVENT, "raise_window: win %#x\n", WINID(win));
 
-void
-map_window(struct ws_win *win)
-{
        if (win == NULL)
                return;
 
-       DNPRINTF(SWM_D_EVENT, "map_window: win %#x, mapped: %s\n",
-           win->id, YESNO(win->mapped));
-
-       if (win->mapped)
-               return;
+       ws = win->ws;
 
-       xcb_map_window(conn, win->id);
-       set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL);
-       win->mapped = 1;
-}
+       TAILQ_FOREACH(target, &ws->stack, stack_entry) {
+               if (target == win || ICONIC(target))
+                       continue;
+               if (ws->cur_layout == &layouts[SWM_MAX_STACK])
+                       break;
+               if (TRANS(win) && (win->transient == target->transient ||
+                   win->transient == target->id))
+                       break;
+               if (FULLSCREEN(win))
+                       break;
+               if (FULLSCREEN(target))
+                       continue;
+               if (MAXIMIZED(win))
+                       break;
+               if (MAXIMIZED(target))
+                       continue;
+               if (ABOVE(win) || TRANS(win) ||
+                   (win->ws->focus == win && ws->always_raise))
+                       break;
+               if (!ABOVE(target) && !TRANS(target))
+                       break;
+       }
+
+       TAILQ_REMOVE(&ws->stack, win, stack_entry);
+       if (target)
+               TAILQ_INSERT_BEFORE(target, win, stack_entry);
+       else
+               TAILQ_INSERT_TAIL(&ws->stack, win, stack_entry);
+
+       update_win_stacking(win);
+
+#ifdef SWM_DEBUG
+       if (swm_debug & SWM_D_STACK) {
+               DPRINTF("=== stacking order (top down) === \n");
+               TAILQ_FOREACH(target, &ws->stack, stack_entry) {
+                       DPRINTF("win %#x, fs: %s, maximized: %s, above: %s, "
+                           "iconic: %s\n", target->id, YESNO(FULLSCREEN(target)),
+                           YESNO(MAXIMIZED(target)), YESNO(ABOVE(target)),
+                           YESNO(ICONIC(target)));
+               }
+       }
+#endif
+       DNPRINTF(SWM_D_EVENT, "raise_window: done\n");
+}
+
+void
+update_win_stacking(struct ws_win *win)
+{
+       struct ws_win           *sibling;
+#ifdef SWM_DEBUG
+       struct ws_win           *w;
+#endif
+       struct swm_region       *r;
+       uint32_t                val[2];
+
+       if (win == NULL || (r = win->ws->r) == NULL)
+               return;
+
+       if (win->frame == XCB_WINDOW_NONE) {
+               DNPRINTF(SWM_D_EVENT, "update_window_stacking: win %#x not "
+                   "reparented.\n", win->id);
+               return;
+       }
+
+       sibling = TAILQ_NEXT(win, stack_entry);
+       if (sibling != NULL && (FLOATING(win) == FLOATING(sibling) ||
+           (win->ws->always_raise && win->ws->focus == win)))
+               val[0] = sibling->frame;
+       else if (FLOATING(win) || (win->ws->always_raise &&
+           win->ws->focus == win))
+               val[0] = r->bar->id;
+       else
+               val[0] = r->id;
+
+       DNPRINTF(SWM_D_EVENT, "update_win_stacking: win %#x (%#x), "
+           "sibling %#x\n", win->frame, win->id, val[0]);
+
+       val[1] = XCB_STACK_MODE_ABOVE;
+
+       xcb_configure_window(conn, win->frame, XCB_CONFIG_WINDOW_SIBLING |
+           XCB_CONFIG_WINDOW_STACK_MODE, val);
+
+#ifdef SWM_DEBUG
+       TAILQ_FOREACH(w, &win->ws->winlist, entry)
+               debug_refresh(w);
+#endif
+}
+
+void
+map_window(struct ws_win *win)
+{
+       if (win == NULL)
+               return;
+
+       DNPRINTF(SWM_D_EVENT, "map_window: win %#x, mapped: %s\n",
+           win->id, YESNO(win->mapped));
+
+       if (win->mapped)
+               return;
+
+       xcb_map_window(conn, win->frame);
+       xcb_map_window(conn, win->id);
+       set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL);
+       win->mapped = true;
+}
 
 void
 unmap_window(struct ws_win *win)
@@ -3104,8 +3432,9 @@ unmap_window(struct ws_win *win)
                return;
 
        xcb_unmap_window(conn, win->id);
+       xcb_unmap_window(conn, win->frame);
        set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC);
-       win->mapped = 0;
+       win->mapped = false;
 }
 
 void
@@ -3160,9 +3489,10 @@ fake_keypress(struct ws_win *win, xcb_keysym_t keysym, uint16_t modifiers)
 }
 
 void
-restart(struct swm_region *r, union arg *args)
+restart(struct binding *bp, struct swm_region *r, union arg *args)
 {
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -3172,7 +3502,7 @@ restart(struct swm_region *r, union arg *args)
 
        execvp(start_argv[0], start_argv);
        warn("execvp failed");
-       quit(NULL, NULL);
+       quit(NULL, NULL, NULL);
 }
 
 struct ws_win *
@@ -3198,6 +3528,26 @@ get_pointer_win(xcb_window_t root)
        return win;
 }
 
+void
+center_pointer(struct swm_region *r)
+{
+       struct ws_win                   *win;
+
+       if (!warp_pointer || r == NULL)
+               return;
+
+       win = r->ws->focus;
+
+       DNPRINTF(SWM_D_EVENT, "center_pointer: win %#x.\n", WINID(win));
+
+       if (win && win->mapped)
+               xcb_warp_pointer(conn, XCB_NONE, win->frame, 0, 0, 0, 0,
+                   WIDTH(win) / 2, HEIGHT(win) / 2);
+       else
+               xcb_warp_pointer(conn, XCB_NONE, r->id, 0, 0, 0, 0,
+                   WIDTH(r) / 2, HEIGHT(r) / 2);
+}
+
 struct swm_region *
 root_to_region(xcb_window_t root, int check)
 {
@@ -3256,61 +3606,105 @@ root_to_region(xcb_window_t root, int check)
        return (r);
 }
 
-struct ws_win *
-find_unmanaged_window(xcb_window_t id)
+struct swm_region *
+find_region(xcb_window_t id)
 {
-       struct ws_win           *win;
-       int                     i, j, num_screens;
+       struct swm_region       *r;
+       int                     i, num_screens;
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
-               for (j = 0; j < workspace_limit; j++)
-                       TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
-                           entry)
-                               if (id == win->id)
-                                       return (win);
-       return (NULL);
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       if (r->id == id)
+                               return r;
+
+       return NULL;
+}
+
+struct swm_bar *
+find_bar(xcb_window_t id)
+{
+       struct swm_region       *r;
+       int                     i, num_screens;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       if (r->bar && r->bar->id == id)
+                               return r->bar;
+
+       return NULL;
+}
+
+struct ws_win *
+find_frame_window(xcb_window_t id) {
+       struct swm_region       *r;
+       struct ws_win           *w;
+       int                     i, num_screens;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       TAILQ_FOREACH(w, &r->ws->winlist, entry)
+                               if (w->frame == id)
+                                       return w;
+
+       return NULL;
 }
 
 struct ws_win *
 find_window(xcb_window_t id)
 {
-       struct ws_win           *win;
+       struct ws_win           *win = NULL;
        int                     i, j, num_screens;
-       xcb_query_tree_reply_t  *r;
+       xcb_query_tree_reply_t  *qtr;
+
+       DNPRINTF(SWM_D_MISC, "find_window: id: %#x\n", id);
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
-                               if (id == win->id)
+                               if (id == win->id || id == win->frame)
                                        return (win);
 
-       r = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
-       if (r == NULL)
-               return (NULL);
 
-       /* if we were looking for the parent return that window instead */
-       if (r->parent == 0 || r->root == r->parent) {
-               free(r);
-               return (NULL);
+       /* If window isn't top-level, try to find managed ancestor. */
+       qtr = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
+       if (qtr) {
+               if (qtr->parent != XCB_WINDOW_NONE && qtr->parent != qtr->root)
+                       win = find_window(qtr->parent);
+
+#ifdef SWM_DEBUG
+               if (win)
+                       DNPRINTF(SWM_D_MISC, "find_window: found child %#x "
+                           "of %#x.\n", win->id, qtr->parent);
+#endif
+
+               free(qtr);
        }
 
-       /* look for parent */
+       return (win);
+}
+
+struct ws_win *
+find_unmanaged_window(xcb_window_t id)
+{
+       struct ws_win           *win;
+       int                     i, j, num_screens;
+
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
-                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
-                               if (r->parent == win->id) {
-                                       free(r);
+                       TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
+                           entry)
+                               if (id == win->id)
                                        return (win);
-                               }
-
-       free(r);
        return (NULL);
 }
 
 void
-spawn(int ws_idx, union arg *args, int close_fd)
+spawn(int ws_idx, union arg *args, bool close_fd)
 {
        int                     fd;
        char                    *ret = NULL;
@@ -3319,7 +3713,16 @@ spawn(int ws_idx, union arg *args, int close_fd)
 
        close(xcb_get_file_descriptor(conn));
 
-       setenv("LD_PRELOAD", SWM_LIB, 1);
+       if ((ret = getenv("LD_PRELOAD"))) {
+               if (asprintf(&ret, "%s:%s", SWM_LIB, ret) == -1) {
+                       warn("spawn: asprintf LD_PRELOAD");
+                       _exit(1);
+               }
+               setenv("LD_PRELOAD", ret, 1);
+               free(ret);
+       } else {
+               setenv("LD_PRELOAD", SWM_LIB, 1);
+       }
 
        if (asprintf(&ret, "%d", ws_idx) == -1) {
                warn("spawn: asprintf SWM_WS");
@@ -3367,23 +3770,33 @@ spawn(int ws_idx, union arg *args, int close_fd)
 void
 kill_refs(struct ws_win *win)
 {
-       int                     i, x, num_screens;
-       struct swm_region       *r;
        struct workspace        *ws;
+       struct ws_win           *w;
+       int                     i, j, num_screens;
 
        if (win == NULL)
                return;
 
        num_screens = get_screen_count();
-       for (i = 0; i < num_screens; i++)
-               TAILQ_FOREACH(r, &screens[i].rl, entry)
-                       for (x = 0; x < workspace_limit; x++) {
-                               ws = &r->s->ws[x];
-                               if (win == ws->focus)
-                                       ws->focus = NULL;
-                               if (win == ws->focus_prev)
-                                       ws->focus_prev = NULL;
-                       }
+       for (i = 0; i < num_screens; i++) {
+               for (j = 0; j < workspace_limit; j++) {
+                       ws = &screens[i].ws[j];
+
+                       if (win == ws->focus)
+                               ws->focus = NULL;
+                       if (win == ws->focus_prev)
+                               ws->focus_prev = NULL;
+                       if (win == ws->focus_pending)
+                               ws->focus_pending = NULL;
+                       if (win == ws->focus_raise)
+                               ws->focus_raise = NULL;
+
+                       if (TRANS(win))
+                               TAILQ_FOREACH(w, &ws->winlist, entry)
+                                       if (win == w->focus_child)
+                                               w->focus_child = NULL;
+               }
+       }
 }
 
 int
@@ -3462,6 +3875,9 @@ unfocus_win(struct ws_win *win)
        if (win->ws->focus == win) {
                win->ws->focus = NULL;
                win->ws->focus_prev = win;
+               if (win->ws->focus_raise == win && !FLOATING(win)) {
+                       update_win_stacking(win);
+               }
        }
 
        if (validate_win(win->ws->focus)) {
@@ -3474,7 +3890,20 @@ unfocus_win(struct ws_win *win)
                win->ws->focus_prev = NULL;
        }
 
-       update_window_color(win);
+       draw_frame(win);
+
+       /* Raise window to "top unfocused position." */
+       if (win->ws->always_raise)
+               raise_window(win);
+
+       /* Update border width */
+       if (win->bordered && (win->quirks & SWM_Q_MINIMALBORDER) &&
+           FLOATING(win)) {
+               win->bordered = 0;
+               X(win) += border_width;
+               Y(win) += border_width;
+               update_window(win);
+       }
 
        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
            ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1, &none);
@@ -3485,9 +3914,10 @@ unfocus_win(struct ws_win *win)
 void
 focus_win(struct ws_win *win)
 {
-       struct ws_win                   *cfw = NULL, *parent = NULL, *w;
+       struct ws_win                   *cfw = NULL, *parent = NULL, *w, *tmpw;
        struct workspace                *ws;
-       xcb_get_input_focus_reply_t     *gifr;
+       xcb_get_input_focus_reply_t     *gifr = NULL;
+       xcb_get_window_attributes_reply_t       *war = NULL;
 
        DNPRINTF(SWM_D_FOCUS, "focus_win: win %#x\n", WINID(win));
 
@@ -3506,20 +3936,28 @@ focus_win(struct ws_win *win)
 
        gifr = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
        if (gifr) {
+               DNPRINTF(SWM_D_FOCUS, "focus_win: cur focus: %#x\n",
+                   gifr->focus);
+
                cfw = find_window(gifr->focus);
-               if (cfw != NULL && cfw != win) {
-                       if (cfw->ws != ws && cfw->ws->r != NULL) {
-                               /* Change border to unfocused color. */
-                               xcb_change_window_attributes(conn, cfw->id,
-                                   XCB_CW_BORDER_PIXEL,
-                                   &cfw->s->c[(MAXIMIZED(cfw) ?
-                                   SWM_S_COLOR_UNFOCUS_MAXIMIZED :
-                                   SWM_S_COLOR_UNFOCUS)].pixel);
-                       } else {
-                               unfocus_win(cfw);
+               if (cfw) {
+                       if (cfw != win) {
+                               if (cfw->ws != ws && cfw->ws->r != NULL &&
+                                   cfw->frame != XCB_WINDOW_NONE) {
+                                       draw_frame(cfw);
+                               } else {
+                                       unfocus_win(cfw);
+                               }
+                       }
+               } else {
+                       war = xcb_get_window_attributes_reply(conn,
+                           xcb_get_window_attributes(conn, gifr->focus), NULL);
+                       if (war && war->override_redirect && ws->focus == win) {
+                               DNPRINTF(SWM_D_FOCUS, "focus_win: skip refocus "
+                                   "from override_redirect.\n");
+                               goto out;
                        }
                }
-               free(gifr);
        }
 
        if (ws->focus != win) {
@@ -3528,9 +3966,8 @@ focus_win(struct ws_win *win)
                ws->focus = win;
        }
 
-       /* If this window directs focus to a child window, then clear. */
-       if (win->focus_child)
-               win->focus_child = NULL;
+       /* Clear focus child redirect. */
+       win->focus_child = NULL;
 
        /* If transient, adjust parent's focus child for focus_magic. */
        if (TRANS(win)) {
@@ -3539,16 +3976,28 @@ focus_win(struct ws_win *win)
                        parent->focus_child = win;
        }
 
-       if (cfw != win && ws->r != NULL) {
+       /* Update window border even if workspace is hidden. */
+       draw_frame(win);
+
+       if (cfw == win) {
+               DNPRINTF(SWM_D_FOCUS, "focus_win: already focused.\n");
+               goto out;
+       }
+
+       if (ws->r) {
                /* Set input focus if no input hint, or indicated by hint. */
-               if (!(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT) ||
-                   (win->hints.flags & XCB_ICCCM_WM_HINT_INPUT &&
-                    win->hints.input))
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
-                                       win->id, last_event_time);
-               else
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
+               if (ACCEPTS_FOCUS(win)) {
+                       DNPRINTF(SWM_D_FOCUS, "focus_win: set_input_focus: %#x,"
+                           " revert-to: parent, time: %#x\n", win->id,
+                           last_event_time);
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+                           win->id, last_event_time);
+               } else if (!win->take_focus) {
+                       DNPRINTF(SWM_D_FOCUS, "focus_win: set_input_focus: %#x,"
+                           " revert-to: parent, time: 0\n", ws->r->id);
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
                            ws->r->id, XCB_CURRENT_TIME);
+               }
 
                /* Tell app it can adjust focus to a specific window. */
                if (win->take_focus) {
@@ -3568,20 +4017,25 @@ focus_win(struct ws_win *win)
                                map_window(parent);
 
                                /* Map siblings next. */
-                               TAILQ_FOREACH(w, &ws->winlist, entry)
+                               TAILQ_FOREACH_SAFE(w, &ws->stack, stack_entry,
+                                   tmpw)
                                        if (w != win && !ICONIC(w) &&
-                                           win->transient == parent->id)
+                                           w->transient == parent->id) {
+                                               raise_window(w);
                                                map_window(w);
+                                       }
                        }
 
                        /* Map focused window. */
                        raise_window(win);
                        map_window(win);
 
-                       /* Finally, map children of focus window. */
-                       TAILQ_FOREACH(w, &ws->winlist, entry)
-                               if (w->transient == win->id && !ICONIC(w))
+                       /* Stack any children of focus window. */
+                       TAILQ_FOREACH_SAFE(w, &ws->stack, stack_entry, tmpw)
+                               if (w->transient == win->id && !ICONIC(w)) {
+                                       raise_window(w);
                                        map_window(w);
+                               }
                } else if (tile_gap < 0 && !ABOVE(win)) {
                        /*
                         * Windows overlap in the layout.
@@ -3593,16 +4047,16 @@ focus_win(struct ws_win *win)
 
                set_region(ws->r);
 
-               update_window_color(win);
-
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
                    ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1,
                    &win->id);
+
+               bar_draw(ws->r->bar);
        }
 
 out:
-       bar_draw();
-
+       free(gifr);
+       free(war);
        DNPRINTF(SWM_D_FOCUS, "focus_win: done.\n");
 }
 
@@ -3647,7 +4101,7 @@ event_drain(uint8_t rt)
 
        /* ensure all pending requests have been processed before filtering. */
        xcb_aux_sync(conn);
-       while ((evt = xcb_poll_for_event(conn))) {
+       while ((evt = get_next_event(false))) {
                if (XCB_EVENT_RESPONSE_TYPE(evt) != rt)
                        event_handle(evt);
 
@@ -3691,6 +4145,10 @@ set_region(struct swm_region *r)
 
        r->s->r_focus = r;
 
+       /* Update the focus window frame on the now unfocused region. */
+       if (rf && rf->ws->focus)
+               draw_frame(rf->ws->focus);
+
        ewmh_update_current_desktop();
 }
 
@@ -3711,25 +4169,29 @@ focus_region(struct swm_region *r)
                focus_win(nfw);
        } else {
                /* New region is empty; need to manually unfocus win. */
-               if (old_r)
+               if (old_r) {
                        unfocus_win(old_r->ws->focus);
+                       /* Clear bar since empty. */
+                       bar_draw(old_r->bar);
+               }
 
-               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->id,
+               DNPRINTF(SWM_D_FOCUS, "focus_region: set_input_focus: %#x, "
+                   "revert-to: parent, time: 0\n", r->id);
+               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, r->id,
                    XCB_CURRENT_TIME);
 
-               /* Clear bar since empty. */
-               bar_draw();
        }
 }
 
 void
-switchws(struct swm_region *r, union arg *args)
+switchws(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct swm_region       *this_r, *other_r;
        struct ws_win           *win;
        struct workspace        *new_ws, *old_ws;
        xcb_window_t            none = XCB_WINDOW_NONE;
-       int                     wsid = args->id, unmap_old = 0;
+       int                     wsid = args->id;
+       bool                    unmap_old = false;
 
        if (!(r && r->s))
                return;
@@ -3749,25 +4211,40 @@ switchws(struct swm_region *r, union arg *args)
        if (new_ws == old_ws)
                return;
 
+       other_r = new_ws->r;
+       if (other_r && workspace_clamp &&
+           bp->action != FN_RG_MOVE_NEXT && bp->action != FN_RG_MOVE_PREV) {
+               DNPRINTF(SWM_D_WS, "switchws: ws clamped.\n");
+
+               if (warp_focus) {
+                       DNPRINTF(SWM_D_WS, "switchws: warping focus to region "
+                           "with ws %d.\n", wsid);
+                       focus_region(other_r);
+                       center_pointer(other_r);
+                       focus_flush();
+               }
+               return;
+       }
+
        if ((win = old_ws->focus) != NULL) {
-               update_window_color(win);
+               draw_frame(win);
 
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
                    ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1,
                    &none);
        }
 
-       other_r = new_ws->r;
-       if (other_r == NULL) {
-               /* the other workspace is hidden, hide this one */
-               old_ws->r = NULL;
-               unmap_old = 1;
-       } else {
+       if (other_r) {
                /* the other ws is visible in another region, exchange them */
                other_r->ws_prior = new_ws;
                other_r->ws = old_ws;
                old_ws->r = other_r;
+       } else {
+               /* the other workspace is hidden, hide this one */
+               old_ws->r = NULL;
+               unmap_old = true;
        }
+
        this_r->ws_prior = old_ws;
        this_r->ws = new_ws;
        new_ws->r = this_r;
@@ -3781,7 +4258,9 @@ switchws(struct swm_region *r, union arg *args)
        }
 
        new_ws->state = SWM_WS_STATE_MAPPING;
-       stack();
+
+       stack(other_r);
+       stack(this_r);
 
        /* unmap old windows */
        if (unmap_old) {
@@ -3800,13 +4279,16 @@ switchws(struct swm_region *r, union arg *args)
 
        /* Clear bar and set focus on region input win if new ws is empty. */
        if (new_ws->focus_pending == NULL && new_ws->focus == NULL) {
-               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->id,
+               DNPRINTF(SWM_D_FOCUS, "switchws: set_input_focus: %#x, "
+                   "revert-to: parent, time: 0\n", r->id);
+               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, r->id,
                    XCB_CURRENT_TIME);
-               bar_draw();
+               bar_draw(r->bar);
        }
 
        ewmh_update_current_desktop();
 
+       center_pointer(r);
        focus_flush();
        new_ws->state = SWM_WS_STATE_MAPPED;
 
@@ -3814,12 +4296,11 @@ switchws(struct swm_region *r, union arg *args)
 }
 
 void
-cyclews(struct swm_region *r, union arg *args)
+cyclews(struct binding *bp, struct swm_region *r, union arg *args)
 {
        union                   arg a;
        struct swm_screen       *s = r->s;
-       int                     cycle_all = 0;
-       int                     mv = 0;
+       bool                    cycle_all = false, mv = false;
 
        DNPRINTF(SWM_D_WS, "cyclews: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
            args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
@@ -3829,19 +4310,19 @@ cyclews(struct swm_region *r, union arg *args)
        do {
                switch (args->id) {
                case SWM_ARG_ID_CYCLEWS_MOVE_UP:
-                       mv = 1;
+                       mv = true;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_UP_ALL:
-                       cycle_all = 1;
+                       cycle_all = true;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_UP:
                        a.id = (a.id < workspace_limit - 1) ? a.id + 1 : 0;
                        break;
                case SWM_ARG_ID_CYCLEWS_MOVE_DOWN:
-                       mv = 1;
+                       mv = true;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_DOWN_ALL:
-                       cycle_all = 1;
+                       cycle_all = true;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_DOWN:
                        a.id = (a.id > 0) ? a.id - 1 : workspace_limit - 1;
@@ -3857,16 +4338,16 @@ cyclews(struct swm_region *r, union arg *args)
                        continue;
 
                if (mv)
-                       send_to_ws(r, &a);
+                       send_to_ws(bp, r, &a);
 
-               switchws(r, &a);
+               switchws(bp, r, &a);
        } while (a.id != r->ws->idx);
 
        DNPRINTF(SWM_D_FOCUS, "cyclews: done\n");
 }
 
 void
-priorws(struct swm_region *r, union arg *args)
+priorws(struct binding *bp, struct swm_region *r, union arg *args)
 {
        union arg               a;
 
@@ -3879,16 +4360,18 @@ priorws(struct swm_region *r, union arg *args)
                return;
 
        a.id = r->ws_prior->idx;
-       switchws(r, &a);
+       switchws(bp, r, &a);
        DNPRINTF(SWM_D_FOCUS, "priorws: done\n");
 }
 
 void
-focusrg(struct swm_region *r, union arg *args)
+focusrg(struct binding *bp, struct swm_region *r, union arg *args)
 {
        int                     ridx = args->id, i, num_screens;
        struct swm_region       *rr = NULL;
 
+       (void)bp;
+
        num_screens = get_screen_count();
        /* do nothing if we don't have more than one screen */
        if (!(num_screens > 1 || outputs > 1))
@@ -3904,13 +4387,15 @@ focusrg(struct swm_region *r, union arg *args)
                return;
 
        focus_region(rr);
+       center_pointer(rr);
        focus_flush();
        DNPRINTF(SWM_D_FOCUS, "focusrg: done\n");
 }
 
 void
-cyclerg(struct swm_region *r, union arg *args)
+cyclerg(struct binding *bp, struct swm_region *r, union arg *args)
 {
+       union arg               a;
        struct swm_region       *rr = NULL;
        int                     i, num_screens;
 
@@ -3924,11 +4409,13 @@ cyclerg(struct swm_region *r, union arg *args)
 
        switch (args->id) {
        case SWM_ARG_ID_CYCLERG_UP:
+       case SWM_ARG_ID_CYCLERG_MOVE_UP:
                rr = TAILQ_NEXT(r, entry);
                if (rr == NULL)
                        rr = TAILQ_FIRST(&screens[i].rl);
                break;
        case SWM_ARG_ID_CYCLERG_DOWN:
+       case SWM_ARG_ID_CYCLERG_MOVE_DOWN:
                rr = TAILQ_PREV(r, swm_region_list, entry);
                if (rr == NULL)
                        rr = TAILQ_LAST(&screens[i].rl, swm_region_list);
@@ -3939,8 +4426,22 @@ cyclerg(struct swm_region *r, union arg *args)
        if (rr == NULL)
                return;
 
-       focus_region(rr);
-       focus_flush();
+       switch (args->id) {
+       case SWM_ARG_ID_CYCLERG_UP:
+       case SWM_ARG_ID_CYCLERG_DOWN:
+               focus_region(rr);
+               center_pointer(rr);
+               focus_flush();
+               break;
+       case SWM_ARG_ID_CYCLERG_MOVE_UP:
+       case SWM_ARG_ID_CYCLERG_MOVE_DOWN:
+               a.id = rr->ws->idx;
+               switchws(bp, r, &a);
+               break;
+       default:
+               return;
+       };
+
        DNPRINTF(SWM_D_FOCUS, "cyclerg: done\n");
 }
 
@@ -3968,12 +4469,14 @@ sort_windows(struct ws_win_list *wl)
 }
 
 void
-swapwin(struct swm_region *r, union arg *args)
+swapwin(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *target, *source;
        struct ws_win           *cur_focus;
        struct ws_win_list      *wl;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_WS, "swapwin: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
            args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
 
@@ -4072,7 +4575,8 @@ swapwin(struct swm_region *r, union arg *args)
        sort_windows(wl);
        ewmh_update_client_list();
 
-       stack();
+       stack(r);
+       center_pointer(r);
        focus_flush();
 out:
        DNPRINTF(SWM_D_MOVE, "swapwin: done\n");
@@ -4219,29 +4723,32 @@ get_region_focus(struct swm_region *r)
 }
 
 void
-focus(struct swm_region *r, union arg *args)
+focus(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *head, *cur_focus = NULL, *winfocus = NULL;
        struct ws_win_list      *wl = NULL;
        struct workspace        *ws = NULL;
        union arg               a;
        int                     i;
-       xcb_icccm_wm_hints_t    hints;
 
        if (!(r && r->ws))
                goto out;
 
-       DNPRINTF(SWM_D_FOCUS, "focus: id: %d\n", args->id);
-
        cur_focus = r->ws->focus;
        ws = r->ws;
        wl = &ws->winlist;
 
+       DNPRINTF(SWM_D_FOCUS, "focus: id: %d, cur_focus: %#x\n", args->id,
+           WINID(cur_focus));
+
        /* Make sure an uniconified window has focus, if one exists. */
        if (cur_focus == NULL) {
                cur_focus = TAILQ_FIRST(wl);
                while (cur_focus != NULL && ICONIC(cur_focus))
                        cur_focus = TAILQ_NEXT(cur_focus, entry);
+
+               DNPRINTF(SWM_D_FOCUS, "focus: new cur_focus: %#x\n",
+                   WINID(cur_focus));
        }
 
        switch (args->id) {
@@ -4256,8 +4763,11 @@ focus(struct swm_region *r, union arg *args)
                                winfocus = TAILQ_LAST(wl, ws_win_list);
                        if (winfocus == cur_focus)
                                break;
-               } while (winfocus != NULL &&
-                   (ICONIC(winfocus) || winfocus->id == cur_focus->transient));
+               } while (winfocus && (ICONIC(winfocus) ||
+                   winfocus->id == cur_focus->transient ||
+                   (cur_focus->transient != XCB_WINDOW_NONE &&
+                   winfocus->transient == cur_focus->transient) ||
+                   (winfocus->quirks & SWM_Q_NOFOCUSCYCLE)));
                break;
        case SWM_ARG_ID_FOCUSNEXT:
                if (cur_focus == NULL)
@@ -4270,8 +4780,11 @@ focus(struct swm_region *r, union arg *args)
                                winfocus = TAILQ_FIRST(wl);
                        if (winfocus == cur_focus)
                                break;
-               } while (winfocus != NULL &&
-                   (ICONIC(winfocus) || winfocus->id == cur_focus->transient));
+               } while (winfocus && (ICONIC(winfocus) ||
+                   winfocus->id == cur_focus->transient ||
+                   (cur_focus->transient != XCB_WINDOW_NONE &&
+                   winfocus->transient == cur_focus->transient) ||
+                   (winfocus->quirks & SWM_Q_NOFOCUSCYCLE)));
                break;
        case SWM_ARG_ID_FOCUSMAIN:
                if (cur_focus == NULL)
@@ -4291,29 +4804,28 @@ focus(struct swm_region *r, union arg *args)
                                head = TAILQ_FIRST(&r->s->ws[(ws->idx + i) %
                                    workspace_limit].winlist);
 
-                       while (head != NULL &&
-                           (head = TAILQ_NEXT(head, entry)) != NULL) {
+                       while (head) {
                                if (head == cur_focus) {
-                                       winfocus = cur_focus;
-                                       break;
-                               }
-                               if (xcb_icccm_get_wm_hints_reply(conn,
-                                   xcb_icccm_get_wm_hints(conn, head->id),
-                                   &hints, NULL) != 0 &&
-                                   xcb_icccm_wm_hints_get_urgency(&hints)) {
+                                       if (i > 0) {
+                                               winfocus = cur_focus;
+                                               break;
+                                       }
+                               } else if (get_urgent(head)) {
                                        winfocus = head;
                                        break;
                                }
+
+                               head = TAILQ_NEXT(head, entry);
                        }
 
-                       if (winfocus != NULL)
+                       if (winfocus)
                                break;
                }
 
                /* Switch ws if new focus is on a different ws. */
-               if (winfocus != NULL && winfocus->ws != ws) {
+               if (winfocus && winfocus->ws != ws) {
                        a.id = winfocus->ws->idx;
-                       switchws(r, &a);
+                       switchws(bp, r, &a);
                }
                break;
        default:
@@ -4321,9 +4833,10 @@ focus(struct swm_region *r, union arg *args)
        }
 
        if (clear_maximized(ws) > 0)
-               stack();
+               stack(r);
 
        focus_win(get_focus_magic(winfocus));
+       center_pointer(r);
        focus_flush();
 
 out:
@@ -4331,11 +4844,24 @@ out:
 }
 
 void
-cycle_layout(struct swm_region *r, union arg *args)
+focus_pointer(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       (void)args;
+
+       /* Not needed for buttons since this is already done in buttonpress. */
+       if (bp->type == KEYBIND) {
+               focus_win(get_pointer_win(r->s->root));
+               focus_flush();
+       }
+}
+
+void
+cycle_layout(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct workspace        *ws = r->ws;
 
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)args;
 
        DNPRINTF(SWM_D_EVENT, "cycle_layout: workspace: %d\n", ws->idx);
@@ -4346,95 +4872,91 @@ cycle_layout(struct swm_region *r, union arg *args)
 
        clear_maximized(ws);
 
-       stack();
-       bar_draw();
+       stack(r);
+       bar_draw(r->bar);
 
        focus_win(get_region_focus(r));
 
+       center_pointer(r);
        focus_flush();
 }
 
 void
-stack_config(struct swm_region *r, union arg *args)
+stack_config(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct workspace        *ws = r->ws;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_STACK, "stack_config: id: %d workspace: %d\n",
            args->id, ws->idx);
 
        if (clear_maximized(ws) > 0)
-               stack();
+               stack(r);
 
        if (ws->cur_layout->l_config != NULL)
                ws->cur_layout->l_config(ws, args->id);
 
        if (args->id != SWM_ARG_ID_STACKINIT)
-               stack();
-       bar_draw();
+               stack(r);
+       bar_draw(r->bar);
 
+       center_pointer(r);
        focus_flush();
 }
 
 void
-stack(void) {
+stack(struct swm_region *r) {
        struct swm_geometry     g;
-       struct swm_region       *r, *r_prev = NULL;
-       int                     i, num_screens;
+       struct swm_region       *r_prev;
        uint32_t                val[2];
-#ifdef SWM_DEBUG
-       int j;
-#endif
 
-       DNPRINTF(SWM_D_STACK, "stack: begin\n");
+       if (r == NULL)
+               return;
 
-       num_screens = get_screen_count();
-       for (i = 0; i < num_screens; i++) {
-#ifdef SWM_DEBUG
-               j = 0;
-#endif
-               TAILQ_FOREACH(r, &screens[i].rl, entry) {
-                       /* Adjust stack area for region bar and padding. */
-                       g = r->g;
-                       g.x += region_padding;
-                       g.y += region_padding;
-                       g.w -= 2 * border_width + 2 * region_padding;
-                       g.h -= 2 * border_width + 2 * region_padding;
-                       if (bar_enabled && r->ws->bar_enabled) {
-                               if (!bar_at_bottom)
-                                       g.y += bar_height;
-                               g.h -= bar_height;
-                       }
+       DNPRINTF(SWM_D_STACK, "stack: begin\n");
 
-                       DNPRINTF(SWM_D_STACK, "stack: workspace: %d (screen: "
-                           "%d, region: %d), (x,y) WxH: (%d,%d) %d x %d\n",
-                           r->ws->idx, i, j++, g.x, g.y, g.w, g.h);
-
-                       if (r_prev) {
-                               /* Stack bar/input relative to prev. region. */
-                               val[1] = XCB_STACK_MODE_ABOVE;
-
-                               val[0] = r_prev->id;
-                               DNPRINTF(SWM_D_STACK, "stack: region input %#x "
-                                   "relative to %#x.\n", r->id, val[0]);
-                               xcb_configure_window(conn, r->id,
-                                   XCB_CONFIG_WINDOW_SIBLING |
-                                   XCB_CONFIG_WINDOW_STACK_MODE, val);
-
-                               val[0] = r_prev->bar->id;
-                               DNPRINTF(SWM_D_STACK, "stack: region bar %#x "
-                                   "relative to %#x.\n", r->bar->id, val[0]);
-                               xcb_configure_window(conn, r->bar->id,
-                                   XCB_CONFIG_WINDOW_SIBLING |
-                                   XCB_CONFIG_WINDOW_STACK_MODE, val);
-                       }
+       /* Adjust stack area for region bar and padding. */
+       g = r->g;
+       g.x += region_padding;
+       g.y += region_padding;
+       g.w -= 2 * border_width + 2 * region_padding;
+       g.h -= 2 * border_width + 2 * region_padding;
+       if (bar_enabled && r->ws->bar_enabled) {
+               if (!bar_at_bottom)
+                       g.y += bar_height;
+               g.h -= bar_height;
+       }
+
+       DNPRINTF(SWM_D_STACK, "stack: workspace: %d (screen: "
+           "%d, region: %d), (x,y) WxH: (%d,%d) %d x %d\n",
+           r->ws->idx, r->s->idx, get_region_index(r), g.x, g.y, g.w, g.h);
+
+       r_prev = TAILQ_PREV(r, swm_region_list, entry);
+       if (r_prev) {
+               /* Stack bar/input relative to prev. region. */
+               val[1] = XCB_STACK_MODE_ABOVE;
+
+               val[0] = r_prev->id;
+               DNPRINTF(SWM_D_STACK, "stack: region input %#x "
+                   "relative to %#x.\n", r->id, val[0]);
+               xcb_configure_window(conn, r->id,
+                   XCB_CONFIG_WINDOW_SIBLING |
+                   XCB_CONFIG_WINDOW_STACK_MODE, val);
 
-                       r->ws->cur_layout->l_stack(r->ws, &g);
-                       r->ws->cur_layout->l_string(r->ws);
-                       /* save r so we can track region changes */
-                       r->ws->old_r = r;
-                       r_prev = r;
-               }
+               val[0] = r_prev->bar->id;
+               DNPRINTF(SWM_D_STACK, "stack: region bar %#x "
+                   "relative to %#x.\n", r->bar->id, val[0]);
+               xcb_configure_window(conn, r->bar->id,
+                   XCB_CONFIG_WINDOW_SIBLING |
+                   XCB_CONFIG_WINDOW_STACK_MODE, val);
        }
+
+       r->ws->cur_layout->l_stack(r->ws, &g);
+       r->ws->cur_layout->l_string(r->ws);
+       /* save r so we can track region changes */
+       r->ws->old_r = r;
+
        if (font_adjusted)
                font_adjusted--;
 
@@ -4451,7 +4973,7 @@ store_float_geom(struct ws_win *win)
        win->g_float = win->g;
        win->g_float.x -= X(win->ws->r);
        win->g_float.y -= Y(win->ws->r);
-       win->g_floatvalid = 1;
+       win->g_floatvalid = true;
        DNPRINTF(SWM_D_MISC, "store_float_geom: win %#x, g: (%d,%d)"
            " %d x %d, g_float: (%d,%d) %d x %d\n", win->id, X(win), Y(win),
            WIDTH(win), HEIGHT(win), win->g_float.x, win->g_float.y,
@@ -4483,6 +5005,8 @@ update_floater(struct ws_win *win)
        struct workspace        *ws;
        struct swm_region       *r;
 
+       DNPRINTF(SWM_D_MISC, "update_floater: win %#x\n", WINID(win));
+
        if (win == NULL)
                return;
 
@@ -4491,7 +5015,7 @@ update_floater(struct ws_win *win)
        if ((r = ws->r) == NULL)
                return;
 
-       DNPRINTF(SWM_D_MISC, "update_floater: win %#x\n", win->id);
+       win->bordered = true;
 
        if (FULLSCREEN(win)) {
                /* _NET_WM_FULLSCREEN: fullscreen without border. */
@@ -4499,7 +5023,7 @@ update_floater(struct ws_win *win)
                        store_float_geom(win);
 
                win->g = r->g;
-               win->bordered = 0;
+               win->bordered = false;
        } else if (MAXIMIZED(win)) {
                /* Maximize: like a single stacked window. */
                if (!win->g_floatvalid)
@@ -4507,18 +5031,18 @@ update_floater(struct ws_win *win)
 
                win->g = r->g;
 
-               if (bar_enabled && ws->bar_enabled) {
-                       win->bordered = 1;
+               if (bar_enabled && ws->bar_enabled && !maximize_hide_bar) {
                        if (!bar_at_bottom)
                                Y(win) += bar_height;
                        HEIGHT(win) -= bar_height;
                } else if (disable_border) {
-                       win->bordered = 0;
-               } else {
-                       win->bordered = 1;
+                       win->bordered = false;
                }
 
                if (win->bordered) {
+                       /* Window geometry excludes frame. */
+                       X(win) += border_width;
+                       Y(win) += border_width;
                        HEIGHT(win) -= 2 * border_width;
                        WIDTH(win) -= 2 * border_width;
                }
@@ -4528,10 +5052,12 @@ update_floater(struct ws_win *win)
                if (r != ws->old_r)
                        load_float_geom(win);
 
-               if ((win->quirks & SWM_Q_FULLSCREEN) &&
-                   WIDTH(win) >= WIDTH(r) && HEIGHT(win) >= HEIGHT(r)) {
-                       /* Remove border for FULLSCREEN quirk. */
-                       win->bordered = 0;
+               if (((win->quirks & SWM_Q_FULLSCREEN) &&
+                    WIDTH(win) >= WIDTH(r) && HEIGHT(win) >= HEIGHT(r)) ||
+                   ((!WS_FOCUSED(win->ws) || win->ws->focus != win) &&
+                    (win->quirks & SWM_Q_MINIMALBORDER))) {
+                       /* Remove border */
+                       win->bordered = false;
                } else if (!MANUAL(win)) {
                        if (TRANS(win) && (win->quirks & SWM_Q_TRANSSZ)) {
                                /* Adjust size on TRANSSZ quirk. */
@@ -4545,10 +5071,8 @@ update_floater(struct ws_win *win)
                                 * unless manually moved, resized or ANYWHERE
                                 * quirk is set.
                                 */
-                               X(win) = X(r) + (WIDTH(r) - WIDTH(win)) / 2 -
-                                   BORDER(win);
-                               Y(win) = Y(r) + (HEIGHT(r) - HEIGHT(win)) / 2 -
-                                   BORDER(win);
+                               X(win) = X(r) + (WIDTH(r) - WIDTH(win)) / 2;
+                               Y(win) = Y(r) + (HEIGHT(r) - HEIGHT(win)) / 2;
                                store_float_geom(win);
                        }
                }
@@ -4595,22 +5119,32 @@ adjust_font(struct ws_win *win)
        tmp = (g)->h; (g)->h = (g)->w; (g)->w = tmp;    \
 } while (0)
 void
-stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
+stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool flip)
 {
-       struct swm_geometry     win_g, r_g = *g;
+       struct swm_geometry     cell, r_g = *g;
        struct ws_win           *win;
-       int                     i, j, s, stacks;
+       int                     i = 0, j = 0, s = 0, stacks = 0;
        int                     w_inc = 1, h_inc, w_base = 1, h_base;
-       int                     hrh, extra = 0, h_slice, last_h = 0;
-       int                     split, colno, winno, mwin, msize, mscale;
-       int                     remain, missing, v_slice, reconfigure = 0;
-       int                     bordered = 1;
+       int                     hrh = 0, extra = 0, h_slice = 0, last_h = 0;
+       int                     split = 0, colno = 0;
+       int                     winno, mwin = 0, msize = 0;
+       int                     remain, missing, v_slice, mscale;
+       bool                    bordered = true, reconfigure = false;
+
+       /*
+        * cell: geometry for window, including frame.
+        * mwin: # of windows in master area.
+        * mscale: size increment of master area.
+        * stacks: # of stack columns
+        */
 
        DNPRINTF(SWM_D_STACK, "stack_master: workspace: %d, rot: %s, "
            "flip: %s\n", ws->idx, YESNO(rot), YESNO(flip));
 
+       memset(&cell, 0, sizeof(cell));
+
        /* Prepare tiling variables, if needed. */
-       if ((winno = count_win(ws, 0)) > 0) {
+       if ((winno = count_win(ws, false)) > 0) {
                /* Find first tiled window. */
                TAILQ_FOREACH(win, &ws->winlist, entry)
                        if (!FLOATING(win) && !ICONIC(win))
@@ -4631,7 +5165,10 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        mscale = ws->l_state.vertical_msize;
                        stacks = ws->l_state.vertical_stacks;
                }
-               win_g = r_g;
+
+               cell = r_g;
+               cell.x += border_width;
+               cell.y += border_width;
 
                if (stacks > winno - mwin)
                        stacks = winno - mwin;
@@ -4644,26 +5181,27 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
 
                        split = mwin;
                        colno = split;
-                       win_g.w = v_slice * mscale;
+                       cell.w = v_slice * mscale;
 
                        if (w_inc > 1 && w_inc < v_slice) {
                                /* Adjust for requested size increment. */
-                               remain = (win_g.w - w_base) % w_inc;
-                               win_g.w -= remain;
+                               remain = (cell.w - w_base) % w_inc;
+                               cell.w -= remain;
                        }
 
-                       msize = win_g.w;
+                       msize = cell.w;
                        if (flip)
-                               win_g.x += r_g.w - msize;
+                               cell.x += r_g.w - msize;
                } else {
                        msize = -2;
                        colno = split = winno / stacks;
-                       win_g.w = ((r_g.w - (stacks * 2 * border_width) +
+                       cell.w = ((r_g.w - (stacks * 2 * border_width) +
                            2 * border_width) / stacks);
                }
+
                hrh = r_g.h / colno;
                extra = r_g.h - (colno * hrh);
-               win_g.h = hrh - 2 * border_width;
+               cell.h = hrh - 2 * border_width;
                i = j = 0, s = stacks;
        }
 
@@ -4687,24 +5225,24 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        extra = r_g.h - (colno * hrh);
 
                        if (!flip)
-                               win_g.x += win_g.w + 2 * border_width +
+                               cell.x += cell.w + 2 * border_width +
                                    tile_gap;
 
-                       win_g.w = (r_g.w - msize -
+                       cell.w = (r_g.w - msize -
                            (stacks * (2 * border_width + tile_gap))) / stacks;
                        if (s == 1)
-                               win_g.w += (r_g.w - msize -
+                               cell.w += (r_g.w - msize -
                                    (stacks * (2 * border_width + tile_gap))) %
                                    stacks;
 
                        if (flip)
-                               win_g.x -= win_g.w + 2 * border_width +
+                               cell.x -= cell.w + 2 * border_width +
                                    tile_gap;
                        s--;
                        j = 0;
                }
 
-               win_g.h = hrh - 2 * border_width - tile_gap;
+               cell.h = hrh - 2 * border_width - tile_gap;
 
                if (rot) {
                        h_inc = win->sh.width_inc;
@@ -4715,57 +5253,64 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                }
 
                if (j == colno - 1) {
-                       win_g.h = hrh + extra;
+                       cell.h = hrh + extra;
                } else if (h_inc > 1 && h_inc < h_slice) {
                        /* adjust for window's requested size increment */
-                       remain = (win_g.h - h_base) % h_inc;
+                       remain = (cell.h - h_base) % h_inc;
                        missing = h_inc - remain;
 
                        if (missing <= extra || j == 0) {
                                extra -= missing;
-                               win_g.h += missing;
+                               cell.h += missing;
                        } else {
-                               win_g.h -= remain;
+                               cell.h -= remain;
                                extra += remain;
                        }
                }
 
                if (j == 0)
-                       win_g.y = r_g.y;
+                       cell.y = r_g.y + border_width;
                else
-                       win_g.y += last_h + 2 * border_width + tile_gap;
+                       cell.y += last_h + 2 * border_width + tile_gap;
+
+               /* Window coordinates exclude frame. */
 
-               if (disable_border && !(bar_enabled && ws->bar_enabled) &&
-                   winno == 1){
-                       bordered = 0;
-                       win_g.w += 2 * border_width;
-                       win_g.h += 2 * border_width;
+               if (winno > 1 || !disable_border ||
+                   (bar_enabled && ws->bar_enabled)) {
+                       bordered = true;
                } else {
-                       bordered = 1;
+                       bordered = false;
                }
 
                if (rot) {
-                       if (X(win) != win_g.y || Y(win) != win_g.x ||
-                           WIDTH(win) != win_g.h || HEIGHT(win) != win_g.w) {
-                               reconfigure = 1;
-                               X(win) = win_g.y;
-                               Y(win) = win_g.x;
-                               WIDTH(win) = win_g.h;
-                               HEIGHT(win) = win_g.w;
+                       if (X(win) != cell.y || Y(win) != cell.x ||
+                           WIDTH(win) != cell.h || HEIGHT(win) != cell.w) {
+                               reconfigure = true;
+                               X(win) = cell.y;
+                               Y(win) = cell.x;
+                               WIDTH(win) = cell.h;
+                               HEIGHT(win) = cell.w;
                        }
                } else {
-                       if (X(win) != win_g.x || Y(win) != win_g.y ||
-                           WIDTH(win) != win_g.w || HEIGHT(win) != win_g.h) {
-                               reconfigure = 1;
-                               X(win) = win_g.x;
-                               Y(win) = win_g.y;
-                               WIDTH(win) = win_g.w;
-                               HEIGHT(win) = win_g.h;
+                       if (X(win) != cell.x || Y(win) != cell.y ||
+                           WIDTH(win) != cell.w || HEIGHT(win) != cell.h) {
+                               reconfigure = true;
+                               X(win) = cell.x;
+                               Y(win) = cell.y;
+                               WIDTH(win) = cell.w;
+                               HEIGHT(win) = cell.h;
                        }
                }
 
+               if (!bordered) {
+                       X(win) -= border_width;
+                       Y(win) -= border_width;
+                       WIDTH(win) += 2 * border_width;
+                       HEIGHT(win) += 2 * border_width;
+               }
+
                if (bordered != win->bordered) {
-                       reconfigure = 1;
+                       reconfigure = true;
                        win->bordered = bordered;
                }
 
@@ -4774,7 +5319,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        update_window(win);
                }
 
-               last_h = win_g.h;
+               last_h = cell.h;
                i++;
                j++;
        }
@@ -4820,6 +5365,9 @@ vertical_config(struct workspace *ws, int id)
                if (ws->l_state.vertical_mwin > 0)
                        ws->l_state.vertical_mwin--;
                break;
+       case SWM_ARG_ID_STACKBALANCE:
+               ws->l_state.vertical_msize = SWM_V_SLICE / (ws->l_state.vertical_stacks + 1);
+               break;
        case SWM_ARG_ID_STACKINC:
                ws->l_state.vertical_stacks++;
                break;
@@ -4870,6 +5418,9 @@ horizontal_config(struct workspace *ws, int id)
                if (ws->l_state.horizontal_mwin > 0)
                        ws->l_state.horizontal_mwin--;
                break;
+       case SWM_ARG_ID_STACKBALANCE:
+               ws->l_state.horizontal_msize = SWM_H_SLICE / (ws->l_state.horizontal_stacks + 1);
+               break;
        case SWM_ARG_ID_STACKINC:
                ws->l_state.horizontal_stacks++;
                break;
@@ -4898,7 +5449,7 @@ void
 max_stack(struct workspace *ws, struct swm_geometry *g)
 {
        struct swm_geometry     gg = *g;
-       struct ws_win           *w, *win = NULL, *parent = NULL;
+       struct ws_win           *w, *win = NULL, *parent = NULL, *tmpw;
        int                     winno;
 
        DNPRINTF(SWM_D_STACK, "max_stack: workspace: %d\n", ws->idx);
@@ -4906,8 +5457,8 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
        if (ws == NULL)
                return;
 
-       winno = count_win(ws, 0);
-       if (winno == 0 && count_win(ws, 1) == 0)
+       winno = count_win(ws, false);
+       if (winno == 0 && count_win(ws, true) == 0)
                return;
 
        /* Figure out which top level window should be visible. */
@@ -4949,30 +5500,35 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
                if (X(w) != gg.x || Y(w) != gg.y || WIDTH(w) != gg.w ||
                    HEIGHT(w) != gg.h) {
                        w->g = gg;
-                       if (bar_enabled && ws->bar_enabled){
-                               w->bordered = 1;
-                       } else {
-                               w->bordered = 0;
+
+                       if (disable_border && !(bar_enabled && ws->bar_enabled)) {
+                               w->bordered = false;
                                WIDTH(w) += 2 * border_width;
                                HEIGHT(w) += 2 * border_width;
+                       } else {
+                               w->bordered = true;
+                               X(w) += border_width;
+                               Y(w) += border_width;
                        }
 
                        update_window(w);
                }
        }
 
-       if (TRANS(win)) {
-               parent = find_window(win->transient);
+       /* If transient, stack parent and its children. */
+       if (TRANS(win) && (parent = find_window(win->transient))) {
                raise_window(parent);
 
-               TAILQ_FOREACH(w, &ws->stack, stack_entry)
+               TAILQ_FOREACH_SAFE(w, &ws->stack, stack_entry, tmpw)
                        if (w->transient == parent->id)
                                raise_window(w);
        }
 
+       /* Make sure focus window is on top. */
        raise_window(win);
 
-       TAILQ_FOREACH(w, &ws->stack, stack_entry)
+       /* Stack any children of focus window. */
+       TAILQ_FOREACH_SAFE(w, &ws->stack, stack_entry, tmpw)
                if (w->transient == win->id)
                        raise_window(w);
 
@@ -4983,7 +5539,7 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
 }
 
 void
-send_to_rg(struct swm_region *r, union arg *args)
+send_to_rg(struct binding *bp, struct swm_region *r, union arg *args)
 {
        int                     ridx = args->id, i, num_screens;
        struct swm_region       *rr = NULL;
@@ -5005,7 +5561,7 @@ send_to_rg(struct swm_region *r, union arg *args)
 
        a.id = rr->ws->idx;
 
-       send_to_ws(r, &a);
+       send_to_ws(bp, r, &a);
 }
 
 struct swm_region *
@@ -5027,11 +5583,13 @@ region_under(struct swm_screen *s, int x, int y)
 
 /* Transfer focused window to target workspace and focus. */
 void
-send_to_ws(struct swm_region *r, union arg *args)
+send_to_ws(struct binding *bp, struct swm_region *r, union arg *args)
 {
        int                     wsid = args->id;
        struct ws_win           *win = NULL;
 
+       (void)bp;
+
        if (r && r->ws && r->ws->focus)
                win = r->ws->focus;
        else
@@ -5045,12 +5603,16 @@ send_to_ws(struct swm_region *r, union arg *args)
        if (win->ws->idx == wsid)
                return;
 
-       win_to_ws(win, wsid, 1);
+       win_to_ws(win, wsid, true);
 
-       /* Set window to be focus on target ws. */
+       /* Set new focus on target ws. */
        if (focus_mode != SWM_FOCUS_FOLLOW) {
+               win->ws->focus_prev = win->ws->focus;
                win->ws->focus = win;
                win->ws->focus_pending = NULL;
+
+               if (win->ws->focus_prev)
+                       draw_frame(win->ws->focus_prev);
        }
 
        DNPRINTF(SWM_D_STACK, "send_to_ws: focus_pending: %#x, focus: %#x, "
@@ -5062,27 +5624,60 @@ send_to_ws(struct swm_region *r, union arg *args)
        ewmh_apply_flags(win, win->ewmh_flags & ~EWMH_F_MAXIMIZED);
        ewmh_update_wm_state(win);
 
-       /* Restack and set new focus. */
-       if (FLOATING(win))
-               load_float_geom(win);
+       /* Restack focused region. */
+       stack(r);
 
-       stack();
+       /* If destination ws has a region, restack. */
+       if (win->ws->r) {
+               if (FLOATING(win))
+                       load_float_geom(win);
+
+               stack(win->ws->r);
+       }
 
+       /* Set new focus on current ws. */
        if (focus_mode != SWM_FOCUS_FOLLOW) {
                if (r->ws->focus != NULL) {
                        focus_win(r->ws->focus);
                } else {
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->id,
-                           XCB_CURRENT_TIME);
-                       bar_draw();
+                       DNPRINTF(SWM_D_FOCUS, "send_to_ws: set_input_focus: "
+                           "%#x, revert-to: parent, time: 0\n", r->id);
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+                           r->id, XCB_CURRENT_TIME);
+                       bar_draw(r->bar);
                }
        }
 
+       center_pointer(r);
        focus_flush();
 }
 
+/* Transfer focused window to region-relative workspace and focus. */
 void
-win_to_ws(struct ws_win *win, int wsid, int unfocus)
+send_to_rg_relative(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       union arg               args_abs;
+       struct swm_region       *r_other;
+
+       if (args->id == 1) {
+               r_other = TAILQ_NEXT(r, entry);
+               if (r_other == NULL)
+                       r_other = TAILQ_FIRST(&r->s->rl);
+       } else {
+               r_other = TAILQ_PREV(r, swm_region_list, entry);
+               if (r_other == NULL)
+                       r_other = TAILQ_LAST(&r->s->rl, swm_region_list);
+       }
+
+       /* Map relative to absolute */
+       args_abs = *args;
+       args_abs.id = r_other->ws->idx;
+
+       send_to_ws(bp, r, &args_abs);
+}
+
+void
+win_to_ws(struct ws_win *win, int wsid, bool unfocus)
 {
        struct ws_win           *parent;
        struct workspace        *ws, *nws, *pws;
@@ -5176,9 +5771,10 @@ win_to_ws(struct ws_win *win, int wsid, int unfocus)
 }
 
 void
-pressbutton(struct swm_region *r, union arg *args)
+pressbutton(struct binding *bp, struct swm_region *r, union arg *args)
 {
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)r;
 
        xcb_test_fake_input(conn, XCB_BUTTON_PRESS, args->id,
@@ -5188,9 +5784,35 @@ pressbutton(struct swm_region *r, union arg *args)
 }
 
 void
-raise_toggle(struct swm_region *r, union arg *args)
+raise_focus(struct binding *bp, struct swm_region *r, union arg *args)
 {
-       /* suppress unused warning since var is needed */
+       struct ws_win   *win;
+       uint32_t        val;
+
+       /* Suppress warning. */
+       (void)bp;
+       (void)args;
+
+       if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
+               return;
+
+       win = r->ws->focus;
+       r->ws->focus_raise = win;
+       raise_window(win);
+
+       /* Temporarily override stacking order also in the stack */
+       if (!FLOATING(win)) {
+               val = XCB_STACK_MODE_ABOVE;
+               xcb_configure_window(conn, win->frame,
+                   XCB_CONFIG_WINDOW_STACK_MODE, &val);
+       }
+}
+
+void
+raise_toggle(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       /* Suppress warning. */
+       (void)bp;
        (void)args;
 
        if (r == NULL || r->ws == NULL)
@@ -5201,18 +5823,19 @@ raise_toggle(struct swm_region *r, union arg *args)
 
        r->ws->always_raise = !r->ws->always_raise;
 
-       /* bring floaters back to top */
-       if (!r->ws->always_raise)
-               stack();
+       /* Update focused win stacking order based on new always_raise value. */
+       raise_window(r->ws->focus);
 
        focus_flush();
 }
 
 void
-iconify(struct swm_region *r, union arg *args)
+iconify(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *w;
-       /* suppress unused warning since var is needed */
+
+       /* Suppress warning. */
+       (void)bp;
        (void)args;
 
        if ((w = r->ws->focus) == NULL)
@@ -5221,7 +5844,7 @@ iconify(struct swm_region *r, union arg *args)
        ewmh_apply_flags(w, w->ewmh_flags | EWMH_F_HIDDEN);
        ewmh_update_wm_state(w);
 
-       stack();
+       stack(r);
 
        focus_flush();
 }
@@ -5260,13 +5883,15 @@ get_win_name(xcb_window_t win)
 }
 
 void
-uniconify(struct swm_region *r, union arg *args)
+uniconify(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *win;
        FILE                    *lfile;
        char                    *name;
        int                     count = 0;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_MISC, "uniconify\n");
 
        if (r == NULL || r->ws == NULL)
@@ -5309,10 +5934,12 @@ uniconify(struct swm_region *r, union arg *args)
 }
 
 void
-name_workspace(struct swm_region *r, union arg *args)
+name_workspace(struct binding *bp, struct swm_region *r, union arg *args)
 {
        FILE                    *lfile;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_MISC, "name_workspace\n");
 
        if (r == NULL)
@@ -5331,12 +5958,14 @@ name_workspace(struct swm_region *r, union arg *args)
 }
 
 void
-search_workspace(struct swm_region *r, union arg *args)
+search_workspace(struct binding *bp, struct swm_region *r, union arg *args)
 {
        int                     i;
        struct workspace        *ws;
        FILE                    *lfile;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_MISC, "search_workspace\n");
 
        if (r == NULL)
@@ -5374,12 +6003,13 @@ search_win_cleanup(void)
 }
 
 void
-search_win(struct swm_region *r, union arg *args)
+search_win(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *win = NULL;
        struct search_window    *sw = NULL;
        xcb_window_t            w;
-       uint32_t                wa[2];
+       uint32_t                wa[3];
+       xcb_screen_t            *screen;
        int                     i, width, height;
        char                    s[8];
        FILE                    *lfile;
@@ -5390,6 +6020,8 @@ search_win(struct swm_region *r, union arg *args)
        XGCValues               l_gcv;
        XRectangle              l_ibox, l_lbox;
 
+       (void)bp;
+
        DNPRINTF(SWM_D_MISC, "search_win\n");
 
        search_r = r;
@@ -5400,6 +6032,9 @@ search_win(struct swm_region *r, union arg *args)
        if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL)
                return;
 
+       if ((screen = get_screen(r->s->idx)) == NULL)
+               errx(1, "ERROR: can't get screen %d.", r->s->idx);
+
        TAILQ_INIT(&search_wl);
 
        i = 1;
@@ -5423,6 +6058,7 @@ search_win(struct swm_region *r, union arg *args)
                w = xcb_generate_id(conn);
                wa[0] = r->s->c[SWM_S_COLOR_FOCUS].pixel;
                wa[1] = r->s->c[SWM_S_COLOR_UNFOCUS].pixel;
+               wa[2] = screen->default_colormap;
 
                if (bar_font_legacy) {
                        XmbTextExtents(bar_fs, s, len, &l_ibox, &l_lbox);
@@ -5435,10 +6071,10 @@ search_win(struct swm_region *r, union arg *args)
                        height = bar_font->height + 4;
                }
 
-               xcb_create_window(conn, XCB_COPY_FROM_PARENT, w, win->id, 0, 0,
+               xcb_create_window(conn, screen->root_depth, w, win->frame, 0, 0,
                    width, height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                   XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL |
-                   XCB_CW_BORDER_PIXEL, wa);
+                   screen->root_visual, XCB_CW_BACK_PIXEL |
+                   XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, wa);
 
                xcb_map_window(conn, w);
 
@@ -5463,7 +6099,7 @@ search_win(struct swm_region *r, union arg *args)
                            DefaultVisual(display, r->s->idx),
                            DefaultColormap(display, r->s->idx));
 
-                       XftDrawStringUtf8(draw, &bar_font_color, bar_font, 2,
+                       XftDrawStringUtf8(draw, &search_font_color, bar_font, 2,
                            (HEIGHT(r->bar) + bar_font->height) / 2 -
                            bar_font->descent, (FcChar8 *)s, len);
 
@@ -5503,7 +6139,7 @@ search_resp_uniconify(const char *resp, size_t len)
                        /* XXX this should be a callback to generalize */
                        ewmh_apply_flags(win, win->ewmh_flags & ~EWMH_F_HIDDEN);
                        ewmh_update_wm_state(win);
-                       stack();
+                       stack(search_r);
                        free(s);
                        break;
                }
@@ -5554,7 +6190,7 @@ ewmh_update_desktop_names(void)
                        ++len;
                }
 
-               if((name_list = calloc(len, sizeof(char))) == NULL)
+               if ((name_list = calloc(len, sizeof(char))) == NULL)
                        err(1, "update_desktop_names: calloc: failed to "
                            "allocate memory.");
 
@@ -5660,10 +6296,13 @@ ewmh_update_current_desktop(void)
        int                     num_screens, i;
 
        num_screens = get_screen_count();
-       for (i = 0; i < num_screens; ++i)
-               xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
-                   screens[i].root, ewmh[_NET_CURRENT_DESKTOP].atom,
-                   XCB_ATOM_CARDINAL, 32, 1, &screens[i].r_focus->ws->idx);
+       for (i = 0; i < num_screens; ++i) {
+               if (screens[i].r_focus)
+                       xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
+                           screens[i].root, ewmh[_NET_CURRENT_DESKTOP].atom,
+                           XCB_ATOM_CARDINAL, 32, 1,
+                           &screens[i].r_focus->ws->idx);
+       }
 }
 
 void
@@ -5731,7 +6370,7 @@ search_resp_search_workspace(const char *resp)
        }
        free(q);
        a.id = ws_idx - 1;
-       switchws(search_r, &a);
+       switchws(NULL, search_r, &a);
 }
 
 void
@@ -5827,8 +6466,10 @@ done:
 }
 
 void
-wkill(struct swm_region *r, union arg *args)
+wkill(struct binding *bp, struct swm_region *r, union arg *args)
 {
+       (void)bp;
+
        DNPRINTF(SWM_D_MISC, "wkill: win %#x, id: %d\n", WINID(r->ws->focus),
            args->id);
 
@@ -5862,11 +6503,12 @@ clear_maximized(struct workspace *ws)
 }
 
 void
-maximize_toggle(struct swm_region *r, union arg *args)
+maximize_toggle(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *w = r->ws->focus;
 
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)args;
 
        if (w == NULL)
@@ -5883,21 +6525,23 @@ maximize_toggle(struct swm_region *r, union arg *args)
        ewmh_apply_flags(w, w->ewmh_flags ^ EWMH_F_MAXIMIZED);
        ewmh_update_wm_state(w);
 
-       stack();
+       stack(r);
 
        if (w == w->ws->focus)
                focus_win(w);
 
+       center_pointer(r);
        focus_flush();
        DNPRINTF(SWM_D_MISC, "maximize_toggle: done\n");
 }
 
 void
-floating_toggle(struct swm_region *r, union arg *args)
+floating_toggle(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *w = r->ws->focus;
 
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)args;
 
        if (w == NULL)
@@ -5914,15 +6558,42 @@ floating_toggle(struct swm_region *r, union arg *args)
        ewmh_apply_flags(w, w->ewmh_flags ^ EWMH_F_ABOVE);
        ewmh_update_wm_state(w);
 
-       stack();
+       stack(r);
 
        if (w == w->ws->focus)
                focus_win(w);
 
+       center_pointer(r);
        focus_flush();
        DNPRINTF(SWM_D_MISC, "floating_toggle: done\n");
 }
 
+void
+fullscreen_toggle(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       struct ws_win           *w = r->ws->focus;
+
+       /* suppress unused warning since var is needed */
+       (void)bp;
+       (void)args;
+
+       if (w == NULL)
+               return;
+
+       DNPRINTF(SWM_D_MISC, "fullscreen_toggle: win %#x\n", w->id);
+
+       ewmh_apply_flags(w, w->ewmh_flags ^ EWMH_F_FULLSCREEN);
+       ewmh_update_wm_state(w);
+
+       stack(r);
+
+       if (w == w->ws->focus)
+               focus_win(w);
+
+       center_pointer(r);
+       focus_flush();
+       DNPRINTF(SWM_D_MISC, "fullscreen_toggle: done\n");
+}
 void
 region_containment(struct ws_win *win, struct swm_region *r, int opts)
 {
@@ -5936,10 +6607,10 @@ region_containment(struct ws_win *win, struct swm_region *r, int opts)
         * side of the region boundary.  Positive values indicate the side of
         * the window has passed beyond the region boundary.
         */
-       rt = opts & SWM_CW_RIGHT ? MAX_X(win) + BORDER(win) - MAX_X(r) : bw;
-       lt = opts & SWM_CW_LEFT ? X(r) - X(win) + BORDER(win) : bw;
-       bm = opts & SWM_CW_BOTTOM ? MAX_Y(win) + BORDER(win) - MAX_Y(r) : bw;
-       tp = opts & SWM_CW_TOP ? Y(r) - Y(win) + BORDER(win) : bw;
+       rt = opts & SWM_CW_RIGHT ? MAX_X(win) - MAX_X(r) : bw;
+       lt = opts & SWM_CW_LEFT ? X(r) - X(win) : bw;
+       bm = opts & SWM_CW_BOTTOM ? MAX_Y(win) - MAX_Y(r) : bw;
+       tp = opts & SWM_CW_TOP ? Y(r) - Y(win) : bw;
 
        DNPRINTF(SWM_D_MISC, "region_containment: win %#x, rt: %d, lt: %d, "
            "bm: %d, tp: %d, SOFTBOUNDARY: %s, HARDBOUNDARY: %s\n", win->id, rt,
@@ -5974,32 +6645,32 @@ constrain_window(struct ws_win *win, struct swm_geometry *b, int *opts)
            YESNO(*opts & SWM_CW_BOTTOM), YESNO(*opts & SWM_CW_TOP),
            YESNO(*opts & SWM_CW_RESIZABLE));
 
-       if ((*opts & SWM_CW_RIGHT) && MAX_X(win) + BORDER(win) > b->x + b->w) {
+       if ((*opts & SWM_CW_RIGHT) && MAX_X(win) > b->x + b->w) {
                if (*opts & SWM_CW_RESIZABLE)
-                       WIDTH(win) = b->x + b->w - X(win) - BORDER(win);
+                       WIDTH(win) = b->x + b->w - X(win);
                else
-                       X(win) = b->x + b->w - WIDTH(win) - BORDER(win);
+                       X(win) = b->x + b->w - WIDTH(win);
        }
 
-       if ((*opts & SWM_CW_LEFT) && X(win) + BORDER(win) < b->x) {
+       if ((*opts & SWM_CW_LEFT) && X(win) < b->x) {
                if (*opts & SWM_CW_RESIZABLE)
-                       WIDTH(win) -= b->x - X(win) - BORDER(win);
+                       WIDTH(win) -= b->x - X(win);
 
-               X(win) = b->x - BORDER(win);
+               X(win) = b->x;
        }
 
-       if ((*opts & SWM_CW_BOTTOM) && MAX_Y(win) + BORDER(win) > b->y + b->h) {
+       if ((*opts & SWM_CW_BOTTOM) && MAX_Y(win) > b->y + b->h) {
                if (*opts & SWM_CW_RESIZABLE)
-                       HEIGHT(win) = b->y + b->h - Y(win) - BORDER(win);
+                       HEIGHT(win) = b->y + b->h - Y(win);
                else
-                       Y(win) = b->y + b->h - HEIGHT(win) - BORDER(win);
+                       Y(win) = b->y + b->h - HEIGHT(win);
        }
 
-       if ((*opts & SWM_CW_TOP) && Y(win) + BORDER(win) < b->y) {
+       if ((*opts & SWM_CW_TOP) && Y(win) < b->y) {
                if (*opts & SWM_CW_RESIZABLE)
-                       HEIGHT(win) -= b->y - Y(win) - BORDER(win);
+                       HEIGHT(win) -= b->y - Y(win);
 
-               Y(win) = b->y - BORDER(win);
+               Y(win) = b->y;
        }
 
        if (*opts & SWM_CW_RESIZABLE) {
@@ -6011,9 +6682,22 @@ constrain_window(struct ws_win *win, struct swm_geometry *b, int *opts)
 }
 
 void
-update_window_color(struct ws_win *win)
+draw_frame(struct ws_win *win)
 {
-       uint32_t        *pixel;
+       xcb_rectangle_t         rect[4];
+       uint32_t                *pixel;
+       int                     n = 0;
+
+       if (win->frame == XCB_WINDOW_NONE) {
+               DNPRINTF(SWM_D_EVENT, "draw_frame: win %#x not "
+                   "reparented.\n", win->id);
+               return;
+       }
+
+       if (!win->bordered) {
+               DNPRINTF(SWM_D_EVENT, "draw_frame: win %#x frame "
+                   "disabled\n", win->id);
+       }
 
        if (WS_FOCUSED(win->ws) && win->ws->focus == win)
                pixel = MAXIMIZED(win) ?
@@ -6024,8 +6708,29 @@ update_window_color(struct ws_win *win)
                    &win->s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED].pixel :
                    &win->s->c[SWM_S_COLOR_UNFOCUS].pixel;
 
-       xcb_change_window_attributes(conn, win->id,
-           XCB_CW_BORDER_PIXEL, pixel);
+       /* Top (with corners) */
+       rect[n].x = 0;
+       rect[n].y = 0;
+       rect[n].width = WIDTH(win) + 2 * border_width;
+       rect[n].height = border_width;
+       /* Left (without corners) */
+       rect[++n].x = 0;
+       rect[n].y = border_width;
+       rect[n].width = border_width;
+       rect[n].height = HEIGHT(win);
+       /* Right (without corners) */
+       rect[++n].x = border_width + WIDTH(win);
+       rect[n].y = border_width;
+       rect[n].width = border_width;
+       rect[n].height = HEIGHT(win);
+       /* Bottom (with corners)*/
+       rect[++n].x = 0;
+       rect[n].y = border_width + HEIGHT(win);
+       rect[n].width = WIDTH(win) + 2 * border_width;
+       rect[n].height = border_width;
+
+       xcb_change_gc(conn, win->s->bar_gc, XCB_GC_FOREGROUND, pixel);
+       xcb_poly_fill_rectangle(conn, win->frame, win->s->bar_gc, 4, rect);
 }
 
 void
@@ -6034,37 +6739,139 @@ update_window(struct ws_win *win)
        uint16_t        mask;
        uint32_t        wc[5];
 
+       if (win->frame == XCB_WINDOW_NONE) {
+               DNPRINTF(SWM_D_EVENT, "update_window: skip win %#x; "
+                   "not reparented.\n", win->id);
+               return;
+       }
+
        mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
            XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
            XCB_CONFIG_WINDOW_BORDER_WIDTH;
-       wc[0] = X(win);
-       wc[1] = Y(win);
+
+       /* Reconfigure frame. */
+       if (win->bordered) {
+               wc[0] = X(win) - border_width;
+               wc[1] = Y(win) - border_width;
+               wc[2] = WIDTH(win) + 2 * border_width;
+               wc[3] = HEIGHT(win) + 2 * border_width;
+       } else {
+               wc[0] = X(win);
+               wc[1] = Y(win);
+               wc[2] = WIDTH(win);
+               wc[3] = HEIGHT(win);
+       }
+
+       wc[4] = 0;
+
+       DNPRINTF(SWM_D_EVENT, "update_window: win %#x frame %#x, (x,y) w x h: "
+           "(%d,%d) %d x %d, bordered: %s\n", win->id, win->frame, wc[0],
+           wc[1], wc[2], wc[3], YESNO(win->bordered));
+
+       xcb_configure_window(conn, win->frame, mask, wc);
+
+       /* Reconfigure client window. */
+       wc[0] = wc[1] = win->bordered ? border_width : 0;
        wc[2] = WIDTH(win);
        wc[3] = HEIGHT(win);
-       wc[4] = BORDER(win);
 
        DNPRINTF(SWM_D_EVENT, "update_window: win %#x, (x,y) w x h: "
            "(%d,%d) %d x %d, bordered: %s\n", win->id, wc[0], wc[1], wc[2],
            wc[3], YESNO(win->bordered));
-
        xcb_configure_window(conn, win->id, mask, wc);
+
+       /* ICCCM 4.2.3 Synthetic ConfigureNotify when moved and not resized. */
+       if ((X(win) != win->g_prev.x || Y(win) != win->g_prev.y) &&
+           (win->java || (WIDTH(win) == win->g_prev.w &&
+           HEIGHT(win) == win->g_prev.h))) {
+               /* Java has special needs when moved together with a resize. */
+               config_win(win, NULL);
+       }
+
+       win->g_prev = win->g;
+}
+
+struct event {
+       SIMPLEQ_ENTRY(event)    entry;
+       xcb_generic_event_t     *ev;
+};
+SIMPLEQ_HEAD(event_queue, event) events = SIMPLEQ_HEAD_INITIALIZER(events);
+
+xcb_generic_event_t *
+get_next_event(bool dowait)
+{
+       struct event            *ep;
+       xcb_generic_event_t     *evt;
+
+       /* Try queue first. */
+       if ((ep = SIMPLEQ_FIRST(&events))) {
+               evt = ep->ev;
+               SIMPLEQ_REMOVE_HEAD(&events, entry);
+               free(ep);
+       } else if (dowait)
+               evt = xcb_wait_for_event(conn);
+       else
+               evt = xcb_poll_for_event(conn);
+
+       return evt;
+}
+
+void
+put_back_event(xcb_generic_event_t *evt)
+{
+       struct event    *ep;
+       if ((ep = malloc(sizeof (struct event))) == NULL)
+               err(1, "put_back_event: failed to allocate memory.");
+       ep->ev = evt;
+       SIMPLEQ_INSERT_HEAD(&events, ep, entry);
+}
+
+/* Peeks at next event to detect auto-repeat. */
+bool
+keyrepeating(xcb_key_release_event_t *kre)
+{
+       xcb_generic_event_t     *evt;
+
+       /* Ensure repeating keypress is finished processing. */
+       xcb_aux_sync(conn);
+
+       if ((evt = get_next_event(false))) {
+               put_back_event(evt);
+
+               if (XCB_EVENT_RESPONSE_TYPE(evt) == XCB_KEY_PRESS &&
+                  kre->sequence == evt->sequence &&
+                  kre->detail == ((xcb_key_press_event_t *)evt)->detail)
+                       return true;
+       }
+
+       return false;
+}
+
+bool
+keybindreleased(struct binding *bp, xcb_key_release_event_t *kre)
+{
+       if (bp->type == KEYBIND && !keyrepeating(kre) &&
+               bp->value == xcb_key_press_lookup_keysym(syms, kre, 0))
+               return true;
+
+       return false;
 }
 
 #define SWM_RESIZE_STEPS       (50)
 
 void
-resize(struct ws_win *win, union arg *args)
+resize_win(struct ws_win *win, struct binding *bp, int opt)
 {
        xcb_timestamp_t         timestamp = 0;
        struct swm_region       *r = NULL;
        struct swm_geometry     g;
-       int                     resize_stp = 0;
-       int                     top = 0, left = 0, resizing;
+       int                     top = 0, left = 0;
        int                     dx, dy;
        xcb_cursor_t                    cursor;
        xcb_query_pointer_reply_t       *xpr = NULL;
        xcb_generic_event_t             *evt;
        xcb_motion_notify_event_t       *mne;
+       bool                    resizing, step = false;
 
        if (win == NULL)
                return;
@@ -6090,7 +6897,7 @@ resize(struct ws_win *win, union arg *args)
            ~EWMH_F_MAXIMIZED);
        ewmh_update_wm_state(win);
 
-       stack();
+       stack(r);
 
        focus_flush();
 
@@ -6100,27 +6907,27 @@ resize(struct ws_win *win, union arg *args)
                goto out;
        }
 
-       switch (args->id) {
+       switch (opt) {
        case SWM_ARG_ID_WIDTHSHRINK:
                WIDTH(win) -= SWM_RESIZE_STEPS;
-               resize_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_WIDTHGROW:
                WIDTH(win) += SWM_RESIZE_STEPS;
-               resize_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_HEIGHTSHRINK:
                HEIGHT(win) -= SWM_RESIZE_STEPS;
-               resize_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_HEIGHTGROW:
                HEIGHT(win) += SWM_RESIZE_STEPS;
-               resize_stp = 1;
+               step = true;
                break;
        default:
                break;
        }
-       if (resize_stp) {
+       if (step) {
                region_containment(win, r, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE |
                    SWM_CW_HARDBOUNDARY);
                update_window(win);
@@ -6146,7 +6953,7 @@ resize(struct ws_win *win, union arg *args)
        if (xpr->win_y < HEIGHT(win) / 2)
                top = 1;
 
-       if (args->id == SWM_ARG_ID_CENTER)
+       if (opt == SWM_ARG_ID_CENTER)
                cursor = cursors[XC_SIZING].cid;
        else if (top)
                cursor = cursors[left ? XC_TOP_LEFT_CORNER :
@@ -6157,18 +6964,31 @@ resize(struct ws_win *win, union arg *args)
 
        xcb_grab_pointer(conn, 0, win->id, MOUSEMASK,
            XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, cursor,
-           XCB_CURRENT_TIME),
+           XCB_CURRENT_TIME);
+
+       /* Release keyboard freeze if called via keybind. */
+       if (bp->type == KEYBIND)
+               xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD,
+                   XCB_CURRENT_TIME);
 
        xcb_flush(conn);
-       resizing = 1;
-       while (resizing && (evt = xcb_wait_for_event(conn))) {
+       resizing = true;
+       while (resizing && (evt = get_next_event(true))) {
                switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
                case XCB_BUTTON_RELEASE:
-                       DNPRINTF(SWM_D_EVENT, "resize: BUTTON_RELEASE\n");
-                       resizing = 0;
+                       if (bp->type == BTNBIND && bp->value ==
+                           ((xcb_button_release_event_t *)evt)->detail)
+                               resizing = false;
+                       break;
+               case XCB_KEY_RELEASE:
+                       if (keybindreleased(bp, (xcb_key_release_event_t *)evt))
+                               resizing = false;
                        break;
                case XCB_MOTION_NOTIFY:
                        mne = (xcb_motion_notify_event_t *)evt;
+                       DNPRINTF(SWM_D_EVENT, "resize: MOTION_NOTIFY: "
+                           "root: %#x\n", mne->root);
+
                        /* cursor offset/delta from start of the operation */
                        dx = mne->root_x - xpr->root_x;
                        dy = mne->root_y - xpr->root_y;
@@ -6177,7 +6997,7 @@ resize(struct ws_win *win, union arg *args)
                        if (top)
                                dy = -dy;
 
-                       if (args->id == SWM_ARG_ID_CENTER) {
+                       if (opt == SWM_ARG_ID_CENTER) {
                                if (g.h / 2 + dy < 1)
                                        dy = 1 - g.h / 2;
 
@@ -6197,7 +7017,7 @@ resize(struct ws_win *win, union arg *args)
                        if (left)
                                dx = -dx;
 
-                       if (args->id == SWM_ARG_ID_CENTER) {
+                       if (opt == SWM_ARG_ID_CENTER) {
                                if (g.w / 2 + dx < 1)
                                        dx = 1 - g.w / 2;
 
@@ -6224,8 +7044,16 @@ resize(struct ws_win *win, union arg *args)
                                xcb_flush(conn);
                        }
                        break;
+               case XCB_BUTTON_PRESS:
+                       /* Ignore. */
+                       DNPRINTF(SWM_D_EVENT, "resize: BUTTON_PRESS ignored\n");
+                       xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER,
+                           ((xcb_button_press_event_t *)evt)->time);
+                       xcb_flush(conn);
+                       break;
                case XCB_KEY_PRESS:
                        /* Ignore. */
+                       DNPRINTF(SWM_D_EVENT, "resize: KEY_PRESS ignored\n");
                        xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD,
                            ((xcb_key_press_event_t *)evt)->time);
                        xcb_flush(conn);
@@ -6256,16 +7084,35 @@ out:
 }
 
 void
-resize_step(struct swm_region *r, union arg *args)
+resize(struct binding *bp, struct swm_region *r, union arg *args)
 {
        struct ws_win           *win = NULL;
+       xcb_query_pointer_reply_t       *qpr = NULL;
 
-       if (r && r->ws && r->ws->focus)
-               win = r->ws->focus;
-       else
+       if (r == NULL)
+               return;
+
+       if (args->id != SWM_ARG_ID_DONTCENTER &&
+           args->id != SWM_ARG_ID_CENTER) {
+               /* {height,width}_{grow,shrink} use the focus window. */
+               if (r->ws)
+                       win = r->ws->focus;
+       } else {
+               /* resize uses window under pointer. */
+               qpr = xcb_query_pointer_reply(conn,
+                   xcb_query_pointer(conn, r->s->root), NULL);
+               if (qpr)
+                       win = find_window(qpr->child);
+       }
+
+       if (win == NULL)
                return;
 
-       resize(win, args);
+       resize_win(win, bp, args->id);
+
+       if (args->id && bp->type == KEYBIND)
+               center_pointer(r);
+
        focus_flush();
 }
 
@@ -6280,11 +7127,14 @@ regionize(struct ws_win *win, int x, int y)
                r = region_under(win->s, X(win) + WIDTH(win) / 2,
                    Y(win) + HEIGHT(win) / 2);
 
-       if (r != NULL && r != win->ws->r) {
-               if (clear_maximized(r->ws) > 0)
-                       stack();
+       if (r && r != win->ws->r) {
+               clear_maximized(r->ws);
 
-               win_to_ws(win, r->ws->idx, 0);
+               win_to_ws(win, r->ws->idx, false);
+
+               /* Stack old and new region. */
+               stack(r);
+               stack(win->ws->r);
 
                /* Set focus on new ws. */
                unfocus_win(r->ws->focus);
@@ -6298,14 +7148,14 @@ regionize(struct ws_win *win, int x, int y)
 #define SWM_MOVE_STEPS (50)
 
 void
-move(struct ws_win *win, union arg *args)
+move_win(struct ws_win *win, struct binding *bp, int opt)
 {
        struct swm_region               *r;
        xcb_timestamp_t                 timestamp = 0;
-       int                             move_stp = 0, moving, restack = 0;
        xcb_query_pointer_reply_t       *qpr = NULL;
        xcb_generic_event_t             *evt;
        xcb_motion_notify_event_t       *mne;
+       bool                            moving, restack = false, step = false;
 
        if (win == NULL)
                return;
@@ -6325,7 +7175,7 @@ move(struct ws_win *win, union arg *args)
 
        if (!(ABOVE(win) || TRANS(win)) || MAXIMIZED(win)) {
                store_float_geom(win);
-               restack = 1;
+               restack = true;
        }
 
        ewmh_apply_flags(win, (win->ewmh_flags | SWM_F_MANUAL | EWMH_F_ABOVE) &
@@ -6333,7 +7183,7 @@ move(struct ws_win *win, union arg *args)
        ewmh_update_wm_state(win);
 
        if (restack)
-               stack();
+               stack(r);
 
        focus_flush();
 
@@ -6343,28 +7193,27 @@ move(struct ws_win *win, union arg *args)
                goto out;
        }
 
-       move_stp = 0;
-       switch (args->id) {
+       switch (opt) {
        case SWM_ARG_ID_MOVELEFT:
                X(win) -= (SWM_MOVE_STEPS - border_width);
-               move_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_MOVERIGHT:
                X(win) += (SWM_MOVE_STEPS - border_width);
-               move_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_MOVEUP:
                Y(win) -= (SWM_MOVE_STEPS - border_width);
-               move_stp = 1;
+               step = true;
                break;
        case SWM_ARG_ID_MOVEDOWN:
                Y(win) += (SWM_MOVE_STEPS - border_width);
-               move_stp = 1;
+               step = true;
                break;
        default:
                break;
        }
-       if (move_stp) {
+       if (step) {
                regionize(win, -1, -1);
                region_containment(win, win->ws->r, SWM_CW_ALLSIDES);
                update_window(win);
@@ -6384,21 +7233,40 @@ move(struct ws_win *win, union arg *args)
                return;
        }
 
+       /* Release keyboard freeze if called via keybind. */
+       if (bp->type == KEYBIND)
+               xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD,
+                    XCB_CURRENT_TIME);
+
        regionize(win, qpr->root_x, qpr->root_y);
        region_containment(win, win->ws->r, SWM_CW_ALLSIDES |
            SWM_CW_SOFTBOUNDARY);
        update_window(win);
        xcb_flush(conn);
-       moving = 1;
-       while (moving && (evt = xcb_wait_for_event(conn))) {
+       moving = true;
+       while (moving && (evt = get_next_event(true))) {
                switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
                case XCB_BUTTON_RELEASE:
-                       DNPRINTF(SWM_D_EVENT, "move: BUTTON_RELEASE\n");
-                       moving = 0;
+                       if (bp->type == BTNBIND && bp->value ==
+                           ((xcb_button_release_event_t *)evt)->detail)
+                               moving = false;
+
+                       xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER,
+                           ((xcb_button_release_event_t *)evt)->time);
+                       xcb_flush(conn);
+                       break;
+               case XCB_KEY_RELEASE:
+                       if (keybindreleased(bp, (xcb_key_release_event_t *)evt))
+                               moving = false;
+
+                       xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD,
+                           ((xcb_key_release_event_t *)evt)->time);
+                       xcb_flush(conn);
                        break;
                case XCB_MOTION_NOTIFY:
                        mne = (xcb_motion_notify_event_t *)evt;
-                       DNPRINTF(SWM_D_EVENT, "motion: root: %#x\n", mne->root);
+                       DNPRINTF(SWM_D_EVENT, "move: MOTION_NOTIFY: "
+                           "root: %#x\n", mne->root);
                        X(win) = mne->root_x - qpr->win_x - border_width;
                        Y(win) = mne->root_y - qpr->win_y - border_width;
 
@@ -6412,6 +7280,12 @@ move(struct ws_win *win, union arg *args)
                                xcb_flush(conn);
                        }
                        break;
+               case XCB_BUTTON_PRESS:
+                       /* Thaw and ignore. */
+                       xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER,
+                           ((xcb_button_press_event_t *)evt)->time);
+                       xcb_flush(conn);
+                       break;
                case XCB_KEY_PRESS:
                        /* Ignore. */
                        xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD,
@@ -6421,7 +7295,7 @@ move(struct ws_win *win, union arg *args)
                default:
                        event_handle(evt);
 
-                       /* It's possible for win to have been freed above. */
+                       /* It's possible for win to have been freed. */
                        if (validate_win(win)) {
                                DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
                                goto out;
@@ -6439,8 +7313,8 @@ move(struct ws_win *win, union arg *args)
        store_float_geom(win);
 
        /* New region set to fullscreen layout. */
-       if (win->ws->cur_layout == &layouts[SWM_MAX_STACK]) {
-               stack();
+       if (win->ws->r && win->ws->cur_layout == &layouts[SWM_MAX_STACK]) {
+               stack(win->ws->r);
                focus_flush();
        }
 
@@ -6451,198 +7325,192 @@ out:
 }
 
 void
-move_step(struct swm_region *r, union arg *args)
+move(struct binding *bp, struct swm_region *r, union arg *args)
 {
-       struct ws_win           *win = NULL;
+       struct ws_win                   *win = NULL;
+       xcb_query_pointer_reply_t       *qpr = NULL;
 
-       if (r && r->ws && r->ws->focus)
-               win = r->ws->focus;
-       else
+       if (r == NULL)
                return;
 
-       if (!TRANS(win) && !ABOVE(win))
-               return;
+       if (args->id) {
+               /* move_* uses focus window. */
+               if (r->ws)
+                       win = r->ws->focus;
 
-       move(win, args);
-       focus_flush();
-}
+               /* Disallow move_ on tiled. */
+               if (win && !(TRANS(win) || ABOVE(win)))
+                       return;
+       } else {
+               /* move uses window under pointer. */
+               qpr = xcb_query_pointer_reply(conn,
+                   xcb_query_pointer(conn, r->s->root), NULL);
+               if (qpr)
+                       win = find_window(qpr->child);
+       }
 
-/* key definitions */
-struct keyfunc {
-       char                    name[SWM_FUNCNAME_LEN];
-       void                    (*func)(struct swm_region *r, union arg *);
-       union arg               args;
-} keyfuncs[KF_INVALID + 1] = {
-       /* name                 function        argument */
-       { "bar_toggle",         bar_toggle,     {.id = SWM_ARG_ID_BAR_TOGGLE} },
-       { "bar_toggle_ws",      bar_toggle,     {.id = SWM_ARG_ID_BAR_TOGGLE_WS} },
-       { "button2",            pressbutton,    {2} },
-       { "cycle_layout",       cycle_layout,   {0} },
-       { "flip_layout",        stack_config,   {.id = SWM_ARG_ID_FLIPLAYOUT} },
-       { "float_toggle",       floating_toggle,{0} },
-       { "focus_main",         focus,          {.id = SWM_ARG_ID_FOCUSMAIN} },
-       { "focus_next",         focus,          {.id = SWM_ARG_ID_FOCUSNEXT} },
-       { "focus_prev",         focus,          {.id = SWM_ARG_ID_FOCUSPREV} },
-       { "focus_urgent",       focus,          {.id = SWM_ARG_ID_FOCUSURGENT} },
-       { "maximize_toggle",    maximize_toggle,{0} },
-       { "height_grow",        resize_step,    {.id = SWM_ARG_ID_HEIGHTGROW} },
-       { "height_shrink",      resize_step,    {.id = SWM_ARG_ID_HEIGHTSHRINK} },
-       { "iconify",            iconify,        {0} },
-       { "master_shrink",      stack_config,   {.id = SWM_ARG_ID_MASTERSHRINK} },
-       { "master_grow",        stack_config,   {.id = SWM_ARG_ID_MASTERGROW} },
-       { "master_add",         stack_config,   {.id = SWM_ARG_ID_MASTERADD} },
-       { "master_del",         stack_config,   {.id = SWM_ARG_ID_MASTERDEL} },
-       { "move_down",          move_step,      {.id = SWM_ARG_ID_MOVEDOWN} },
-       { "move_left",          move_step,      {.id = SWM_ARG_ID_MOVELEFT} },
-       { "move_right",         move_step,      {.id = SWM_ARG_ID_MOVERIGHT} },
-       { "move_up",            move_step,      {.id = SWM_ARG_ID_MOVEUP} },
-       { "mvrg_1",             send_to_rg,     {.id = 0} },
-       { "mvrg_2",             send_to_rg,     {.id = 1} },
-       { "mvrg_3",             send_to_rg,     {.id = 2} },
-       { "mvrg_4",             send_to_rg,     {.id = 3} },
-       { "mvrg_5",             send_to_rg,     {.id = 4} },
-       { "mvrg_6",             send_to_rg,     {.id = 5} },
-       { "mvrg_7",             send_to_rg,     {.id = 6} },
-       { "mvrg_8",             send_to_rg,     {.id = 7} },
-       { "mvrg_9",             send_to_rg,     {.id = 8} },
-       { "mvws_1",             send_to_ws,     {.id = 0} },
-       { "mvws_2",             send_to_ws,     {.id = 1} },
-       { "mvws_3",             send_to_ws,     {.id = 2} },
-       { "mvws_4",             send_to_ws,     {.id = 3} },
-       { "mvws_5",             send_to_ws,     {.id = 4} },
-       { "mvws_6",             send_to_ws,     {.id = 5} },
-       { "mvws_7",             send_to_ws,     {.id = 6} },
-       { "mvws_8",             send_to_ws,     {.id = 7} },
-       { "mvws_9",             send_to_ws,     {.id = 8} },
-       { "mvws_10",            send_to_ws,     {.id = 9} },
-       { "mvws_11",            send_to_ws,     {.id = 10} },
-       { "mvws_12",            send_to_ws,     {.id = 11} },
-       { "mvws_13",            send_to_ws,     {.id = 12} },
-       { "mvws_14",            send_to_ws,     {.id = 13} },
-       { "mvws_15",            send_to_ws,     {.id = 14} },
-       { "mvws_16",            send_to_ws,     {.id = 15} },
-       { "mvws_17",            send_to_ws,     {.id = 16} },
-       { "mvws_18",            send_to_ws,     {.id = 17} },
-       { "mvws_19",            send_to_ws,     {.id = 18} },
-       { "mvws_20",            send_to_ws,     {.id = 19} },
-       { "mvws_21",            send_to_ws,     {.id = 20} },
-       { "mvws_22",            send_to_ws,     {.id = 21} },
-       { "name_workspace",     name_workspace, {0} },
-       { "quit",               quit,           {0} },
-       { "raise_toggle",       raise_toggle,   {0} },
-       { "restart",            restart,        {0} },
-       { "rg_1",               focusrg,        {.id = 0} },
-       { "rg_2",               focusrg,        {.id = 1} },
-       { "rg_3",               focusrg,        {.id = 2} },
-       { "rg_4",               focusrg,        {.id = 3} },
-       { "rg_5",               focusrg,        {.id = 4} },
-       { "rg_6",               focusrg,        {.id = 5} },
-       { "rg_7",               focusrg,        {.id = 6} },
-       { "rg_8",               focusrg,        {.id = 7} },
-       { "rg_9",               focusrg,        {.id = 8} },
-       { "rg_next",            cyclerg,        {.id = SWM_ARG_ID_CYCLERG_UP} },
-       { "rg_prev",            cyclerg,        {.id = SWM_ARG_ID_CYCLERG_DOWN} },
-       { "screen_next",        cyclerg,        {.id = SWM_ARG_ID_CYCLERG_UP} },
-       { "screen_prev",        cyclerg,        {.id = SWM_ARG_ID_CYCLERG_DOWN} },
-       { "search_win",         search_win,     {0} },
-       { "search_workspace",   search_workspace,       {0} },
-       { "spawn_custom",       NULL,           {0} },
-       { "stack_inc",          stack_config,   {.id = SWM_ARG_ID_STACKINC} },
-       { "stack_dec",          stack_config,   {.id = SWM_ARG_ID_STACKDEC} },
-       { "stack_reset",        stack_config,   {.id = SWM_ARG_ID_STACKRESET} },
-       { "swap_main",          swapwin,        {.id = SWM_ARG_ID_SWAPMAIN} },
-       { "swap_next",          swapwin,        {.id = SWM_ARG_ID_SWAPNEXT} },
-       { "swap_prev",          swapwin,        {.id = SWM_ARG_ID_SWAPPREV} },
-       { "uniconify",          uniconify,      {0} },
-       { "version",            version,        {0} },
-       { "width_grow",         resize_step,    {.id = SWM_ARG_ID_WIDTHGROW} },
-       { "width_shrink",       resize_step,    {.id = SWM_ARG_ID_WIDTHSHRINK} },
-       { "wind_del",           wkill,          {.id = SWM_ARG_ID_DELETEWINDOW} },
-       { "wind_kill",          wkill,          {.id = SWM_ARG_ID_KILLWINDOW} },
-       { "ws_1",               switchws,       {.id = 0} },
-       { "ws_2",               switchws,       {.id = 1} },
-       { "ws_3",               switchws,       {.id = 2} },
-       { "ws_4",               switchws,       {.id = 3} },
-       { "ws_5",               switchws,       {.id = 4} },
-       { "ws_6",               switchws,       {.id = 5} },
-       { "ws_7",               switchws,       {.id = 6} },
-       { "ws_8",               switchws,       {.id = 7} },
-       { "ws_9",               switchws,       {.id = 8} },
-       { "ws_10",              switchws,       {.id = 9} },
-       { "ws_11",              switchws,       {.id = 10} },
-       { "ws_12",              switchws,       {.id = 11} },
-       { "ws_13",              switchws,       {.id = 12} },
-       { "ws_14",              switchws,       {.id = 13} },
-       { "ws_15",              switchws,       {.id = 14} },
-       { "ws_16",              switchws,       {.id = 15} },
-       { "ws_17",              switchws,       {.id = 16} },
-       { "ws_18",              switchws,       {.id = 17} },
-       { "ws_19",              switchws,       {.id = 18} },
-       { "ws_20",              switchws,       {.id = 19} },
-       { "ws_21",              switchws,       {.id = 20} },
-       { "ws_22",              switchws,       {.id = 21} },
-       { "ws_next",            cyclews,        {.id = SWM_ARG_ID_CYCLEWS_UP} },
-       { "ws_next_all",        cyclews,        {.id = SWM_ARG_ID_CYCLEWS_UP_ALL} },
-       { "ws_next_move",       cyclews,        {.id = SWM_ARG_ID_CYCLEWS_MOVE_UP} },
-       { "ws_prev",            cyclews,        {.id = SWM_ARG_ID_CYCLEWS_DOWN} },
-       { "ws_prev_all",        cyclews,        {.id = SWM_ARG_ID_CYCLEWS_DOWN_ALL} },
-       { "ws_prev_move",       cyclews,        {.id = SWM_ARG_ID_CYCLEWS_MOVE_DOWN} },
-       { "ws_prior",           priorws,        {0} },
-       { "dumpwins",           dumpwins,       {0} }, /* MUST BE LAST */
-       { "invalid key func",   NULL,           {0} },
-};
+       if (win == NULL)
+               return;
 
-int
-key_cmp(struct key *kp1, struct key *kp2)
-{
-       if (kp1->keysym < kp2->keysym)
-               return (-1);
-       if (kp1->keysym > kp2->keysym)
-               return (1);
+       move_win(win, bp, args->id);
 
-       if (kp1->mod < kp2->mod)
-               return (-1);
-       if (kp1->mod > kp2->mod)
-               return (1);
+       if (args->id && bp->type == KEYBIND)
+               center_pointer(r);
 
-       return (0);
+       focus_flush();
 }
 
-/* mouse */
-enum { client_click, root_click };
-struct button {
-       unsigned int            action;
-       unsigned int            mask;
-       unsigned int            button;
-       void                    (*func)(struct ws_win *, union arg *);
+/* action definitions */
+struct action {
+       char                    name[SWM_FUNCNAME_LEN];
+       void                    (*func)(struct binding *, struct swm_region *,
+                                   union arg *);
+       uint32_t                flags;
        union arg               args;
-} buttons[] = {
-#define MODKEY_SHIFT   MODKEY | XCB_MOD_MASK_SHIFT
-         /* action     key             mouse button    func    args */
-       { client_click, MODKEY,         XCB_BUTTON_INDEX_3,     resize, {.id = SWM_ARG_ID_DONTCENTER} },
-       { client_click, MODKEY_SHIFT,   XCB_BUTTON_INDEX_3,     resize, {.id = SWM_ARG_ID_CENTER} },
-       { client_click, MODKEY,         XCB_BUTTON_INDEX_1,     move,   {0} },
-#undef MODKEY_SHIFT
+} actions[FN_INVALID + 2] = {
+       /* name                 function        argument */
+       { "bar_toggle",         bar_toggle,     0, {.id = SWM_ARG_ID_BAR_TOGGLE} },
+       { "bar_toggle_ws",      bar_toggle,     0, {.id = SWM_ARG_ID_BAR_TOGGLE_WS} },
+       { "button2",            pressbutton,    0, {.id = 2} },
+       { "cycle_layout",       cycle_layout,   0, {0} },
+       { "flip_layout",        stack_config,   0, {.id = SWM_ARG_ID_FLIPLAYOUT} },
+       { "float_toggle",       floating_toggle,0, {0} },
+       { "focus",              focus_pointer,  0, {0} },
+       { "focus_main",         focus,          0, {.id = SWM_ARG_ID_FOCUSMAIN} },
+       { "focus_next",         focus,          0, {.id = SWM_ARG_ID_FOCUSNEXT} },
+       { "focus_prev",         focus,          0, {.id = SWM_ARG_ID_FOCUSPREV} },
+       { "focus_urgent",       focus,          0, {.id = SWM_ARG_ID_FOCUSURGENT} },
+       { "fullscreen_toggle",  fullscreen_toggle, 0, {0} },
+       { "maximize_toggle",    maximize_toggle,0, {0} },
+       { "height_grow",        resize,         0, {.id = SWM_ARG_ID_HEIGHTGROW} },
+       { "height_shrink",      resize,         0, {.id = SWM_ARG_ID_HEIGHTSHRINK} },
+       { "iconify",            iconify,        0, {0} },
+       { "master_shrink",      stack_config,   0, {.id = SWM_ARG_ID_MASTERSHRINK} },
+       { "master_grow",        stack_config,   0, {.id = SWM_ARG_ID_MASTERGROW} },
+       { "master_add",         stack_config,   0, {.id = SWM_ARG_ID_MASTERADD} },
+       { "master_del",         stack_config,   0, {.id = SWM_ARG_ID_MASTERDEL} },
+       { "move",               move,           FN_F_NOREPLAY, {0} },
+       { "move_down",          move,           0, {.id = SWM_ARG_ID_MOVEDOWN} },
+       { "move_left",          move,           0, {.id = SWM_ARG_ID_MOVELEFT} },
+       { "move_right",         move,           0, {.id = SWM_ARG_ID_MOVERIGHT} },
+       { "move_up",            move,           0, {.id = SWM_ARG_ID_MOVEUP} },
+       { "mvrg_1",             send_to_rg,     0, {.id = 0} },
+       { "mvrg_2",             send_to_rg,     0, {.id = 1} },
+       { "mvrg_3",             send_to_rg,     0, {.id = 2} },
+       { "mvrg_4",             send_to_rg,     0, {.id = 3} },
+       { "mvrg_5",             send_to_rg,     0, {.id = 4} },
+       { "mvrg_6",             send_to_rg,     0, {.id = 5} },
+       { "mvrg_7",             send_to_rg,     0, {.id = 6} },
+       { "mvrg_8",             send_to_rg,     0, {.id = 7} },
+       { "mvrg_9",             send_to_rg,     0, {.id = 8} },
+       { "mvrg_next",          send_to_rg_relative,    0, {.id = 1} },
+       { "mvrg_prev",          send_to_rg_relative,    0, {.id = -1} },
+       { "mvws_1",             send_to_ws,     0, {.id = 0} },
+       { "mvws_2",             send_to_ws,     0, {.id = 1} },
+       { "mvws_3",             send_to_ws,     0, {.id = 2} },
+       { "mvws_4",             send_to_ws,     0, {.id = 3} },
+       { "mvws_5",             send_to_ws,     0, {.id = 4} },
+       { "mvws_6",             send_to_ws,     0, {.id = 5} },
+       { "mvws_7",             send_to_ws,     0, {.id = 6} },
+       { "mvws_8",             send_to_ws,     0, {.id = 7} },
+       { "mvws_9",             send_to_ws,     0, {.id = 8} },
+       { "mvws_10",            send_to_ws,     0, {.id = 9} },
+       { "mvws_11",            send_to_ws,     0, {.id = 10} },
+       { "mvws_12",            send_to_ws,     0, {.id = 11} },
+       { "mvws_13",            send_to_ws,     0, {.id = 12} },
+       { "mvws_14",            send_to_ws,     0, {.id = 13} },
+       { "mvws_15",            send_to_ws,     0, {.id = 14} },
+       { "mvws_16",            send_to_ws,     0, {.id = 15} },
+       { "mvws_17",            send_to_ws,     0, {.id = 16} },
+       { "mvws_18",            send_to_ws,     0, {.id = 17} },
+       { "mvws_19",            send_to_ws,     0, {.id = 18} },
+       { "mvws_20",            send_to_ws,     0, {.id = 19} },
+       { "mvws_21",            send_to_ws,     0, {.id = 20} },
+       { "mvws_22",            send_to_ws,     0, {.id = 21} },
+       { "name_workspace",     name_workspace, 0, {0} },
+       { "quit",               quit,           0, {0} },
+       { "raise",              raise_focus,    0, {0} },
+       { "raise_toggle",       raise_toggle,   0, {0} },
+       { "resize",             resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_DONTCENTER} },
+       { "resize_centered",    resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_CENTER} },
+       { "restart",            restart,        0, {0} },
+       { "rg_1",               focusrg,        0, {.id = 0} },
+       { "rg_2",               focusrg,        0, {.id = 1} },
+       { "rg_3",               focusrg,        0, {.id = 2} },
+       { "rg_4",               focusrg,        0, {.id = 3} },
+       { "rg_5",               focusrg,        0, {.id = 4} },
+       { "rg_6",               focusrg,        0, {.id = 5} },
+       { "rg_7",               focusrg,        0, {.id = 6} },
+       { "rg_8",               focusrg,        0, {.id = 7} },
+       { "rg_9",               focusrg,        0, {.id = 8} },
+       { "rg_move_next",       cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_MOVE_UP} },
+       { "rg_move_prev",       cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_MOVE_DOWN} },
+       { "rg_next",            cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_UP} },
+       { "rg_prev",            cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_DOWN} },
+       { "screen_next",        cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_UP} },
+       { "screen_prev",        cyclerg,        0, {.id = SWM_ARG_ID_CYCLERG_DOWN} },
+       { "search_win",         search_win,     0, {0} },
+       { "search_workspace",   search_workspace,       0, {0} },
+       { "spawn_custom",       NULL,           0, {0} },
+       { "stack_balance",      stack_config,   0, {.id = SWM_ARG_ID_STACKBALANCE} },
+       { "stack_inc",          stack_config,   0, {.id = SWM_ARG_ID_STACKINC} },
+       { "stack_dec",          stack_config,   0, {.id = SWM_ARG_ID_STACKDEC} },
+       { "stack_reset",        stack_config,   0, {.id = SWM_ARG_ID_STACKRESET} },
+       { "swap_main",          swapwin,        0, {.id = SWM_ARG_ID_SWAPMAIN} },
+       { "swap_next",          swapwin,        0, {.id = SWM_ARG_ID_SWAPNEXT} },
+       { "swap_prev",          swapwin,        0, {.id = SWM_ARG_ID_SWAPPREV} },
+       { "uniconify",          uniconify,      0, {0} },
+       { "version",            version,        0, {0} },
+       { "width_grow",         resize,         0, {.id = SWM_ARG_ID_WIDTHGROW} },
+       { "width_shrink",       resize,         0, {.id = SWM_ARG_ID_WIDTHSHRINK} },
+       { "wind_del",           wkill,          0, {.id = SWM_ARG_ID_DELETEWINDOW} },
+       { "wind_kill",          wkill,          0, {.id = SWM_ARG_ID_KILLWINDOW} },
+       { "ws_1",               switchws,       0, {.id = 0} },
+       { "ws_2",               switchws,       0, {.id = 1} },
+       { "ws_3",               switchws,       0, {.id = 2} },
+       { "ws_4",               switchws,       0, {.id = 3} },
+       { "ws_5",               switchws,       0, {.id = 4} },
+       { "ws_6",               switchws,       0, {.id = 5} },
+       { "ws_7",               switchws,       0, {.id = 6} },
+       { "ws_8",               switchws,       0, {.id = 7} },
+       { "ws_9",               switchws,       0, {.id = 8} },
+       { "ws_10",              switchws,       0, {.id = 9} },
+       { "ws_11",              switchws,       0, {.id = 10} },
+       { "ws_12",              switchws,       0, {.id = 11} },
+       { "ws_13",              switchws,       0, {.id = 12} },
+       { "ws_14",              switchws,       0, {.id = 13} },
+       { "ws_15",              switchws,       0, {.id = 14} },
+       { "ws_16",              switchws,       0, {.id = 15} },
+       { "ws_17",              switchws,       0, {.id = 16} },
+       { "ws_18",              switchws,       0, {.id = 17} },
+       { "ws_19",              switchws,       0, {.id = 18} },
+       { "ws_20",              switchws,       0, {.id = 19} },
+       { "ws_21",              switchws,       0, {.id = 20} },
+       { "ws_22",              switchws,       0, {.id = 21} },
+       { "ws_next",            cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_UP} },
+       { "ws_next_all",        cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_UP_ALL} },
+       { "ws_next_move",       cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_MOVE_UP} },
+       { "ws_prev",            cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_DOWN} },
+       { "ws_prev_all",        cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_DOWN_ALL} },
+       { "ws_prev_move",       cyclews,        0, {.id = SWM_ARG_ID_CYCLEWS_MOVE_DOWN} },
+       { "ws_prior",           priorws,        0, {0} },
+       /* SWM_DEBUG actions MUST be here: */
+       { "debug_toggle",       debug_toggle,   0, {0} },
+       { "dumpwins",           dumpwins,       0, {0} },
+       /* ALWAYS last: */
+       { "invalid action",     NULL,           0, {0} },
 };
 
 void
-update_modkey(unsigned int mod)
+update_modkey(uint16_t mod)
 {
-       int                     i;
-       struct key              *kp;
+       struct binding          *bp;
 
+       /* Replace all instances of the old mod key. */
+       RB_FOREACH(bp, binding_tree, &bindings)
+               if (bp->mod & mod_key)
+                       bp->mod = (bp->mod & ~mod_key) | mod;
        mod_key = mod;
-       RB_FOREACH(kp, key_tree, &keys)
-               if (kp->mod & XCB_MOD_MASK_SHIFT)
-                       kp->mod = mod | XCB_MOD_MASK_SHIFT;
-               else
-                       kp->mod = mod;
-
-       for (i = 0; i < LENGTH(buttons); i++)
-               if (buttons[i].mask & XCB_MOD_MASK_SHIFT)
-                       buttons[i].mask = mod | XCB_MOD_MASK_SHIFT;
-               else
-                       buttons[i].mask = mod;
 }
 
 int
@@ -6758,7 +7626,7 @@ spawn_custom(struct swm_region *r, union arg *args, const char *spawn_name)
                return;
        a.argv = real_args;
        if (fork() == 0)
-               spawn(r->ws->idx, &a, 1);
+               spawn(r->ws->idx, &a, true);
 
        for (i = 0; i < spawn_argc; i++)
                free(real_args[i]);
@@ -6795,7 +7663,7 @@ spawn_select(struct swm_region *r, union arg *args, const char *spawn_name,
                        err(1, "dup2");
                close(select_list_pipe[1]);
                close(select_resp_pipe[0]);
-               spawn(r->ws->idx, &a, 0);
+               spawn(r->ws->idx, &a, false);
                break;
        default: /* parent */
                close(select_list_pipe[0]);
@@ -6811,8 +7679,8 @@ spawn_select(struct swm_region *r, union arg *args, const char *spawn_name,
 /* Argument tokenizer. */
 char *
 argsep(char **sp) {
-       int                     single_quoted = 0, double_quoted = 0;
        char                    *arg, *cp, *next;
+       bool                    single_quoted = false, double_quoted = false;
 
        if (*sp == NULL)
                return NULL;
@@ -6911,6 +7779,16 @@ spawn_remove(struct spawn_prog *sp)
        DNPRINTF(SWM_D_SPAWN, "spawn_remove: leave\n");
 }
 
+void
+clear_spawns(void)
+{
+       struct spawn_prog       *sp;
+
+       while ((sp = TAILQ_FIRST(&spawns)) != NULL) {
+               spawn_remove(sp);
+       }
+}
+
 struct spawn_prog*
 spawn_find(const char *name)
 {
@@ -6967,18 +7845,17 @@ setconfspawn(const char *selector, const char *value, int flags)
 void
 validate_spawns(void)
 {
+       struct binding          *bp;
        struct spawn_prog       *sp;
        char                    which[PATH_MAX];
        size_t                  i;
 
-       struct key              *kp;
-
-       RB_FOREACH(kp, key_tree, &keys) {
-               if (kp->funcid != KF_SPAWN_CUSTOM)
+       RB_FOREACH(bp, binding_tree, &bindings) {
+               if (bp->action != FN_SPAWN_CUSTOM)
                        continue;
 
                /* find program */
-               sp = spawn_find(kp->spawn_name);
+               sp = spawn_find(bp->spawn_name);
                if (sp == NULL || sp->flags & SWM_SPAWN_OPTIONAL)
                        continue;
 
@@ -7037,63 +7914,86 @@ setup_spawn(void)
        setconfspawn("initscr",         "initscreen.sh",        SWM_SPAWN_OPTIONAL);
 }
 
-/* key bindings */
+/* bindings */
 #define SWM_MODNAME_SIZE       32
 #define SWM_KEY_WS             "\n+ \t"
 int
-parsekeys(const char *keystr, unsigned int currmod, unsigned int *mod, KeySym *ks)
+parsebinding(const char *bindstr, uint16_t *mod, enum binding_type *type,
+    uint32_t *val, uint32_t *flags)
 {
        char                    *str, *cp, *name;
-       KeySym                  uks;
+       KeySym                  ks, lks, uks;
 
-       DNPRINTF(SWM_D_KEY, "parsekeys: enter [%s]\n", keystr);
-       if (mod == NULL || ks == NULL) {
-               DNPRINTF(SWM_D_KEY, "parsekeys: no mod or key vars\n");
+       DNPRINTF(SWM_D_KEY, "parsebinding: enter [%s]\n", bindstr);
+       if (mod == NULL || val == NULL) {
+               DNPRINTF(SWM_D_KEY, "parsebinding: no mod or key vars\n");
                return (1);
        }
-       if (keystr == NULL || strlen(keystr) == 0) {
-               DNPRINTF(SWM_D_KEY, "parsekeys: no keystr\n");
+       if (bindstr == NULL || strlen(bindstr) == 0) {
+               DNPRINTF(SWM_D_KEY, "parsebinding: no bindstr\n");
                return (1);
        }
 
-       if ((cp = str = strdup(keystr)) == NULL)
-               err(1, "parsekeys: strdup");
+       if ((cp = str = strdup(bindstr)) == NULL)
+               err(1, "parsebinding: strdup");
 
-       *ks = XCB_NO_SYMBOL;
+       *val = XCB_NO_SYMBOL;
        *mod = 0;
+       *flags = 0;
+       *type = KEYBIND;
        while ((name = strsep(&cp, SWM_KEY_WS)) != NULL) {
-               DNPRINTF(SWM_D_KEY, "parsekeys: key [%s]\n", name);
+               DNPRINTF(SWM_D_KEY, "parsebinding: entry [%s]\n", name);
                if (cp)
                        cp += (long)strspn(cp, SWM_KEY_WS);
                if (strncasecmp(name, "MOD", SWM_MODNAME_SIZE) == 0)
-                       *mod |= currmod;
+                       *mod |= mod_key;
                else if (strncasecmp(name, "Mod1", SWM_MODNAME_SIZE) == 0)
                        *mod |= XCB_MOD_MASK_1;
                else if (strncasecmp(name, "Mod2", SWM_MODNAME_SIZE) == 0)
-                       *mod += XCB_MOD_MASK_2;
+                       *mod |= XCB_MOD_MASK_2;
                else if (strncmp(name, "Mod3", SWM_MODNAME_SIZE) == 0)
                        *mod |= XCB_MOD_MASK_3;
                else if (strncmp(name, "Mod4", SWM_MODNAME_SIZE) == 0)
                        *mod |= XCB_MOD_MASK_4;
+               else if (strncmp(name, "Mod5", SWM_MODNAME_SIZE) == 0)
+                       *mod |= XCB_MOD_MASK_5;
                else if (strncasecmp(name, "SHIFT", SWM_MODNAME_SIZE) == 0)
                        *mod |= XCB_MOD_MASK_SHIFT;
                else if (strncasecmp(name, "CONTROL", SWM_MODNAME_SIZE) == 0)
                        *mod |= XCB_MOD_MASK_CONTROL;
-               else {
-                       *ks = XStringToKeysym(name);
-                       XConvertCase(*ks, ks, &uks);
-                       if (ks == XCB_NO_SYMBOL) {
+               else if (strncasecmp(name, "ANYMOD", SWM_MODNAME_SIZE) == 0)
+                       *mod |= XCB_MOD_MASK_ANY;
+               else if (strncasecmp(name, "REPLAY", SWM_MODNAME_SIZE) == 0)
+                       *flags |= BINDING_F_REPLAY;
+               else if (sscanf(name, "Button%u", val) == 1) {
+                       DNPRINTF(SWM_D_KEY, "parsebinding: button %u\n", *val);
+                       *type = BTNBIND;
+                       if (*val > 255 || *val == 0) {
+                               DNPRINTF(SWM_D_KEY,
+                                   "parsebinding: invalid button %u\n", *val);
+                               return (1);
+                       }
+               } else {
+                       /* TODO: do this without Xlib. */
+                       ks = XStringToKeysym(name);
+                       if (ks == NoSymbol) {
                                DNPRINTF(SWM_D_KEY,
-                                   "parsekeys: invalid key %s\n",
-                                   name);
+                                   "parsebinding: invalid key %s\n", name);
                                free(str);
                                return (1);
                        }
+
+                       XConvertCase(ks, &lks, &uks);
+                       *val = (uint32_t)lks;
                }
        }
 
+       /* If ANYMOD was specified, ignore the rest. */
+       if (*mod & XCB_MOD_MASK_ANY)
+               *mod = XCB_MOD_MASK_ANY;
+
        free(str);
-       DNPRINTF(SWM_D_KEY, "parsekeys: leave ok\n");
+       DNPRINTF(SWM_D_KEY, "parsebinding: leave ok\n");
        return (0);
 }
 
@@ -7106,97 +8006,104 @@ strdupsafe(const char *str)
                return (strdup(str));
 }
 
-void
-key_insert(unsigned int mod, KeySym ks, enum keyfuncid kfid,
-    const char *spawn_name)
+int
+binding_cmp(struct binding *bp1, struct binding *bp2)
 {
-       struct key              *kp;
-
-       DNPRINTF(SWM_D_KEY, "key_insert: enter %s [%s]\n",
-           keyfuncs[kfid].name, spawn_name);
+       if (bp1->type < bp2->type)
+               return (-1);
+       if (bp1->type > bp2->type)
+               return (1);
 
-       if ((kp = malloc(sizeof *kp)) == NULL)
-               err(1, "key_insert: malloc");
+       if (bp1->value < bp2->value)
+               return (-1);
+       if (bp1->value > bp2->value)
+               return (1);
 
-       kp->mod = mod;
-       kp->keysym = ks;
-       kp->funcid = kfid;
-       kp->spawn_name = strdupsafe(spawn_name);
-       RB_INSERT(key_tree, &keys, kp);
+       if (bp1->mod < bp2->mod)
+               return (-1);
+       if (bp1->mod > bp2->mod)
+               return (1);
 
-       DNPRINTF(SWM_D_KEY, "key_insert: leave\n");
+       return (0);
 }
 
-struct key *
-key_lookup(unsigned int mod, KeySym ks)
+void
+binding_insert(uint16_t mod, enum binding_type type, uint32_t val,
+    enum actionid aid, uint32_t flags, const char *spawn_name)
 {
-       struct key              kp;
+       struct binding          *bp;
+
+       DNPRINTF(SWM_D_KEY, "binding_insert: mod: %u, type: %d, val: %u, "
+           "action: %s(%d), spawn_name: %s\n", mod, type, val,
+           actions[aid].name, aid, spawn_name);
+
+       if ((bp = malloc(sizeof *bp)) == NULL)
+               err(1, "binding_insert: malloc");
 
-       kp.keysym = ks;
-       kp.mod = mod;
+       bp->mod = mod;
+       bp->type = type;
+       bp->value = val;
+       bp->action = aid;
+       bp->flags = flags;
+       bp->spawn_name = strdupsafe(spawn_name);
+       RB_INSERT(binding_tree, &bindings, bp);
 
-       return (RB_FIND(key_tree, &keys, &kp));
+       DNPRINTF(SWM_D_KEY, "binding_insert: leave\n");
 }
 
-void
-key_remove(struct key *kp)
+struct binding *
+binding_lookup(uint16_t mod, enum binding_type type, uint32_t val)
 {
-       DNPRINTF(SWM_D_KEY, "key_remove: %s\n", keyfuncs[kp->funcid].name);
+       struct binding          bp;
 
-       RB_REMOVE(key_tree, &keys, kp);
-       free(kp->spawn_name);
-       free(kp);
+       bp.mod = mod;
+       bp.type = type;
+       bp.value = val;
 
-       DNPRINTF(SWM_D_KEY, "key_remove: leave\n");
+       return (RB_FIND(binding_tree, &bindings, &bp));
 }
 
 void
-key_replace(struct key *kp, unsigned int mod, KeySym ks, enum keyfuncid kfid,
-    const char *spawn_name)
+binding_remove(struct binding *bp)
 {
-       DNPRINTF(SWM_D_KEY, "key_replace: %s [%s]\n", keyfuncs[kp->funcid].name,
-           spawn_name);
+       DNPRINTF(SWM_D_KEY, "binding_remove: mod: %u, type: %d, val: %u, "
+           "action: %s(%d), spawn_name: %s\n", bp->mod, bp->type, bp->value,
+           actions[bp->action].name, bp->action, bp->spawn_name);
 
-       key_remove(kp);
-       key_insert(mod, ks, kfid, spawn_name);
+       RB_REMOVE(binding_tree, &bindings, bp);
+       free(bp->spawn_name);
+       free(bp);
 
-       DNPRINTF(SWM_D_KEY, "key_replace: leave\n");
+       DNPRINTF(SWM_D_KEY, "binding_remove: leave\n");
 }
 
 void
-setkeybinding(unsigned int mod, KeySym ks, enum keyfuncid kfid,
-    const char *spawn_name)
+setbinding(uint16_t mod, enum binding_type type, uint32_t val,
+    enum actionid aid, uint32_t flags, const char *spawn_name)
 {
-       struct key              *kp;
+       struct binding          *bp;
 
-       DNPRINTF(SWM_D_KEY, "setkeybinding: enter %s [%s]\n",
-           keyfuncs[kfid].name, spawn_name);
+       DNPRINTF(SWM_D_KEY, "setbinding: enter %s [%s]\n",
+           actions[aid].name, spawn_name);
 
-       if ((kp = key_lookup(mod, ks)) != NULL) {
-               if (kfid == KF_INVALID)
-                       key_remove(kp);
-               else
-                       key_replace(kp, mod, ks, kfid, spawn_name);
-               DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
-               return;
-       }
-       if (kfid == KF_INVALID) {
-               warnx("bind: Key combination already unbound.");
-               DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
-               return;
-       }
+       /* Unbind any existing. Loop is to handle MOD_MASK_ANY. */
+       while ((bp = binding_lookup(mod, type, val)))
+               binding_remove(bp);
 
-       key_insert(mod, ks, kfid, spawn_name);
-       DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
+       if (aid != FN_INVALID)
+               binding_insert(mod, type, val, aid, flags, spawn_name);
+
+       DNPRINTF(SWM_D_KEY, "setbinding: leave\n");
 }
 
 int
 setconfbinding(const char *selector, const char *value, int flags)
 {
-       enum keyfuncid          kfid;
-       unsigned int            mod;
-       KeySym                  ks;
        struct spawn_prog       *sp;
+       uint32_t                keybtn, opts;
+       uint16_t                mod;
+       enum actionid           aid;
+       enum binding_type       type;
 
        /* suppress unused warning since var is needed */
        (void)flags;
@@ -7205,21 +8112,21 @@ setconfbinding(const char *selector, const char *value, int flags)
            "value: [%s]\n", selector, value);
        if (selector == NULL || strlen(selector) == 0) {
                DNPRINTF(SWM_D_KEY, "setconfbinding: unbind %s\n", value);
-               if (parsekeys(value, mod_key, &mod, &ks) == 0) {
-                       kfid = KF_INVALID;
-                       setkeybinding(mod, ks, kfid, NULL);
+               if (parsebinding(value, &mod, &type, &keybtn, &opts) == 0) {
+                       setbinding(mod, type, keybtn, FN_INVALID, opts, NULL);
                        return (0);
                } else
                        return (1);
        }
        /* search by key function name */
-       for (kfid = 0; kfid < KF_INVALID; (kfid)++) {
-               if (strncasecmp(selector, keyfuncs[kfid].name,
+       for (aid = 0; aid < FN_INVALID; aid++) {
+               if (strncasecmp(selector, actions[aid].name,
                    SWM_FUNCNAME_LEN) == 0) {
                        DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
-                           "keyfunc\n", selector);
-                       if (parsekeys(value, mod_key, &mod, &ks) == 0) {
-                               setkeybinding(mod, ks, kfid, NULL);
+                           "action\n", selector);
+                       if (parsebinding(value, &mod, &type, &keybtn,
+                           &opts) == 0) {
+                               setbinding(mod, type, keybtn, aid, opts, NULL);
                                return (0);
                        } else
                                return (1);
@@ -7229,8 +8136,8 @@ setconfbinding(const char *selector, const char *value, int flags)
        if ((sp = spawn_find(selector)) != NULL) {
                DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
                    "spawn\n", selector);
-               if (parsekeys(value, mod_key, &mod, &ks) == 0) {
-                       setkeybinding(mod, ks, KF_SPAWN_CUSTOM,
+               if (parsebinding(value, &mod, &type, &keybtn, &opts) == 0) {
+                       setbinding(mod, type, keybtn, FN_SPAWN_CUSTOM, opts,
                            sp->name);
                        return (0);
                } else
@@ -7240,143 +8147,173 @@ setconfbinding(const char *selector, const char *value, int flags)
        return (1);
 }
 
-void
-setup_keys(void)
-{
-#define MODKEY_SHIFT   MODKEY | XCB_MOD_MASK_SHIFT
-       setkeybinding(MODKEY,           XK_b,           KF_BAR_TOGGLE,  NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_b,           KF_BAR_TOGGLE_WS,NULL);
-       setkeybinding(MODKEY,           XK_v,           KF_BUTTON2,     NULL);
-       setkeybinding(MODKEY,           XK_space,       KF_CYCLE_LAYOUT,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_backslash,   KF_FLIP_LAYOUT, NULL);
-       setkeybinding(MODKEY,           XK_t,           KF_FLOAT_TOGGLE,NULL);
-       setkeybinding(MODKEY,           XK_m,           KF_FOCUS_MAIN,  NULL);
-       setkeybinding(MODKEY,           XK_j,           KF_FOCUS_NEXT,  NULL);
-       setkeybinding(MODKEY,           XK_Tab,         KF_FOCUS_NEXT,  NULL);
-       setkeybinding(MODKEY,           XK_k,           KF_FOCUS_PREV,  NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_Tab,         KF_FOCUS_PREV,  NULL);
-       setkeybinding(MODKEY,           XK_u,           KF_FOCUS_URGENT,NULL);
-       setkeybinding(MODKEY,           XK_e,           KF_MAXIMIZE_TOGGLE,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_equal,       KF_HEIGHT_GROW,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_minus,       KF_HEIGHT_SHRINK,NULL);
-       setkeybinding(MODKEY,           XK_w,           KF_ICONIFY,     NULL);
-       setkeybinding(MODKEY,           XK_h,           KF_MASTER_SHRINK, NULL);
-       setkeybinding(MODKEY,           XK_l,           KF_MASTER_GROW, NULL);
-       setkeybinding(MODKEY,           XK_comma,       KF_MASTER_ADD,  NULL);
-       setkeybinding(MODKEY,           XK_period,      KF_MASTER_DEL,  NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_bracketright,KF_MOVE_DOWN,NULL);
-       setkeybinding(MODKEY,           XK_bracketleft, KF_MOVE_LEFT,NULL);
-       setkeybinding(MODKEY,           XK_bracketright,KF_MOVE_RIGHT,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_bracketleft, KF_MOVE_UP,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_End,      KF_MVRG_1,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Down,     KF_MVRG_2,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Next,     KF_MVRG_3,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Left,     KF_MVRG_4,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Begin,    KF_MVRG_5,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Right,    KF_MVRG_6,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Home,     KF_MVRG_7,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Up,       KF_MVRG_8,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_KP_Prior,    KF_MVRG_9,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_1,           KF_MVWS_1,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_2,           KF_MVWS_2,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_3,           KF_MVWS_3,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_4,           KF_MVWS_4,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_5,           KF_MVWS_5,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_6,           KF_MVWS_6,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_7,           KF_MVWS_7,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_8,           KF_MVWS_8,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_9,           KF_MVWS_9,      NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_0,           KF_MVWS_10,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F1,          KF_MVWS_11,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F2,          KF_MVWS_12,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F3,          KF_MVWS_13,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F4,          KF_MVWS_14,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F5,          KF_MVWS_15,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F6,          KF_MVWS_16,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F7,          KF_MVWS_17,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F8,          KF_MVWS_18,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F9,          KF_MVWS_19,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F10,         KF_MVWS_20,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F11,         KF_MVWS_21,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_F12,         KF_MVWS_22,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_slash,       KF_NAME_WORKSPACE,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_q,           KF_QUIT,        NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_r,           KF_RAISE_TOGGLE,NULL);
-       setkeybinding(MODKEY,           XK_q,           KF_RESTART,     NULL);
-       setkeybinding(MODKEY,           XK_KP_End,      KF_RG_1,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Down,     KF_RG_2,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Next,     KF_RG_3,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Left,     KF_RG_4,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Begin,    KF_RG_5,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Right,    KF_RG_6,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Home,     KF_RG_7,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Up,       KF_RG_8,        NULL);
-       setkeybinding(MODKEY,           XK_KP_Prior,    KF_RG_9,        NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_Right,       KF_RG_NEXT,     NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_Left,        KF_RG_PREV,     NULL);
-       setkeybinding(MODKEY,           XK_f,           KF_SEARCH_WIN,  NULL);
-       setkeybinding(MODKEY,           XK_slash,       KF_SEARCH_WORKSPACE,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_i,           KF_SPAWN_CUSTOM,"initscr");
-       setkeybinding(MODKEY_SHIFT,     XK_Delete,      KF_SPAWN_CUSTOM,"lock");
-       setkeybinding(MODKEY,           XK_p,           KF_SPAWN_CUSTOM,"menu");
-       setkeybinding(MODKEY,           XK_s,           KF_SPAWN_CUSTOM,"screenshot_all");
-       setkeybinding(MODKEY_SHIFT,     XK_s,           KF_SPAWN_CUSTOM,"screenshot_wind");
-       setkeybinding(MODKEY_SHIFT,     XK_Return,      KF_SPAWN_CUSTOM,"term");
-       setkeybinding(MODKEY_SHIFT,     XK_comma,       KF_STACK_INC,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_period,      KF_STACK_DEC,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_space,       KF_STACK_RESET, NULL);
-       setkeybinding(MODKEY,           XK_Return,      KF_SWAP_MAIN,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_j,           KF_SWAP_NEXT,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_k,           KF_SWAP_PREV,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_w,           KF_UNICONIFY,   NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_v,           KF_VERSION,     NULL);
-       setkeybinding(MODKEY,           XK_equal,       KF_WIDTH_GROW,  NULL);
-       setkeybinding(MODKEY,           XK_minus,       KF_WIDTH_SHRINK,NULL);
-       setkeybinding(MODKEY,           XK_x,           KF_WIND_DEL,    NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_x,           KF_WIND_KILL,   NULL);
-       setkeybinding(MODKEY,           XK_1,           KF_WS_1,        NULL);
-       setkeybinding(MODKEY,           XK_2,           KF_WS_2,        NULL);
-       setkeybinding(MODKEY,           XK_3,           KF_WS_3,        NULL);
-       setkeybinding(MODKEY,           XK_4,           KF_WS_4,        NULL);
-       setkeybinding(MODKEY,           XK_5,           KF_WS_5,        NULL);
-       setkeybinding(MODKEY,           XK_6,           KF_WS_6,        NULL);
-       setkeybinding(MODKEY,           XK_7,           KF_WS_7,        NULL);
-       setkeybinding(MODKEY,           XK_8,           KF_WS_8,        NULL);
-       setkeybinding(MODKEY,           XK_9,           KF_WS_9,        NULL);
-       setkeybinding(MODKEY,           XK_0,           KF_WS_10,       NULL);
-       setkeybinding(MODKEY,           XK_F1,          KF_WS_11,       NULL);
-       setkeybinding(MODKEY,           XK_F2,          KF_WS_12,       NULL);
-       setkeybinding(MODKEY,           XK_F3,          KF_WS_13,       NULL);
-       setkeybinding(MODKEY,           XK_F4,          KF_WS_14,       NULL);
-       setkeybinding(MODKEY,           XK_F5,          KF_WS_15,       NULL);
-       setkeybinding(MODKEY,           XK_F6,          KF_WS_16,       NULL);
-       setkeybinding(MODKEY,           XK_F7,          KF_WS_17,       NULL);
-       setkeybinding(MODKEY,           XK_F8,          KF_WS_18,       NULL);
-       setkeybinding(MODKEY,           XK_F9,          KF_WS_19,       NULL);
-       setkeybinding(MODKEY,           XK_F10,         KF_WS_20,       NULL);
-       setkeybinding(MODKEY,           XK_F11,         KF_WS_21,       NULL);
-       setkeybinding(MODKEY,           XK_F12,         KF_WS_22,       NULL);
-       setkeybinding(MODKEY,           XK_Right,       KF_WS_NEXT,     NULL);
-       setkeybinding(MODKEY,           XK_Left,        KF_WS_PREV,     NULL);
-       setkeybinding(MODKEY,           XK_Up,          KF_WS_NEXT_ALL, NULL);
-       setkeybinding(MODKEY,           XK_Down,        KF_WS_PREV_ALL, NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_Up,          KF_WS_NEXT_MOVE,NULL);
-       setkeybinding(MODKEY_SHIFT,     XK_Down,        KF_WS_PREV_MOVE,NULL);
-       setkeybinding(MODKEY,           XK_a,           KF_WS_PRIOR,    NULL);
+#define MODSHIFT       MODKEY | XCB_MOD_MASK_SHIFT
+void
+setup_keybindings(void)
+{
+#define BINDKEY(m, k, a)       setbinding(m, KEYBIND, k, a, 0, NULL)
+#define BINDKEYSPAWN(m, k, s)  setbinding(m, KEYBIND, k, FN_SPAWN_CUSTOM, 0, s)
+       BINDKEY(MODKEY,         XK_b,                   FN_BAR_TOGGLE);
+       BINDKEY(MODSHIFT,       XK_b,                   FN_BAR_TOGGLE_WS);
+       BINDKEY(MODKEY,         XK_b,                   FN_BAR_TOGGLE);
+       BINDKEY(MODSHIFT,       XK_b,                   FN_BAR_TOGGLE_WS);
+       BINDKEY(MODKEY,         XK_v,                   FN_BUTTON2);
+       BINDKEY(MODKEY,         XK_space,               FN_CYCLE_LAYOUT);
+       BINDKEY(MODSHIFT,       XK_backslash,           FN_FLIP_LAYOUT);
+       BINDKEY(MODKEY,         XK_t,                   FN_FLOAT_TOGGLE);
+       BINDKEY(MODKEY,         XK_m,                   FN_FOCUS_MAIN);
+       BINDKEY(MODKEY,         XK_j,                   FN_FOCUS_NEXT);
+       BINDKEY(MODKEY,         XK_Tab,                 FN_FOCUS_NEXT);
+       BINDKEY(MODKEY,         XK_k,                   FN_FOCUS_PREV);
+       BINDKEY(MODSHIFT,       XK_Tab,                 FN_FOCUS_PREV);
+       BINDKEY(MODKEY,         XK_u,                   FN_FOCUS_URGENT);
+       BINDKEY(MODSHIFT,       XK_e,                   FN_FULLSCREEN_TOGGLE);
+       BINDKEY(MODKEY,         XK_e,                   FN_MAXIMIZE_TOGGLE);
+       BINDKEY(MODSHIFT,       XK_equal,               FN_HEIGHT_GROW);
+       BINDKEY(MODSHIFT,       XK_minus,               FN_HEIGHT_SHRINK);
+       BINDKEY(MODKEY,         XK_w,                   FN_ICONIFY);
+       BINDKEY(MODKEY,         XK_h,                   FN_MASTER_SHRINK);
+       BINDKEY(MODKEY,         XK_l,                   FN_MASTER_GROW);
+       BINDKEY(MODKEY,         XK_comma,               FN_MASTER_ADD);
+       BINDKEY(MODKEY,         XK_period,              FN_MASTER_DEL);
+       BINDKEY(MODSHIFT,       XK_bracketright,        FN_MOVE_DOWN);
+       BINDKEY(MODKEY,         XK_bracketleft,         FN_MOVE_LEFT);
+       BINDKEY(MODKEY,         XK_bracketright,        FN_MOVE_RIGHT);
+       BINDKEY(MODSHIFT,       XK_bracketleft,         FN_MOVE_UP);
+       BINDKEY(MODSHIFT,       XK_KP_End,              FN_MVRG_1);
+       BINDKEY(MODSHIFT,       XK_KP_Down,             FN_MVRG_2);
+       BINDKEY(MODSHIFT,       XK_KP_Next,             FN_MVRG_3);
+       BINDKEY(MODSHIFT,       XK_KP_Left,             FN_MVRG_4);
+       BINDKEY(MODSHIFT,       XK_KP_Begin,            FN_MVRG_5);
+       BINDKEY(MODSHIFT,       XK_KP_Right,            FN_MVRG_6);
+       BINDKEY(MODSHIFT,       XK_KP_Home,             FN_MVRG_7);
+       BINDKEY(MODSHIFT,       XK_KP_Up,               FN_MVRG_8);
+       BINDKEY(MODSHIFT,       XK_KP_Prior,            FN_MVRG_9);
+       BINDKEY(MODSHIFT,       XK_1,                   FN_MVWS_1);
+       BINDKEY(MODSHIFT,       XK_2,                   FN_MVWS_2);
+       BINDKEY(MODSHIFT,       XK_3,                   FN_MVWS_3);
+       BINDKEY(MODSHIFT,       XK_4,                   FN_MVWS_4);
+       BINDKEY(MODSHIFT,       XK_5,                   FN_MVWS_5);
+       BINDKEY(MODSHIFT,       XK_6,                   FN_MVWS_6);
+       BINDKEY(MODSHIFT,       XK_7,                   FN_MVWS_7);
+       BINDKEY(MODSHIFT,       XK_8,                   FN_MVWS_8);
+       BINDKEY(MODSHIFT,       XK_9,                   FN_MVWS_9);
+       BINDKEY(MODSHIFT,       XK_0,                   FN_MVWS_10);
+       BINDKEY(MODSHIFT,       XK_F1,                  FN_MVWS_11);
+       BINDKEY(MODSHIFT,       XK_F2,                  FN_MVWS_12);
+       BINDKEY(MODSHIFT,       XK_F3,                  FN_MVWS_13);
+       BINDKEY(MODSHIFT,       XK_F4,                  FN_MVWS_14);
+       BINDKEY(MODSHIFT,       XK_F5,                  FN_MVWS_15);
+       BINDKEY(MODSHIFT,       XK_F6,                  FN_MVWS_16);
+       BINDKEY(MODSHIFT,       XK_F7,                  FN_MVWS_17);
+       BINDKEY(MODSHIFT,       XK_F8,                  FN_MVWS_18);
+       BINDKEY(MODSHIFT,       XK_F9,                  FN_MVWS_19);
+       BINDKEY(MODSHIFT,       XK_F10,                 FN_MVWS_20);
+       BINDKEY(MODSHIFT,       XK_F11,                 FN_MVWS_21);
+       BINDKEY(MODSHIFT,       XK_F12,                 FN_MVWS_22);
+       BINDKEY(MODSHIFT,       XK_slash,               FN_NAME_WORKSPACE);
+       BINDKEY(MODSHIFT,       XK_q,                   FN_QUIT);
+       BINDKEY(MODKEY,         XK_r,                   FN_RAISE);
+       BINDKEY(MODSHIFT,       XK_r,                   FN_RAISE_TOGGLE);
+       BINDKEY(MODKEY,         XK_q,                   FN_RESTART);
+       BINDKEY(MODKEY,         XK_KP_End,              FN_RG_1);
+       BINDKEY(MODKEY,         XK_KP_Down,             FN_RG_2);
+       BINDKEY(MODKEY,         XK_KP_Next,             FN_RG_3);
+       BINDKEY(MODKEY,         XK_KP_Left,             FN_RG_4);
+       BINDKEY(MODKEY,         XK_KP_Begin,            FN_RG_5);
+       BINDKEY(MODKEY,         XK_KP_Right,            FN_RG_6);
+       BINDKEY(MODKEY,         XK_KP_Home,             FN_RG_7);
+       BINDKEY(MODKEY,         XK_KP_Up,               FN_RG_8);
+       BINDKEY(MODKEY,         XK_KP_Prior,            FN_RG_9);
+       BINDKEY(MODSHIFT,       XK_Right,               FN_RG_NEXT);
+       BINDKEY(MODSHIFT,       XK_Left,                FN_RG_PREV);
+       BINDKEY(MODKEY,         XK_f,                   FN_SEARCH_WIN);
+       BINDKEY(MODKEY,         XK_slash,               FN_SEARCH_WORKSPACE);
+       BINDKEYSPAWN(MODSHIFT,  XK_i,                   "initscr");
+       BINDKEYSPAWN(MODSHIFT,  XK_Delete,              "lock");
+       BINDKEYSPAWN(MODKEY,    XK_p,                   "menu");
+       BINDKEYSPAWN(MODKEY,    XK_s,                   "screenshot_all");
+       BINDKEYSPAWN(MODSHIFT,  XK_s,                   "screenshot_wind");
+       BINDKEYSPAWN(MODSHIFT,  XK_Return,              "term");
+       BINDKEY(MODSHIFT,       XK_comma,               FN_STACK_INC);
+       BINDKEY(MODSHIFT,       XK_period,              FN_STACK_DEC);
+       BINDKEY(MODSHIFT,       XK_space,               FN_STACK_RESET);
+       BINDKEY(MODKEY,         XK_Return,              FN_SWAP_MAIN);
+       BINDKEY(MODSHIFT,       XK_j,                   FN_SWAP_NEXT);
+       BINDKEY(MODSHIFT,       XK_k,                   FN_SWAP_PREV);
+       BINDKEY(MODSHIFT,       XK_w,                   FN_UNICONIFY);
+       BINDKEY(MODSHIFT,       XK_v,                   FN_VERSION);
+       BINDKEY(MODKEY,         XK_equal,               FN_WIDTH_GROW);
+       BINDKEY(MODKEY,         XK_minus,               FN_WIDTH_SHRINK);
+       BINDKEY(MODKEY,         XK_x,                   FN_WIND_DEL);
+       BINDKEY(MODSHIFT,       XK_x,                   FN_WIND_KILL);
+       BINDKEY(MODKEY,         XK_1,                   FN_WS_1);
+       BINDKEY(MODKEY,         XK_2,                   FN_WS_2);
+       BINDKEY(MODKEY,         XK_3,                   FN_WS_3);
+       BINDKEY(MODKEY,         XK_4,                   FN_WS_4);
+       BINDKEY(MODKEY,         XK_5,                   FN_WS_5);
+       BINDKEY(MODKEY,         XK_6,                   FN_WS_6);
+       BINDKEY(MODKEY,         XK_7,                   FN_WS_7);
+       BINDKEY(MODKEY,         XK_8,                   FN_WS_8);
+       BINDKEY(MODKEY,         XK_9,                   FN_WS_9);
+       BINDKEY(MODKEY,         XK_0,                   FN_WS_10);
+       BINDKEY(MODKEY,         XK_F1,                  FN_WS_11);
+       BINDKEY(MODKEY,         XK_F2,                  FN_WS_12);
+       BINDKEY(MODKEY,         XK_F3,                  FN_WS_13);
+       BINDKEY(MODKEY,         XK_F4,                  FN_WS_14);
+       BINDKEY(MODKEY,         XK_F5,                  FN_WS_15);
+       BINDKEY(MODKEY,         XK_F6,                  FN_WS_16);
+       BINDKEY(MODKEY,         XK_F7,                  FN_WS_17);
+       BINDKEY(MODKEY,         XK_F8,                  FN_WS_18);
+       BINDKEY(MODKEY,         XK_F9,                  FN_WS_19);
+       BINDKEY(MODKEY,         XK_F10,                 FN_WS_20);
+       BINDKEY(MODKEY,         XK_F11,                 FN_WS_21);
+       BINDKEY(MODKEY,         XK_F12,                 FN_WS_22);
+       BINDKEY(MODKEY,         XK_Right,               FN_WS_NEXT);
+       BINDKEY(MODKEY,         XK_Left,                FN_WS_PREV);
+       BINDKEY(MODKEY,         XK_Up,                  FN_WS_NEXT_ALL);
+       BINDKEY(MODKEY,         XK_Down,                FN_WS_PREV_ALL);
+       BINDKEY(MODSHIFT,       XK_Up,                  FN_WS_NEXT_MOVE);
+       BINDKEY(MODSHIFT,       XK_Down,                FN_WS_PREV_MOVE);
+       BINDKEY(MODKEY,         XK_a,                   FN_WS_PRIOR);
 #ifdef SWM_DEBUG
-       setkeybinding(MODKEY_SHIFT,     XK_d,           KF_DUMPWINS,    NULL);
+       BINDKEY(MODKEY,         XK_d,                   FN_DEBUG_TOGGLE);
+       BINDKEY(MODSHIFT,       XK_d,                   FN_DUMPWINS);
 #endif
-#undef MODKEY_SHIFT
+#undef BINDKEY
+#undef BINDKEYSPAWN
 }
 
 void
-clear_keys(void)
+setup_btnbindings(void)
 {
-       struct key              *kp;
+       setbinding(ANYMOD, BTNBIND, XCB_BUTTON_INDEX_1, FN_FOCUS,
+           BINDING_F_REPLAY, NULL);
+       setbinding(MODKEY, BTNBIND, XCB_BUTTON_INDEX_3, FN_RESIZE, 0, NULL);
+       setbinding(MODSHIFT, BTNBIND, XCB_BUTTON_INDEX_3, FN_RESIZE_CENTERED, 0,
+           NULL);
+       setbinding(MODKEY, BTNBIND, XCB_BUTTON_INDEX_1, FN_MOVE, 0, NULL);
+}
+#undef MODSHIFT
+
+void
+clear_bindings(void)
+{
+       struct binding          *bp;
+
+       while ((bp = RB_ROOT(&bindings)))
+               binding_remove(bp);
+}
+
+void
+clear_keybindings(void)
+{
+       struct binding          *bp, *bptmp;
 
-       while (RB_EMPTY(&keys) == 0) {
-               kp = RB_ROOT(&keys);
-               key_remove(kp);
+       RB_FOREACH_SAFE(bp, binding_tree, &bindings, bptmp) {
+               if (bp->type != KEYBIND)
+                       continue;
+               binding_remove(bp);
        }
 }
 
@@ -7393,11 +8330,11 @@ setkeymapping(const char *selector, const char *value, int flags)
 
        keymapping_file = expand_tilde(value);
 
-       clear_keys();
+       clear_keybindings();
        /* load new key bindings; if it fails, revert to default bindings */
        if (conf_load(keymapping_file, SWM_CONF_KEYMAPPING)) {
-               clear_keys();
-               setup_keys();
+               clear_keybindings();
+               setup_keybindings();
        }
 
        free(keymapping_file);
@@ -7441,9 +8378,9 @@ updatenumlockmask(void)
 void
 grabkeys(void)
 {
-       struct key              *kp;
+       struct binding          *bp;
        int                     num_screens, k, j;
-       unsigned int            modifiers[4];
+       uint16_t                modifiers[4];
        xcb_keycode_t           *code;
 
        DNPRINTF(SWM_D_MISC, "grabkeys\n");
@@ -7460,38 +8397,63 @@ grabkeys(void)
                        continue;
                xcb_ungrab_key(conn, XCB_GRAB_ANY, screens[k].root,
                        XCB_MOD_MASK_ANY);
-               RB_FOREACH(kp, key_tree, &keys) {
+               RB_FOREACH(bp, binding_tree, &bindings) {
+                       if (bp->type != KEYBIND)
+                               continue;
+
+                       /* If there is a catch-all, only bind that. */
+                       if ((binding_lookup(ANYMOD, KEYBIND, bp->value)) &&
+                           bp->mod != ANYMOD)
+                               continue;
+
                        /* Skip unused ws binds. */
-                       if ((int)kp->funcid > KF_WS_1 + workspace_limit - 1 &&
-                           kp->funcid <= KF_WS_22)
+                       if ((int)bp->action > FN_WS_1 + workspace_limit - 1 &&
+                           bp->action <= FN_WS_22)
                                continue;
 
                        /* Skip unused mvws binds. */
-                       if ((int)kp->funcid > KF_MVWS_1 + workspace_limit - 1 &&
-                           kp->funcid <= KF_MVWS_22)
+                       if ((int)bp->action > FN_MVWS_1 + workspace_limit - 1 &&
+                           bp->action <= FN_MVWS_22)
                                continue;
 
                        if ((code = xcb_key_symbols_get_keycode(syms,
-                                       kp->keysym))) {
-                               for (j = 0; j < LENGTH(modifiers); j++)
+                           bp->value)) == NULL)
+                               continue;
+
+                       if (bp->mod == XCB_MOD_MASK_ANY) {
+                               /* All modifiers are grabbed in one pass. */
+                               DNPRINTF(SWM_D_MOUSE, "grabkeys: grab, "
+                                   "key: %u, modifiers: %d\n", bp->value,
+                                   bp->mod);
+                               xcb_grab_key(conn, 1, screens[k].root,
+                                   bp->mod, *code, XCB_GRAB_MODE_ASYNC,
+                                   XCB_GRAB_MODE_SYNC);
+                       } else {
+                               /* Need to grab each modifier permutation. */
+                               for (j = 0; j < LENGTH(modifiers); j++) {
+                                       DNPRINTF(SWM_D_MOUSE, "grabkeys: grab, "
+                                           "key: %u, modifiers: %d\n",
+                                           bp->value, bp->mod | modifiers[j]);
                                        xcb_grab_key(conn, 1,
                                            screens[k].root,
-                                           kp->mod | modifiers[j],
-                                           *code, XCB_GRAB_MODE_SYNC,
+                                           bp->mod | modifiers[j],
+                                           *code, XCB_GRAB_MODE_ASYNC,
                                            XCB_GRAB_MODE_SYNC);
-                               free(code);
+                               }
                        }
+                       free(code);
                }
        }
 }
 
 void
-grabbuttons(struct ws_win *win)
+grabbuttons(void)
 {
-       unsigned int    modifiers[4];
-       int             i, j;
+       struct binding  *bp;
+       int             num_screens, i, k;
+       uint16_t        modifiers[4];
 
-       DNPRINTF(SWM_D_MOUSE, "grabbuttons: win %#x\n", win->id);
+       DNPRINTF(SWM_D_MOUSE, "grabbuttons\n");
        updatenumlockmask();
 
        modifiers[0] = 0;
@@ -7499,14 +8461,57 @@ grabbuttons(struct ws_win *win)
        modifiers[2] = XCB_MOD_MASK_LOCK;
        modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK;
 
-       for (i = 0; i < LENGTH(buttons); i++)
-               if (buttons[i].action == client_click)
-                       for (j = 0; j < LENGTH(modifiers); ++j)
-                               xcb_grab_button(conn, 0, win->id, BUTTONMASK,
-                                   XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC,
-                                   XCB_WINDOW_NONE, XCB_CURSOR_NONE,
-                                   buttons[i].button, buttons[i].mask |
-                                   modifiers[j]);
+       num_screens = get_screen_count();
+       for (k = 0; k < num_screens; k++) {
+               if (TAILQ_EMPTY(&screens[k].rl))
+                       continue;
+               xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, screens[k].root,
+                       XCB_MOD_MASK_ANY);
+               RB_FOREACH(bp, binding_tree, &bindings) {
+                       if (bp->type != BTNBIND)
+                               continue;
+
+                       /* If there is a catch-all, only bind that. */
+                       if ((binding_lookup(ANYMOD, BTNBIND, bp->value)) &&
+                           bp->mod != ANYMOD)
+                               continue;
+
+                       /* Skip unused ws binds. */
+                       if ((int)bp->action > FN_WS_1 + workspace_limit - 1 &&
+                           bp->action <= FN_WS_22)
+                               continue;
+
+                       /* Skip unused mvws binds. */
+                       if ((int)bp->action > FN_MVWS_1 + workspace_limit - 1 &&
+                           bp->action <= FN_MVWS_22)
+                               continue;
+
+                       if (bp->mod == XCB_MOD_MASK_ANY) {
+                               /* All modifiers are grabbed in one pass. */
+                               DNPRINTF(SWM_D_MOUSE, "grabbuttons: grab, "
+                                   "button: %u, modifiers: %d\n", bp->value,
+                                   bp->mod);
+                               xcb_grab_button(conn, 0, screens[k].root,
+                                   BUTTONMASK, XCB_GRAB_MODE_SYNC,
+                                   XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
+                                   XCB_CURSOR_NONE, bp->value, bp->mod);
+                       } else {
+                               /* Need to grab each modifier permutation. */
+                               for (i = 0; i < LENGTH(modifiers); ++i) {
+                                       DNPRINTF(SWM_D_MOUSE, "grabbuttons: "
+                                           "grab, button: %u, modifiers: %u\n",
+                                           bp->value, bp->mod | modifiers[i]);
+                                       xcb_grab_button(conn, 0,
+                                           screens[k].root, BUTTONMASK,
+                                           XCB_GRAB_MODE_SYNC,
+                                           XCB_GRAB_MODE_ASYNC,
+                                           XCB_WINDOW_NONE,
+                                           XCB_CURSOR_NONE, bp->value,
+                                           bp->mod | modifiers[i]);
+                               }
+                       }
+               }
+       }
 }
 
 const char *quirkname[] = {
@@ -7519,12 +8524,17 @@ const char *quirkname[] = {
        "FOCUSPREV",
        "NOFOCUSONMAP",
        "FOCUSONMAP_SINGLE",
+       "OBEYAPPFOCUSREQ",
+       "IGNOREPID",
+       "IGNORESPAWNWS",
+       "NOFOCUSCYCLE",
+       "MINIMALBORDER",
 };
 
-/* SWM_Q_WS: retain '|' for back compat for now (2009-08-11) */
-#define SWM_Q_WS               "\n|+ \t"
+/* SWM_Q_DELIM: retain '|' for back compat for now (2009-08-11) */
+#define SWM_Q_DELIM            "\n|+ \t"
 int
-parsequirks(const char *qstr, unsigned long *quirk)
+parsequirks(const char *qstr, uint32_t *quirk, int *ws)
 {
        char                    *str, *cp, *name;
        int                     i;
@@ -7532,14 +8542,20 @@ parsequirks(const char *qstr, unsigned long *quirk)
        if (quirk == NULL || qstr == NULL)
                return (1);
 
-       if ((str = strdup(qstr)) == NULL)
+       if ((cp = str = strdup(qstr)) == NULL)
                err(1, "parsequirks: strdup");
 
-       cp = str;
        *quirk = 0;
-       while ((name = strsep(&cp, SWM_Q_WS)) != NULL) {
+       while ((name = strsep(&cp, SWM_Q_DELIM)) != NULL) {
                if (cp)
-                       cp += (long)strspn(cp, SWM_Q_WS);
+                       cp += (long)strspn(cp, SWM_Q_DELIM);
+
+               if (sscanf(name, "WS[%d]", ws) == 1) {
+                       if (*ws > 0)
+                               *ws -= 1;
+                       continue;
+               }
+
                for (i = 0; i < LENGTH(quirkname); i++) {
                        if (strncasecmp(name, quirkname[i],
                            SWM_QUIRK_LEN) == 0) {
@@ -7568,14 +8584,14 @@ parsequirks(const char *qstr, unsigned long *quirk)
 
 void
 quirk_insert(const char *class, const char *instance, const char *name,
-    unsigned long quirk)
+    uint32_t quirk, int ws)
 {
        struct quirk            *qp;
-       int                     failed = 0;
        char                    *str;
+       bool                    failed = false;
 
        DNPRINTF(SWM_D_QUIRK, "quirk_insert: class: %s, instance: %s, name: %s,"
-           " value: %lu\n", class, instance, name, quirk);
+           " value: %u, ws: %d\n", class, instance, name, quirk, ws);
 
        if ((qp = malloc(sizeof *qp)) == NULL)
                err(1, "quirk_insert: malloc");
@@ -7592,7 +8608,7 @@ quirk_insert(const char *class, const char *instance, const char *name,
        if (regcomp(&qp->regex_class, str, REG_EXTENDED | REG_NOSUB)) {
                add_startup_exception("regex failed to compile quirk 'class' "
                    "field: %s", class);
-               failed = 1;
+               failed = true;
        }
        DNPRINTF(SWM_D_QUIRK, "quirk_insert: compiled: %s\n", str);
        free(str);
@@ -7602,7 +8618,7 @@ quirk_insert(const char *class, const char *instance, const char *name,
        if (regcomp(&qp->regex_instance, str, REG_EXTENDED | REG_NOSUB)) {
                add_startup_exception("regex failed to compile quirk 'instance'"
                    " field: %s", instance);
-               failed = 1;
+               failed = true;
        }
        DNPRINTF(SWM_D_QUIRK, "quirk_insert: compiled: %s\n", str);
        free(str);
@@ -7612,7 +8628,7 @@ quirk_insert(const char *class, const char *instance, const char *name,
        if (regcomp(&qp->regex_name, str, REG_EXTENDED | REG_NOSUB)) {
                add_startup_exception("regex failed to compile quirk 'name' "
                    "field: %s", name);
-               failed = 1;
+               failed = true;
        }
        DNPRINTF(SWM_D_QUIRK, "quirk_insert: compiled: %s\n", str);
        free(str);
@@ -7622,6 +8638,7 @@ quirk_insert(const char *class, const char *instance, const char *name,
                quirk_free(qp);
        } else {
                qp->quirk = quirk;
+               qp->ws = ws;
                TAILQ_INSERT_TAIL(&quirks, qp, entry);
        }
        DNPRINTF(SWM_D_QUIRK, "quirk_insert: leave\n");
@@ -7630,7 +8647,7 @@ quirk_insert(const char *class, const char *instance, const char *name,
 void
 quirk_remove(struct quirk *qp)
 {
-       DNPRINTF(SWM_D_QUIRK, "quirk_remove: %s:%s [%lu]\n", qp->class,
+       DNPRINTF(SWM_D_QUIRK, "quirk_remove: %s:%s [%u]\n", qp->class,
            qp->name, qp->quirk);
 
        TAILQ_REMOVE(&quirks, qp, entry);
@@ -7651,45 +8668,56 @@ quirk_free(struct quirk *qp)
        free(qp);
 }
 
+void
+clear_quirks(void)
+{
+       struct quirk            *qp;
+
+       while ((qp = TAILQ_FIRST(&quirks)) != NULL) {
+               quirk_remove(qp);
+       }
+}
+
 void
 quirk_replace(struct quirk *qp, const char *class, const char *instance,
-    const char *name, unsigned long quirk)
+    const char *name, uint32_t quirk, int ws)
 {
-       DNPRINTF(SWM_D_QUIRK, "quirk_replace: %s:%s:%s [%lu]\n", qp->class,
-           qp->instance, qp->name, qp->quirk);
+       DNPRINTF(SWM_D_QUIRK, "quirk_replace: %s:%s:%s [%u], ws: %d\n", qp->class,
+           qp->instance, qp->name, qp->quirk, qp->ws);
 
        quirk_remove(qp);
-       quirk_insert(class, instance, name, quirk);
+       quirk_insert(class, instance, name, quirk, ws);
 
        DNPRINTF(SWM_D_QUIRK, "quirk_replace: leave\n");
 }
 
 void
 setquirk(const char *class, const char *instance, const char *name,
-    unsigned long quirk)
+    uint32_t quirk, int ws)
 {
        struct quirk            *qp;
 
-       DNPRINTF(SWM_D_QUIRK, "setquirk: enter %s:%s:%s [%lu]\n", class,
-           instance, name, quirk);
+       DNPRINTF(SWM_D_QUIRK, "setquirk: enter %s:%s:%s [%u], ws: %d\n", class,
+           instance, name, quirk, ws);
 
        /* Remove/replace existing quirk. */
        TAILQ_FOREACH(qp, &quirks, entry) {
                if (strcmp(qp->class, class) == 0 &&
                    strcmp(qp->instance, instance) == 0 &&
                    strcmp(qp->name, name) == 0) {
-                       if (quirk == 0)
+                       if (quirk == 0 && ws == -1)
                                quirk_remove(qp);
                        else
-                               quirk_replace(qp, class, instance, name, quirk);
+                               quirk_replace(qp, class, instance, name, quirk,
+                                   ws);
                        DNPRINTF(SWM_D_QUIRK, "setquirk: leave\n");
                        return;
                }
        }
 
-       /* Only insert if quirk is not NONE. */
-       if (quirk)
-               quirk_insert(class, instance, name, quirk);
+       /* Only insert if quirk is not NONE or forced ws is set. */
+       if (quirk || ws != -1)
+               quirk_insert(class, instance, name, quirk, ws);
 
        DNPRINTF(SWM_D_QUIRK, "setquirk: leave\n");
 }
@@ -7715,8 +8743,8 @@ setconfquirk(const char *selector, const char *value, int flags)
 {
        char                    *str, *cp, *class;
        char                    *instance = NULL, *name = NULL;
-       int                     retval, count = 0;
-       unsigned long           qrks;
+       int                     retval, count = 0, ws = -1;
+       uint32_t                qrks;
 
        /* suppress unused warning since var is needed */
        (void)flags;
@@ -7759,8 +8787,8 @@ setconfquirk(const char *selector, const char *value, int flags)
        DNPRINTF(SWM_D_CONF, "setconfquirk: class: %s, instance: %s, "
            "name: %s\n", class, instance, name);
 
-       if ((retval = parsequirks(value, &qrks)) == 0)
-               setquirk(class, instance, name, qrks);
+       if ((retval = parsequirks(value, &qrks, &ws)) == 0)
+               setquirk(class, instance, name, qrks, ws);
 
        free(str);
        return (retval);
@@ -7769,19 +8797,32 @@ setconfquirk(const char *selector, const char *value, int flags)
 void
 setup_quirks(void)
 {
-       setquirk("MPlayer",             "xv",           ".*",   SWM_Q_FLOAT | SWM_Q_FULLSCREEN | SWM_Q_FOCUSPREV);
-       setquirk("OpenOffice.org 3.2",  "VCLSalFrame",  ".*",   SWM_Q_FLOAT);
-       setquirk("Firefox-bin",         "firefox-bin",  ".*",   SWM_Q_TRANSSZ);
-       setquirk("Firefox",             "Dialog",       ".*",   SWM_Q_FLOAT);
-       setquirk("Gimp",                "gimp",         ".*",   SWM_Q_FLOAT | SWM_Q_ANYWHERE);
-       setquirk("XTerm",               "xterm",        ".*",   SWM_Q_XTERM_FONTADJ);
-       setquirk("xine",                "Xine Window",  ".*",   SWM_Q_FLOAT | SWM_Q_ANYWHERE);
-       setquirk("Xitk",                "Xitk Combo",   ".*",   SWM_Q_FLOAT | SWM_Q_ANYWHERE);
-       setquirk("xine",                "xine Panel",   ".*",   SWM_Q_FLOAT | SWM_Q_ANYWHERE);
-       setquirk("Xitk",                "Xine Window",  ".*",   SWM_Q_FLOAT | SWM_Q_ANYWHERE);
-       setquirk("xine",                "xine Video Fullscreen Window", ".*",   SWM_Q_FULLSCREEN | SWM_Q_FLOAT);
-       setquirk("pcb",                 "pcb",          ".*",   SWM_Q_FLOAT);
-       setquirk("SDL_App",             "SDL_App",      ".*",   SWM_Q_FLOAT | SWM_Q_FULLSCREEN);
+       setquirk("MPlayer",             "xv",           ".*",
+           SWM_Q_FLOAT | SWM_Q_FULLSCREEN | SWM_Q_FOCUSPREV, -1);
+       setquirk("OpenOffice.org 3.2",  "VCLSalFrame",  ".*",
+           SWM_Q_FLOAT, -1);
+       setquirk("Firefox-bin",         "firefox-bin",  ".*",
+           SWM_Q_TRANSSZ, -1);
+       setquirk("Firefox",             "Dialog",       ".*",
+           SWM_Q_FLOAT, -1);
+       setquirk("Gimp",                "gimp",         ".*",
+           SWM_Q_FLOAT | SWM_Q_ANYWHERE, -1);
+       setquirk("XTerm",               "xterm",        ".*",
+           SWM_Q_XTERM_FONTADJ, -1);
+       setquirk("xine",                "Xine Window",  ".*",
+           SWM_Q_FLOAT | SWM_Q_ANYWHERE, -1);
+       setquirk("Xitk",                "Xitk Combo",   ".*",
+           SWM_Q_FLOAT | SWM_Q_ANYWHERE, -1);
+       setquirk("xine",                "xine Panel",   ".*",
+           SWM_Q_FLOAT | SWM_Q_ANYWHERE, -1);
+       setquirk("Xitk",                "Xine Window",  ".*",
+           SWM_Q_FLOAT | SWM_Q_ANYWHERE, -1);
+       setquirk("xine",                "xine Video Fullscreen Window", ".*",
+           SWM_Q_FULLSCREEN | SWM_Q_FLOAT, -1);
+       setquirk("pcb",                 "pcb",          ".*",
+           SWM_Q_FLOAT, -1);
+       setquirk("SDL_App",             "SDL_App",      ".*",
+           SWM_Q_FLOAT | SWM_Q_FULLSCREEN, -1);
 }
 
 /* conf file stuff */
@@ -7811,6 +8852,8 @@ enum {
        SWM_S_FOCUS_DEFAULT,
        SWM_S_FOCUS_MODE,
        SWM_S_ICONIC_ENABLED,
+       SWM_S_JAVA_WORKAROUND,
+       SWM_S_MAXIMIZE_HIDE_BAR,
        SWM_S_REGION_PADDING,
        SWM_S_SPAWN_ORDER,
        SWM_S_SPAWN_TERM,
@@ -7819,11 +8862,15 @@ enum {
        SWM_S_STACK_ENABLED,
        SWM_S_TERM_WIDTH,
        SWM_S_TILE_GAP,
+       SWM_S_URGENT_COLLAPSE,
        SWM_S_URGENT_ENABLED,
        SWM_S_VERBOSE_LAYOUT,
+       SWM_S_WARP_FOCUS,
+       SWM_S_WARP_POINTER,
        SWM_S_WINDOW_CLASS_ENABLED,
        SWM_S_WINDOW_INSTANCE_ENABLED,
        SWM_S_WINDOW_NAME_ENABLED,
+       SWM_S_WORKSPACE_CLAMP,
        SWM_S_WORKSPACE_LIMIT,
        SWM_S_WORKSPACE_NAME,
 };
@@ -7833,7 +8880,7 @@ setconfvalue(const char *selector, const char *value, int flags)
 {
        struct workspace        *ws;
        int                     i, ws_id, num_screens;
-       char                    *b, *str, s[1024];
+       char                    *b, *str, *sp, s[1024];
 
        switch (flags) {
        case SWM_S_BAR_ACTION:
@@ -7842,7 +8889,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                        err(1, "setconfvalue: bar_action");
                break;
        case SWM_S_BAR_AT_BOTTOM:
-               bar_at_bottom = atoi(value);
+               bar_at_bottom = (atoi(value) != 0);
                break;
        case SWM_S_BAR_BORDER_WIDTH:
                bar_border_width = atoi(value);
@@ -7853,7 +8900,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                /* No longer needed; leave to not break old conf files. */
                break;
        case SWM_S_BAR_ENABLED:
-               bar_enabled = atoi(value);
+               bar_enabled = (atoi(value) != 0);
                break;
        case SWM_S_BAR_ENABLED_WS:
                ws_id = atoi(selector) - 1;
@@ -7864,7 +8911,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                num_screens = get_screen_count();
                for (i = 0; i < num_screens; i++) {
                        ws = (struct workspace *)&screens[i].ws;
-                       ws[ws_id].bar_enabled = atoi(value);
+                       ws[ws_id].bar_enabled = (atoi(value) != 0);
                }
                break;
        case SWM_S_BAR_FONT:
@@ -7878,15 +8925,15 @@ setconfvalue(const char *selector, const char *value, int flags)
                if (!bar_font_legacy)
                        break;
 
-               if ((str = strdup(value)) == NULL)
+               if ((sp = str = strdup(value)) == NULL)
                        err(1, "setconfvalue: strdup");
 
                /* If there are any non-XLFD entries, switch to Xft mode. */
-               while ((b = strsep(&str, ",")) != NULL) {
+               while ((b = strsep(&sp, ",")) != NULL) {
                        if (*b == '\0')
                                continue;
                        if (!isxlfd(b)) {
-                               bar_font_legacy = 0;
+                               bar_font_legacy = false;
                                break;
                        }
                }
@@ -7919,7 +8966,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                        boundary_width = 0;
                break;
        case SWM_S_CLOCK_ENABLED:
-               clock_enabled = atoi(value);
+               clock_enabled = (atoi(value) != 0);
                break;
        case SWM_S_CLOCK_FORMAT:
 #ifndef SWM_DENY_CLOCK_FORMAT
@@ -7929,10 +8976,10 @@ setconfvalue(const char *selector, const char *value, int flags)
 #endif
                break;
        case SWM_S_CYCLE_EMPTY:
-               cycle_empty = atoi(value);
+               cycle_empty = (atoi(value) != 0);
                break;
        case SWM_S_CYCLE_VISIBLE:
-               cycle_visible = atoi(value);
+               cycle_visible = (atoi(value) != 0);
                break;
        case SWM_S_DIALOG_RATIO:
                dialog_ratio = atof(value);
@@ -7940,7 +8987,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                        dialog_ratio = .6;
                break;
        case SWM_S_DISABLE_BORDER:
-               disable_border = atoi(value);
+               disable_border = (atoi(value) != 0);
                break;
        case SWM_S_FOCUS_CLOSE:
                if (strcmp(value, "first") == 0)
@@ -7955,7 +9002,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                        errx(1, "focus_close");
                break;
        case SWM_S_FOCUS_CLOSE_WRAP:
-               focus_close_wrap = atoi(value);
+               focus_close_wrap = (atoi(value) != 0);
                break;
        case SWM_S_FOCUS_DEFAULT:
                if (strcmp(value, "last") == 0)
@@ -7977,7 +9024,13 @@ setconfvalue(const char *selector, const char *value, int flags)
                        errx(1, "focus_mode");
                break;
        case SWM_S_ICONIC_ENABLED:
-               iconic_enabled = atoi(value);
+               iconic_enabled = (atoi(value) != 0);
+               break;
+       case SWM_S_JAVA_WORKAROUND:
+               java_workaround = (atoi(value) != 0);
+               break;
+       case SWM_S_MAXIMIZE_HIDE_BAR:
+               maximize_hide_bar = atoi(value);
                break;
        case SWM_S_REGION_PADDING:
                region_padding = atoi(value);
@@ -8007,7 +9060,7 @@ setconfvalue(const char *selector, const char *value, int flags)
                /* No longer needed; leave to not break old conf files. */
                break;
        case SWM_S_STACK_ENABLED:
-               stack_enabled = atoi(value);
+               stack_enabled = (atoi(value) != 0);
                break;
        case SWM_S_TERM_WIDTH:
                term_width = atoi(value);
@@ -8017,11 +9070,14 @@ setconfvalue(const char *selector, const char *value, int flags)
        case SWM_S_TILE_GAP:
                tile_gap = atoi(value);
                break;
+       case SWM_S_URGENT_COLLAPSE:
+               urgent_collapse = (atoi(value) != 0);
+               break;
        case SWM_S_URGENT_ENABLED:
-               urgent_enabled = atoi(value);
+               urgent_enabled = (atoi(value) != 0);
                break;
        case SWM_S_VERBOSE_LAYOUT:
-               verbose_layout = atoi(value);
+               verbose_layout = (atoi(value) != 0);
                for (i = 0; layouts[i].l_stack != NULL; i++) {
                        if (verbose_layout)
                                layouts[i].l_string = fancy_stacker;
@@ -8029,14 +9085,23 @@ setconfvalue(const char *selector, const char *value, int flags)
                                layouts[i].l_string = plain_stacker;
                }
                break;
+       case SWM_S_WARP_FOCUS:
+               warp_focus = (atoi(value) != 0);
+               break;
+       case SWM_S_WARP_POINTER:
+               warp_pointer = (atoi(value) != 0);
+               break;
        case SWM_S_WINDOW_CLASS_ENABLED:
-               window_class_enabled = atoi(value);
+               window_class_enabled = (atoi(value) != 0);
                break;
        case SWM_S_WINDOW_INSTANCE_ENABLED:
-               window_instance_enabled = atoi(value);
+               window_instance_enabled = (atoi(value) != 0);
                break;
        case SWM_S_WINDOW_NAME_ENABLED:
-               window_name_enabled = atoi(value);
+               window_name_enabled = (atoi(value) != 0);
+               break;
+       case SWM_S_WORKSPACE_CLAMP:
+               workspace_clamp = (atoi(value) != 0);
                break;
        case SWM_S_WORKSPACE_LIMIT:
                workspace_limit = atoi(value);
@@ -8094,6 +9159,8 @@ setconfmodkey(const char *selector, const char *value, int flags)
                update_modkey(XCB_MOD_MASK_3);
        else if (strncasecmp(value, "Mod4", strlen("Mod4")) == 0)
                update_modkey(XCB_MOD_MASK_4);
+       else if (strncasecmp(value, "Mod5", strlen("Mod5")) == 0)
+               update_modkey(XCB_MOD_MASK_5);
        else
                return (1);
        return (0);
@@ -8102,34 +9169,40 @@ setconfmodkey(const char *selector, const char *value, int flags)
 int
 setconfcolor(const char *selector, const char *value, int flags)
 {
-       int     sid, i, num_screens;
+       int     first, last, i = 0, num_screens;
 
-       sid = (selector == NULL || strlen(selector) == 0) ? -1 : atoi(selector);
+       num_screens = get_screen_count();
 
-       /*
-        * When setting focus/unfocus colors, we need to also
-        * set maximize colors to match if they haven't been customized.
-        */
-       i = sid < 0 ? 0 : sid;
-       if (flags == SWM_S_COLOR_FOCUS &&
-           !screens[i].c[SWM_S_COLOR_FOCUS_MAXIMIZED].manual)
-               setscreencolor(value, sid, SWM_S_COLOR_FOCUS_MAXIMIZED);
-       else if (flags == SWM_S_COLOR_UNFOCUS &&
-           !screens[i].c[SWM_S_COLOR_UNFOCUS_MAXIMIZED].manual)
-               setscreencolor(value, sid, SWM_S_COLOR_UNFOCUS_MAXIMIZED);
+       /* conf screen indices begin at 1; treat vals <= 0 as 'all screens.' */
+       if (selector == NULL || strlen(selector) == 0 ||
+           (last = atoi(selector) - 1) < 0) {
+               first = 0;
+               last = num_screens - 1;
+       } else {
+               first = last;
+       }
 
-       setscreencolor(value, sid, flags);
+       if (last >= num_screens) {
+               add_startup_exception("invalid screen index: %d out of bounds "
+                   "(maximum %d)", last + 1, num_screens);
+               return (1);
+       }
+
+       for (i = first; i <= last; ++i) {
+               setscreencolor(value, i, flags);
+
+               /*
+                * When setting focus/unfocus colors, we need to also
+                * set maximize colors to match if they haven't been customized.
+                */
+               if (flags == SWM_S_COLOR_FOCUS &&
+                   !screens[i].c[SWM_S_COLOR_FOCUS_MAXIMIZED].manual)
+                       setscreencolor(value, i, SWM_S_COLOR_FOCUS_MAXIMIZED);
+               else if (flags == SWM_S_COLOR_UNFOCUS &&
+                   !screens[i].c[SWM_S_COLOR_UNFOCUS_MAXIMIZED].manual)
+                       setscreencolor(value, i, SWM_S_COLOR_UNFOCUS_MAXIMIZED);
 
-       /* Track override of color. */
-       num_screens = get_screen_count();
-       if (sid > 0 && sid <= num_screens) {
                screens[i].c[flags].manual = 1;
-       } else if (sid == -1) {
-               for (i = 0; i < num_screens; ++i)
-                       screens[i].c[flags].manual = 1;
-       } else {
-               errx(1, "invalid screen index: %d out of bounds (maximum %d)",
-                   sid, num_screens);
        }
 
        return (0);
@@ -8151,7 +9224,7 @@ setautorun(const char *selector, const char *value, int flags)
 {
        int                     ws_id;
        char                    s[1024];
-       char                    *ap, *sp;
+       char                    *ap, *sp, *str;
        union arg               a;
        int                     argc = 0;
        pid_t                   pid;
@@ -8171,7 +9244,7 @@ setautorun(const char *selector, const char *value, int flags)
        if (ws_id < 0 || ws_id >= workspace_limit)
                errx(1, "autorun: invalid workspace %d", ws_id + 1);
 
-       sp = expand_tilde((char *)&s);
+       sp = str = expand_tilde((char *)&s);
 
        /*
         * This is a little intricate
@@ -8190,18 +9263,18 @@ setautorun(const char *selector, const char *value, int flags)
                        err(1, "setautorun: realloc");
                a.argv[argc - 1] = ap;
        }
-       free(sp);
 
        if ((a.argv = realloc(a.argv, (argc + 1) * sizeof(char *))) == NULL)
                err(1, "setautorun: realloc");
        a.argv[argc] = NULL;
 
        if ((pid = fork()) == 0) {
-               spawn(ws_id, &a, 1);
+               spawn(ws_id, &a, true);
                /* NOTREACHED */
                _exit(1);
        }
        free(a.argv);
+       free(str);
 
        /* parent */
        p = find_pid(pid);
@@ -8221,10 +9294,11 @@ setautorun(const char *selector, const char *value, int flags)
 int
 setlayout(const char *selector, const char *value, int flags)
 {
-       int                     ws_id, i, x, mg, ma, si, ar, f = 0;
+       struct workspace        *ws;
+       int                     ws_id, i, x, mg, ma, si, ar;
        int                     st = SWM_V_STACK, num_screens;
        char                    s[1024];
-       struct workspace        *ws;
+       bool                    f = false;
 
        /* suppress unused warnings since vars are needed */
        (void)selector;
@@ -8247,12 +9321,12 @@ setlayout(const char *selector, const char *value, int flags)
                st = SWM_V_STACK;
        else if (strcasecmp(s, "vertical_flip") == 0) {
                st = SWM_V_STACK;
-               f = 1;
+               f = true;
        } else if (strcasecmp(s, "horizontal") == 0)
                st = SWM_H_STACK;
        else if (strcasecmp(s, "horizontal_flip") == 0) {
                st = SWM_H_STACK;
-               f = 1;
+               f = true;
        } else if (strcasecmp(s, "fullscreen") == 0)
                st = SWM_MAX_STACK;
        else
@@ -8265,7 +9339,7 @@ setlayout(const char *selector, const char *value, int flags)
                ws = (struct workspace *)&screens[i].ws;
                ws[ws_id].cur_layout = &layouts[st];
 
-               ws[ws_id].always_raise = ar;
+               ws[ws_id].always_raise = (ar != 0);
                if (st == SWM_MAX_STACK)
                        continue;
 
@@ -8274,32 +9348,26 @@ setlayout(const char *selector, const char *value, int flags)
                        ws[ws_id].cur_layout->l_config(&ws[ws_id],
                            mg >= 0 ?  SWM_ARG_ID_MASTERGROW :
                            SWM_ARG_ID_MASTERSHRINK);
-                       stack();
                }
                /* master add */
                for (x = 0; x < abs(ma); x++) {
                        ws[ws_id].cur_layout->l_config(&ws[ws_id],
                            ma >= 0 ?  SWM_ARG_ID_MASTERADD :
                            SWM_ARG_ID_MASTERDEL);
-                       stack();
                }
                /* stack inc */
                for (x = 0; x < abs(si); x++) {
                        ws[ws_id].cur_layout->l_config(&ws[ws_id],
                            si >= 0 ?  SWM_ARG_ID_STACKINC :
                            SWM_ARG_ID_STACKDEC);
-                       stack();
                }
                /* Apply flip */
                if (f) {
                        ws[ws_id].cur_layout->l_config(&ws[ws_id],
                            SWM_ARG_ID_FLIPLAYOUT);
-                       stack();
                }
        }
 
-       focus_flush();
-
        return (0);
 }
 
@@ -8342,8 +9410,10 @@ struct config_option configopt[] = {
        { "focus_default",              setconfvalue,   SWM_S_FOCUS_DEFAULT },
        { "focus_mode",                 setconfvalue,   SWM_S_FOCUS_MODE },
        { "iconic_enabled",             setconfvalue,   SWM_S_ICONIC_ENABLED },
+       { "java_workaround",            setconfvalue,   SWM_S_JAVA_WORKAROUND },
        { "keyboard_mapping",           setkeymapping,  0 },
        { "layout",                     setlayout,      0 },
+       { "maximize_hide_bar",          setconfvalue,   SWM_S_MAXIMIZE_HIDE_BAR },
        { "modkey",                     setconfmodkey,  0 },
        { "program",                    setconfspawn,   0 },
        { "quirk",                      setconfquirk,   0 },
@@ -8358,11 +9428,15 @@ struct config_option configopt[] = {
        { "tile_gap",                   setconfvalue,   SWM_S_TILE_GAP },
        { "title_class_enabled",        setconfvalue,   SWM_S_WINDOW_CLASS_ENABLED }, /* For backwards compat. */
        { "title_name_enabled",         setconfvalue,   SWM_S_WINDOW_INSTANCE_ENABLED }, /* For backwards compat. */
+       { "urgent_collapse",            setconfvalue,   SWM_S_URGENT_COLLAPSE },
        { "urgent_enabled",             setconfvalue,   SWM_S_URGENT_ENABLED },
        { "verbose_layout",             setconfvalue,   SWM_S_VERBOSE_LAYOUT },
+       { "warp_focus",                 setconfvalue,   SWM_S_WARP_FOCUS },
+       { "warp_pointer",               setconfvalue,   SWM_S_WARP_POINTER },
        { "window_class_enabled",       setconfvalue,   SWM_S_WINDOW_CLASS_ENABLED },
        { "window_instance_enabled",    setconfvalue,   SWM_S_WINDOW_INSTANCE_ENABLED },
        { "window_name_enabled",        setconfvalue,   SWM_S_WINDOW_NAME_ENABLED },
+       { "workspace_clamp",            setconfvalue,   SWM_S_WORKSPACE_CLAMP },
        { "workspace_limit",            setconfvalue,   SWM_S_WORKSPACE_LIMIT },
        { "name",                       setconfvalue,   SWM_S_WORKSPACE_NAME },
 };
@@ -8385,7 +9459,7 @@ add_startup_exception(const char *fmt, ...)
                return;
 
        /* force bar to be enabled due to exception */
-       bar_enabled = 1;
+       bar_enabled = true;
 
        va_start(ap, fmt);
        _add_startup_exception(fmt, ap);
@@ -8508,6 +9582,7 @@ conf_load(const char *filename, int keymapping)
        if (line)
                free(line);
        fclose(config);
+
        DNPRINTF(SWM_D_CONF, "conf_load: end\n");
 
        return (0);
@@ -8641,13 +9716,16 @@ get_swm_ws(xcb_window_t id)
 }
 
 int
-get_ws_idx(xcb_window_t id)
+get_ws_idx(struct ws_win *win)
 {
        xcb_get_property_reply_t        *gpr;
        int                     ws_idx = -1;
 
+       if (win == NULL)
+               return -1;
+
        gpr = xcb_get_property_reply(conn,
-               xcb_get_property(conn, 0, id, ewmh[_NET_WM_DESKTOP].atom,
+               xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_DESKTOP].atom,
                    XCB_ATOM_CARDINAL, 0, 1),
                NULL);
        if (gpr) {
@@ -8656,30 +9734,96 @@ get_ws_idx(xcb_window_t id)
                free(gpr);
        }
 
-       if (ws_idx == -1)
-               if ((ws_idx = get_swm_ws(id)) != -1)
-                       xcb_delete_property(conn, id, a_swm_ws);
+       if (ws_idx == -1 && !(win->quirks & SWM_Q_IGNORESPAWNWS))
+               ws_idx = get_swm_ws(win->id);
 
        if (ws_idx > workspace_limit - 1 || ws_idx < -1)
                ws_idx = -1;
 
-       DNPRINTF(SWM_D_PROP, "get_ws_idx: win %#x, ws_idx: %d\n", id, ws_idx);
+       DNPRINTF(SWM_D_PROP, "get_ws_idx: win %#x, ws_idx: %d\n", win->id,
+           ws_idx);
 
        return ws_idx;
 }
 
-struct ws_win *
-manage_window(xcb_window_t id, int mapped)
+void
+reparent_window(struct ws_win *win)
 {
-       struct ws_win           *win, *ww;
-       struct swm_region       *r;
-       struct pid_e            *p;
-       struct quirk            *qp;
-       xcb_get_geometry_reply_t        *gr;
-       xcb_window_t            trans = XCB_WINDOW_NONE;
-       uint32_t                i, wa[2], new_flags;
-       int                     ws_idx;
-       char                    *class, *instance, *name;
+       xcb_void_cookie_t       c;
+       xcb_generic_error_t     *error;
+       uint32_t                wa[2];
+
+       win->frame = xcb_generate_id(conn);
+
+       DNPRINTF(SWM_D_MISC, "reparent_window: win %#x, frame: %#x\n",
+           win->id, win->frame);
+
+       wa[0] =
+           XCB_EVENT_MASK_ENTER_WINDOW |
+           XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+           XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+           XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+           XCB_EVENT_MASK_EXPOSURE;
+#ifdef SWM_DEBUG
+       wa[0] |= XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE;
+#endif
+
+       xcb_create_window(conn, XCB_COPY_FROM_PARENT, win->frame, win->s->root,
+           X(win), Y(win), WIDTH(win), HEIGHT(win), 0,
+           XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
+           XCB_CW_EVENT_MASK, wa);
+
+       win->state = SWM_WIN_STATE_REPARENTING;
+       c = xcb_reparent_window_checked(conn, win->id, win->frame, 0, 0);
+       if ((error = xcb_request_check(conn, c))) {
+               DNPRINTF(SWM_D_MISC, "reparent_window: error:\n");
+               event_error(error);
+               free(error);
+
+               /* Abort. */
+               xcb_destroy_window(conn, win->frame);
+               win->frame = XCB_WINDOW_NONE;
+       } else {
+               xcb_change_save_set(conn, XCB_SET_MODE_INSERT, win->id);
+       }
+       DNPRINTF(SWM_D_MISC, "reparent_window: done.\n");
+}
+
+void
+unparent_window(struct ws_win *win)
+{
+       xcb_change_save_set(conn, XCB_SET_MODE_DELETE, win->id);
+       xcb_reparent_window(conn, win->id, win->s->root, X(win), Y(win));
+       xcb_destroy_window(conn, win->frame);
+       win->frame = XCB_WINDOW_NONE;
+       win->state = SWM_WIN_STATE_UNPARENTING;
+}
+
+struct ws_win *
+manage_window(xcb_window_t id, int spawn_pos, bool mapping)
+{
+       struct ws_win                           *win = NULL, *ww;
+       struct swm_region                       *r;
+       struct pid_e                            *p;
+       struct quirk                            *qp;
+       xcb_get_geometry_reply_t                *gr;
+       xcb_get_window_attributes_reply_t       *war = NULL;
+       xcb_window_t                            trans = XCB_WINDOW_NONE;
+       uint32_t                                i, wa[1], new_flags;
+       int                                     ws_idx, force_ws = -1;
+       char                                    *class, *instance, *name;
+
+       if (find_bar(id)) {
+               DNPRINTF(SWM_D_MISC, "manage_window: win %#x is region bar; "
+                   "skipping.\n", id);
+               goto out;
+       }
+
+       if (find_region(id)) {
+               DNPRINTF(SWM_D_MISC, "manage_window: win %#x is region window; "
+                   "skipping.\n", id);
+               goto out;
+       }
 
        if ((win = find_window(id)) != NULL) {
                DNPRINTF(SWM_D_MISC, "manage_window: win %#x already "
@@ -8696,16 +9840,36 @@ manage_window(xcb_window_t id, int mapped)
                if (TRANS(win))
                        set_child_transient(win, &trans);
 
-               goto out;
+               goto remanage;
        } else {
                DNPRINTF(SWM_D_MISC, "manage_window: win %#x is new.\n", id);
        }
 
+       war = xcb_get_window_attributes_reply(conn,
+           xcb_get_window_attributes(conn, id), NULL);
+       if (war == NULL) {
+               DNPRINTF(SWM_D_EVENT, "manage_window: window lost.\n");
+               goto out;
+       }
+
+       if (war->override_redirect) {
+               DNPRINTF(SWM_D_EVENT, "manage_window: override_redirect; "
+                   "skipping.\n");
+               goto out;
+       }
+
+       if (!mapping && war->map_state == XCB_MAP_STATE_UNMAPPED &&
+           get_win_state(id) == XCB_ICCCM_WM_STATE_WITHDRAWN) {
+               DNPRINTF(SWM_D_EVENT, "manage_window: window withdrawn; "
+                   "skipping.\n");
+               goto out;
+       }
+
        /* Try to get initial window geometry. */
        gr = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, id), NULL);
        if (gr == NULL) {
                DNPRINTF(SWM_D_MISC, "manage_window: get geometry failed.\n");
-               return (NULL);
+               goto out;
        }
 
        /* Create and initialize ws_win object. */
@@ -8721,24 +9885,19 @@ manage_window(xcb_window_t id, int mapped)
        /* Ignore window border if there is one. */
        WIDTH(win) = gr->width;
        HEIGHT(win) = gr->height;
-       X(win) = gr->x + gr->border_width - border_width;
-       Y(win) = gr->y + gr->border_width - border_width;
-       win->bordered = 1;
-       win->mapped = mapped;
+       X(win) = gr->x + gr->border_width;
+       Y(win) = gr->y + gr->border_width;
+       win->bordered = false;
+       win->mapped = (war->map_state != XCB_MAP_STATE_UNMAPPED);
        win->s = r->s;  /* this never changes */
 
        free(gr);
 
        /* Select which X events to monitor and set border pixel color. */
-       wa[0] = win->s->c[SWM_S_COLOR_UNFOCUS].pixel;
-       wa[1] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_PROPERTY_CHANGE |
+       wa[0] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_PROPERTY_CHANGE |
            XCB_EVENT_MASK_STRUCTURE_NOTIFY;
-#ifdef SWM_DEBUG
-       wa[1] |= XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE;
-#endif
 
-       xcb_change_window_attributes(conn, win->id, XCB_CW_BORDER_PIXEL |
-           XCB_CW_EVENT_MASK, wa);
+       xcb_change_window_attributes(conn, win->id, XCB_CW_EVENT_MASK, wa);
 
        /* Get WM_SIZE_HINTS. */
        xcb_icccm_get_wm_normal_hints_reply(conn,
@@ -8762,31 +9921,11 @@ manage_window(xcb_window_t id, int mapped)
        /* Get WM_PROTOCOLS. */
        get_wm_protocols(win);
 
-       /* Figure out which workspace the window belongs to. */
-       if ((p = find_pid(window_get_pid(win->id))) != NULL) {
-               win->ws = &r->s->ws[p->ws];
-               TAILQ_REMOVE(&pidlist, p, entry);
-               free(p);
-               p = NULL;
-       } else if ((ws_idx = get_ws_idx(win->id)) != -1 &&
-           !TRANS(win)) {
-               /* _SWM_WS is set; use that. */
-               win->ws = &r->s->ws[ws_idx];
-       } else if (trans && (ww = find_window(trans)) != NULL) {
-               /* Launch transients in the same ws as parent. */
-               win->ws = ww->ws;
-       } else {
-               win->ws = r->ws;
-       }
-
-       /* Set the _NET_WM_DESKTOP atom. */
-       DNPRINTF(SWM_D_PROP, "manage_window: set _NET_WM_DESKTOP: %d\n",
-           win->ws->idx);
-       xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
-           ewmh[_NET_WM_DESKTOP].atom, XCB_ATOM_CARDINAL, 32, 1, &win->ws->idx);
-
-       /* WS must already be set for this to work. */
-       store_float_geom(win);
+#ifdef SWM_DEBUG
+       /* Must be after getting WM_HINTS and WM_PROTOCOLS. */
+       DNPRINTF(SWM_D_FOCUS, "manage_window: input_model: %s\n",
+           get_win_input_model(win));
+#endif
 
        /* Set initial quirks based on EWMH. */
        ewmh_autoquirk(win);
@@ -8806,7 +9945,7 @@ manage_window(xcb_window_t id, int mapped)
        /* java is retarded so treat it special */
        if (strstr(instance, "sun-awt")) {
                DNPRINTF(SWM_D_CLASS, "manage_window: java window detected.\n");
-               win->java = 1;
+               win->java = true;
        }
 
        TAILQ_FOREACH(qp, &quirks, entry) {
@@ -8814,9 +9953,11 @@ manage_window(xcb_window_t id, int mapped)
                    regexec(&qp->regex_instance, instance, 0, NULL, 0) == 0 &&
                    regexec(&qp->regex_name, name, 0, NULL, 0) == 0) {
                        DNPRINTF(SWM_D_CLASS, "manage_window: matched "
-                           "quirk: %s:%s:%s mask: %#lx\n", qp->class,
-                           qp->instance, qp->name, qp->quirk);
+                           "quirk: %s:%s:%s mask: %#x, ws: %d\n", qp->class,
+                           qp->instance, qp->name, qp->quirk, qp->ws);
                        win->quirks = qp->quirk;
+                       if (qp->ws >= 0 && qp->ws < workspace_limit)
+                               force_ws = qp->ws;
                }
        }
 
@@ -8830,6 +9971,39 @@ manage_window(xcb_window_t id, int mapped)
                        fake_keypress(win, XK_KP_Add, XCB_MOD_MASK_SHIFT);
        }
 
+       /* Figure out which workspace the window belongs to. */
+       if (!(win->quirks & SWM_Q_IGNOREPID) &&
+           (p = find_pid(window_get_pid(win->id))) != NULL) {
+               win->ws = &r->s->ws[p->ws];
+               TAILQ_REMOVE(&pidlist, p, entry);
+               free(p);
+               p = NULL;
+       } else if ((ws_idx = get_ws_idx(win)) != -1 &&
+           !TRANS(win)) {
+               /* _SWM_WS is set; use that. */
+               win->ws = &r->s->ws[ws_idx];
+       } else if (trans && (ww = find_window(trans)) != NULL) {
+               /* Launch transients in the same ws as parent. */
+               win->ws = ww->ws;
+       } else {
+               win->ws = r->ws;
+       }
+
+       if (force_ws != -1)
+               win->ws = &r->s->ws[force_ws];
+
+       /* Set the _NET_WM_DESKTOP atom. */
+       DNPRINTF(SWM_D_PROP, "manage_window: set _NET_WM_DESKTOP: %d\n",
+           win->ws->idx);
+       xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
+           ewmh[_NET_WM_DESKTOP].atom, XCB_ATOM_CARDINAL, 32, 1, &win->ws->idx);
+
+       /* Remove any _SWM_WS now that we set _NET_WM_DESKTOP. */
+       xcb_delete_property(conn, win->id, a_swm_ws);
+
+       /* WS must already be set for this to work. */
+       store_float_geom(win);
+
        /* Make sure window is positioned inside its region, if its active. */
        if (win->ws->r) {
                region_containment(win, r, SWM_CW_ALLSIDES |
@@ -8837,16 +10011,16 @@ manage_window(xcb_window_t id, int mapped)
                update_window(win);
        }
 
-out:
-       /* Figure out where to stack the window in the workspace. */
+remanage:
+       /* Figure out where to insert the window in the workspace list. */
        if (trans && (ww = find_window(trans)))
                TAILQ_INSERT_AFTER(&win->ws->winlist, ww, win, entry);
-       else if (win->ws->focus && spawn_position == SWM_STACK_ABOVE)
+       else if (win->ws->focus && spawn_pos == SWM_STACK_ABOVE)
                TAILQ_INSERT_AFTER(&win->ws->winlist, win->ws->focus, win,
                    entry);
-       else if (win->ws->focus && spawn_position == SWM_STACK_BELOW)
+       else if (win->ws->focus && spawn_pos == SWM_STACK_BELOW)
                TAILQ_INSERT_BEFORE(win->ws->focus, win, entry);
-       else switch (spawn_position) {
+       else switch (spawn_pos) {
        default:
        case SWM_STACK_TOP:
        case SWM_STACK_ABOVE:
@@ -8859,7 +10033,8 @@ out:
 
        ewmh_update_client_list();
 
-       TAILQ_INSERT_TAIL(&win->ws->stack, win, stack_entry);
+       TAILQ_INSERT_HEAD(&win->ws->stack, win, stack_entry);
+       lower_window(win);
 
        /* Get/apply initial _NET_WM_STATE */
        ewmh_get_wm_state(win);
@@ -8879,30 +10054,27 @@ out:
        /* Set initial _NET_WM_ALLOWED_ACTIONS */
        ewmh_update_actions(win);
 
-       grabbuttons(win);
+       reparent_window(win);
 
        DNPRINTF(SWM_D_MISC, "manage_window: done. win %#x, (x,y) w x h: "
            "(%d,%d) %d x %d, ws: %d, iconic: %s, transient: %#x\n", win->id,
            X(win), Y(win), WIDTH(win), HEIGHT(win), win->ws->idx,
            YESNO(ICONIC(win)), win->transient);
-
+out:
+       free(war);
        return (win);
 }
 
 void
 free_window(struct ws_win *win)
 {
-       DNPRINTF(SWM_D_MISC, "free_window: win %#x\n", win->id);
+       DNPRINTF(SWM_D_MISC, "free_window: win %#x\n", WINID(win));
 
        if (win == NULL)
                return;
 
-       TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
-
        xcb_icccm_get_wm_class_reply_wipe(&win->ch);
 
-       kill_refs(win);
-
        /* paint memory */
        memset(win, 0xff, sizeof *win); /* XXX kill later */
 
@@ -8913,18 +10085,13 @@ free_window(struct ws_win *win)
 void
 unmanage_window(struct ws_win *win)
 {
-       struct ws_win           *parent;
+       DNPRINTF(SWM_D_MISC, "unmanage_window: win %#x\n", WINID(win));
 
        if (win == NULL)
                return;
 
-       DNPRINTF(SWM_D_MISC, "unmanage_window: win %#x\n", win->id);
-
-       if (TRANS(win)) {
-               parent = find_window(win->transient);
-               if (parent)
-                       parent->focus_child = NULL;
-       }
+       kill_refs(win);
+       unparent_window(win);
 
        TAILQ_REMOVE(&win->ws->stack, win, stack_entry);
        TAILQ_REMOVE(&win->ws->winlist, win, entry);
@@ -8936,29 +10103,57 @@ unmanage_window(struct ws_win *win)
 void
 expose(xcb_expose_event_t *e)
 {
-       int                     i, num_screens;
-       struct swm_region       *r;
+       struct ws_win           *w;
+       struct swm_bar          *b;
+#ifdef SWM_DEBUG
+       struct workspace        *ws;
+#endif
 
-       DNPRINTF(SWM_D_EVENT, "expose: win %#x\n", e->window);
+       DNPRINTF(SWM_D_EVENT, "expose: win %#x, count: %d\n", e->window,
+           e->count);
 
-       num_screens = get_screen_count();
-       for (i = 0; i < num_screens; i++)
-               TAILQ_FOREACH(r, &screens[i].rl, entry)
-                       if (e->window == WINID(r->bar))
-                               bar_draw();
+       if (e->count > 0)
+               return;
 
-       xcb_flush(conn);
+       if ((b = find_bar(e->window))) {
+               bar_draw(b);
+               xcb_flush(conn);
+       } else if ((w = find_window(e->window)) && w->frame == e->window) {
+               draw_frame(w);
+#ifdef SWM_DEBUG
+               ws = w->ws;
+               TAILQ_FOREACH(w, &ws->winlist, entry)
+                       debug_refresh(w);
+#endif
+               xcb_flush(conn);
+       }
+
+       DNPRINTF(SWM_D_EVENT, "expose: done\n");
 }
 
-#ifdef SWM_DEBUG
 void
 focusin(xcb_focus_in_event_t *e)
 {
+       struct ws_win           *win;
+
        DNPRINTF(SWM_D_EVENT, "focusin: win %#x, mode: %s(%u), "
            "detail: %s(%u)\n", e->event, get_notify_mode_label(e->mode),
            e->mode, get_notify_detail_label(e->detail), e->detail);
+       if ((win = find_window(e->event)) && win != win->ws->focus &&
+           win != win->ws->focus_pending &&
+           e->mode == XCB_NOTIFY_MODE_NORMAL) {
+               win->ws->focus_prev = win->ws->focus;
+               win->ws->focus = win;
+               win->ws->focus_pending = NULL;
+
+               if (win->ws->focus_prev)
+                       draw_frame(win->ws->focus_prev);
+               draw_frame(win);
+               raise_window(win);
+       }
 }
 
+#ifdef SWM_DEBUG
 void
 focusout(xcb_focus_out_event_t *e)
 {
@@ -8971,44 +10166,102 @@ focusout(xcb_focus_out_event_t *e)
 void
 keypress(xcb_key_press_event_t *e)
 {
+       struct action           *ap;
+       struct binding          *bp;
        xcb_keysym_t            keysym;
-       struct key              *kp;
+       bool                    replay = true;
+
+       last_event_time = e->time;
 
        keysym = xcb_key_press_lookup_keysym(syms, e, 0);
 
        DNPRINTF(SWM_D_EVENT, "keypress: keysym: %u, win (x,y): %#x (%d,%d), "
            "detail: %u, time: %u, root (x,y): %#x (%d,%d), child: %#x, "
-           "state: %u, same_screen: %s\n", keysym, e->event, e->event_x,
-           e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y,
-           e->child, e->state, YESNO(e->same_screen));
+           "state: %u, cleaned: %u, same_screen: %s\n", keysym, e->event,
+           e->event_x, e->event_y, e->detail, e->time, e->root, e->root_x,
+           e->root_y, e->child, e->state, CLEANMASK(e->state),
+           YESNO(e->same_screen));
+
+       bp = binding_lookup(CLEANMASK(e->state), KEYBIND, keysym);
+       if (bp == NULL) {
+               /* Look for catch-all. */
+               if ((bp = binding_lookup(ANYMOD, KEYBIND, keysym)) == NULL)
+                       goto out;
+       }
 
-       if ((kp = key_lookup(CLEANMASK(e->state), keysym)) == NULL)
+       replay = bp->flags & BINDING_F_REPLAY;
+
+       if ((ap = &actions[bp->action]) == NULL)
                goto out;
 
-       last_event_time = e->time;
+       if (bp->action == FN_SPAWN_CUSTOM)
+               spawn_custom(root_to_region(e->root, SWM_CK_ALL), &ap->args,
+                   bp->spawn_name);
+       else if (ap->func)
+               ap->func(bp, root_to_region(e->root, SWM_CK_ALL), &ap->args);
 
-       if (kp->funcid == KF_SPAWN_CUSTOM)
-               spawn_custom(root_to_region(e->root, SWM_CK_ALL),
-                   &(keyfuncs[kp->funcid].args), kp->spawn_name);
-       else if (keyfuncs[kp->funcid].func)
-               keyfuncs[kp->funcid].func(root_to_region(e->root, SWM_CK_ALL),
-                   &(keyfuncs[kp->funcid].args));
+       replay = replay && !(ap->flags & FN_F_NOREPLAY);
 
 out:
-       /* Unfreeze grab events. */
-       xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, e->time);
+       if (replay) {
+               DNPRINTF(SWM_D_EVENT, "keypress: replaying.\n");
+               /* Pass keypress to event window and unfreeze keyboard queue. */
+               xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, e->time);
+       } else {
+               /* Release freeze. */
+               xcb_allow_events(conn, XCB_ALLOW_SYNC_KEYBOARD, e->time);
+       }
        xcb_flush(conn);
 
        DNPRINTF(SWM_D_EVENT, "keypress: done.\n");
 }
 
+void
+keyrelease(xcb_key_release_event_t *e)
+{
+       struct action           *ap;
+       struct binding          *bp;
+       xcb_keysym_t            keysym;
+
+       last_event_time = e->time;
+
+       keysym = xcb_key_release_lookup_keysym(syms, e, 0);
+
+       DNPRINTF(SWM_D_EVENT, "keyrelease: keysym: %u, win (x,y): %#x (%d,%d), "
+           "detail: %u, time: %u, root (x,y): %#x (%d,%d), child: %#x, "
+           "state: %u, same_screen: %s\n", keysym, e->event, e->event_x,
+           e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y,
+           e->child, e->state, YESNO(e->same_screen));
+
+       bp = binding_lookup(CLEANMASK(e->state), KEYBIND, keysym);
+       if (bp == NULL)
+               /* Look for catch-all. */
+               bp = binding_lookup(ANYMOD, KEYBIND, keysym);
+
+       if (bp && (ap = &actions[bp->action]) && !(ap->flags & FN_F_NOREPLAY) &&
+           bp->flags & BINDING_F_REPLAY) {
+               xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, e->time);
+               DNPRINTF(SWM_D_EVENT, "keyrelease: replaying.\n");
+       } else {
+               xcb_allow_events(conn, XCB_ALLOW_SYNC_KEYBOARD, e->time);
+               DNPRINTF(SWM_D_EVENT, "keyrelease: unfreezing.\n");
+       }
+
+       xcb_flush(conn);
+
+       DNPRINTF(SWM_D_EVENT, "keyrelease: done.\n");
+}
+
 void
 buttonpress(xcb_button_press_event_t *e)
 {
-       struct ws_win           *win = NULL;
+       struct ws_win           *win = NULL, *newf;
        struct swm_region       *r, *old_r;
-       int                     i;
-       int                     handled = 0;
+       struct action           *ap;
+       struct binding          *bp;
+       bool                    replay = true;
+
+       last_event_time = e->time;
 
        DNPRINTF(SWM_D_EVENT, "buttonpress: win (x,y): %#x (%d,%d), "
            "detail: %u, time: %u, root (x,y): %#x (%d,%d), child: %#x, "
@@ -9017,70 +10270,131 @@ buttonpress(xcb_button_press_event_t *e)
            e->state, YESNO(e->same_screen));
 
        if (e->event == e->root) {
-               if (e->child != 0) {
+               if (e->child) {
                        win = find_window(e->child);
-                       /* Pass ButtonPress to window if it isn't managed. */
-                       if (win == NULL)
-                               goto out;
                } else {
                        /* Focus on empty region */
                        /* If no windows on region if its empty. */
                        r = root_to_region(e->root, SWM_CK_POINTER);
-                       if (r == NULL) {
-                               DNPRINTF(SWM_D_EVENT, "buttonpress: "
-                                   "NULL region; ignoring.\n");
-                               goto out;
-                       }
-
-                       if (TAILQ_EMPTY(&r->ws->winlist)) {
+                       if (r && TAILQ_EMPTY(&r->ws->winlist)) {
                                old_r = root_to_region(e->root, SWM_CK_FOCUS);
                                if (old_r && old_r != r)
                                        unfocus_win(old_r->ws->focus);
 
+                               DNPRINTF(SWM_D_FOCUS, "buttonpress: "
+                                   "set_input_focus: %#x, revert-to: parent, "
+                                   "time: %#x\n", e->root, e->time);
                                xcb_set_input_focus(conn,
-                                   XCB_INPUT_FOCUS_PARENT, e->root, e->time);
+                                   XCB_INPUT_FOCUS_POINTER_ROOT,
+                                   e->root, e->time);
 
                                /* Clear bar since empty. */
-                               bar_draw();
+                               bar_draw(r->bar);
 
-                               handled = 1;
-                               goto out;
+                               /* No need to replay event. */
+                               replay = false;
                        }
                }
        } else {
                win = find_window(e->event);
        }
 
-       if (win == NULL)
-               goto out;
+       if (win) {
+               newf = get_focus_magic(win);
+               if (win->ws->focus == newf && newf != win) {
+                       if (win->focus_child == win)
+                               win->focus_child = NULL;
+                       newf = win;
+               }
+               focus_win(newf);
+       }
 
-       last_event_time = e->time;
+       /* Handle any bound action. */
+       bp = binding_lookup(CLEANMASK(e->state), BTNBIND, e->detail);
+       if (bp == NULL) {
+               /* Look for catch-all. */
+               if ((bp = binding_lookup(ANYMOD, BTNBIND, e->detail)) == NULL)
+                       goto out;
 
-       focus_win(get_focus_magic(win));
+       }
 
-       for (i = 0; i < LENGTH(buttons); i++)
-               if (client_click == buttons[i].action && buttons[i].func &&
-                   buttons[i].button == e->detail &&
-                   CLEANMASK(buttons[i].mask) == CLEANMASK(e->state)) {
-                       buttons[i].func(win, &buttons[i].args);
-                       handled = 1;
-               }
+       replay = bp->flags & BINDING_F_REPLAY;
+
+       if ((ap = &actions[bp->action]) == NULL)
+               goto out;
+
+       if (bp->action == FN_SPAWN_CUSTOM)
+               spawn_custom(root_to_region(e->root, SWM_CK_ALL), &ap->args,
+                   bp->spawn_name);
+       else if (ap->func)
+               ap->func(bp, root_to_region(e->root, SWM_CK_ALL), &ap->args);
+
+       replay = replay && !(ap->flags & FN_F_NOREPLAY);
 
 out:
-       if (!handled) {
-               DNPRINTF(SWM_D_EVENT, "buttonpress: passing to window.\n");
+       if (replay) {
+               DNPRINTF(SWM_D_EVENT, "buttonpress: replaying.\n");
                /* Replay event to event window */
                xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, e->time);
        } else {
-               DNPRINTF(SWM_D_EVENT, "buttonpress: handled.\n");
                /* Unfreeze grab events. */
                xcb_allow_events(conn, XCB_ALLOW_SYNC_POINTER, e->time);
        }
 
-       xcb_flush(conn);
+       focus_flush();
+}
+
+void
+buttonrelease(xcb_button_release_event_t *e)
+{
+       struct action           *ap;
+       struct binding          *bp;
+
+       last_event_time = e->time;
+
+       DNPRINTF(SWM_D_EVENT, "buttonrelease: win (x,y): %#x (%d,%d), "
+           "detail: %u, time: %u, root (x,y): %#x (%d,%d), child: %#x, "
+           "state: %u, same_screen: %s\n", e->event, e->event_x, e->event_y,
+           e->detail, e->time, e->root, e->root_x, e->root_y, e->child,
+           e->state, YESNO(e->same_screen));
+
+       bp = binding_lookup(CLEANMASK(e->state), BTNBIND, e->detail);
+       if (bp == NULL)
+               /* Look for catch-all. */
+               bp = binding_lookup(ANYMOD, BTNBIND, e->detail);
+
+       if (bp && (ap = &actions[bp->action]) && !(ap->flags & FN_F_NOREPLAY) &&
+           bp->flags & BINDING_F_REPLAY) {
+               DNPRINTF(SWM_D_EVENT, "buttonrelease: replaying.\n");
+               xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, e->time);
+       } else {
+               xcb_allow_events(conn, XCB_ALLOW_SYNC_POINTER, e->time);
+       }
+
+       xcb_flush(conn);
+}
+
+#ifdef SWM_DEBUG
+char *
+get_win_input_model(struct ws_win *win)
+{
+       char            *inputmodel;
+       /*
+        *      Input Model             Input Field     WM_TAKE_FOCUS
+        *      No Input                False           Absent
+        *      Passive                 True            Absent
+        *      Locally Active          True            Present
+        *      Globally Active         False           Present
+        */
+
+       if (ACCEPTS_FOCUS(win))
+               inputmodel = (win->take_focus) ? "Locally Active" : "Passive";
+       else
+               inputmodel = (win->take_focus) ? "Globally Active" : "No Input";
+
+       return inputmodel;
 }
 
-#ifdef SWM_DEBUG
 void
 print_win_geom(xcb_window_t w)
 {
@@ -9108,7 +10422,7 @@ get_stack_mode_name(uint8_t mode)
 {
        char    *name;
 
-       switch(mode) {
+       switch (mode) {
        case XCB_STACK_MODE_ABOVE:
                name = "Above";
                break;
@@ -9137,13 +10451,14 @@ configurerequest(xcb_configure_request_event_t *e)
 {
        struct ws_win           *win;
        struct swm_region       *r = NULL;
-       int                     new = 0, i = 0;
-       uint16_t                mask = 0;
+       int                     i = 0;
        uint32_t                wc[7] = {0};
+       uint16_t                mask = 0;
+       bool                    new = false;
 
        if ((win = find_window(e->window)) == NULL)
                if ((win = find_unmanaged_window(e->window)) == NULL)
-                       new = 1;
+                       new = true;
 
 #ifdef SWM_DEBUG
        if (swm_debug & SWM_D_EVENT) {
@@ -9233,7 +10548,7 @@ configurerequest(xcb_configure_request_event_t *e)
                if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
                        win->g_float.h = e->height;
 
-               win->g_floatvalid = 1;
+               win->g_floatvalid = true;
 
                if (!MAXIMIZED(win) && !FULLSCREEN(win) &&
                    (TRANS(win) || (ABOVE(win) &&
@@ -9273,12 +10588,9 @@ configurenotify(xcb_configure_notify_event_t *e)
 
        win = find_window(e->window);
        if (win) {
-               xcb_icccm_get_wm_normal_hints_reply(conn,
-                   xcb_icccm_get_wm_normal_hints(conn, win->id),
-                   &win->sh, NULL);
                adjust_font(win);
-               if (font_adjusted) {
-                       stack();
+               if (font_adjusted && win->ws->r) {
+                       stack(win->ws->r);
                        xcb_flush(conn);
                }
        }
@@ -9288,38 +10600,58 @@ void
 destroynotify(xcb_destroy_notify_event_t *e)
 {
        struct ws_win           *win;
+       struct workspace        *ws;
 
        DNPRINTF(SWM_D_EVENT, "destroynotify: win %#x\n", e->window);
 
        if ((win = find_window(e->window)) == NULL) {
                if ((win = find_unmanaged_window(e->window)) == NULL)
-                       return;
+                       goto out;
+               /* Window is on unmanaged list. */
+               TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
                free_window(win);
-               return;
+               goto out;
+       }
+
+       ws = win->ws;
+
+       if (win->frame == e->window) {
+               DNPRINTF(SWM_D_EVENT, "destroynotify: frame for win %#x\n",
+                   win->id);
+               win->frame = XCB_WINDOW_NONE;
+               goto out;
        }
 
        if (focus_mode != SWM_FOCUS_FOLLOW) {
                /* If we were focused, make sure we focus on something else. */
-               if (win == win->ws->focus)
-                       win->ws->focus_pending = get_focus_prev(win);
+               if (win == ws->focus) {
+                       ws->focus_pending = get_focus_prev(win);
+                       if (ws->focus_pending == win)
+                               ws->focus_pending = NULL;
+               }
        }
 
        unmanage_window(win);
-       stack();
+       TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
+       free_window(win);
+       stack(ws->r);
 
-       if (focus_mode != SWM_FOCUS_FOLLOW && WS_FOCUSED(win->ws)) {
-               if (win->ws->focus_pending) {
-                       focus_win(win->ws->focus_pending);
-                       win->ws->focus_pending = NULL;
-               } else if (win == win->ws->focus) {
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
-                           win->ws->r->id, XCB_CURRENT_TIME);
+       if (focus_mode != SWM_FOCUS_FOLLOW && WS_FOCUSED(ws)) {
+               if (ws->focus_pending) {
+                       focus_win(ws->focus_pending);
+                       ws->focus_pending = NULL;
+               } else if (ws->focus == NULL) {
+                       DNPRINTF(SWM_D_FOCUS, "destroynotify: set_input_focus: "
+                           "%#x, revert-to: parent, time: 0\n", ws->r->id);
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+                           ws->r->id, XCB_CURRENT_TIME);
                }
-       }
 
-       free_window(win);
+               focus_flush();
+       }
 
-       focus_flush();
+out:
+       DNPRINTF(SWM_D_EVENT, "destroynotify: done.\n");
 }
 
 #ifdef SWM_DEBUG
@@ -9384,6 +10716,61 @@ get_notify_mode_label(uint8_t mode)
 
        return label;
 }
+
+char *
+get_state_mask_label(uint16_t state)
+{
+       char *label;
+
+       switch (state) {
+       case XCB_KEY_BUT_MASK_SHIFT:
+               label = "ShiftMask";
+               break;
+       case XCB_KEY_BUT_MASK_LOCK:
+               label = "LockMask";
+               break;
+       case XCB_KEY_BUT_MASK_CONTROL:
+               label = "ControlMask";
+               break;
+       case XCB_KEY_BUT_MASK_MOD_1:
+               label = "Mod1Mask";
+               break;
+       case XCB_KEY_BUT_MASK_MOD_2:
+               label = "Mod2Mask";
+               break;
+       case XCB_KEY_BUT_MASK_MOD_3:
+               label = "Mod3Mask";
+               break;
+       case XCB_KEY_BUT_MASK_MOD_4:
+               label = "Mod4Mask";
+               break;
+       case XCB_KEY_BUT_MASK_MOD_5:
+               label = "Mod5Mask";
+               break;
+       case XCB_KEY_BUT_MASK_BUTTON_1:
+               label = "Button1Mask";
+               break;
+       case XCB_KEY_BUT_MASK_BUTTON_2:
+               label = "Button2Mask";
+               break;
+       case XCB_KEY_BUT_MASK_BUTTON_3:
+               label = "Button3Mask";
+               break;
+       case XCB_KEY_BUT_MASK_BUTTON_4:
+               label = "Button4Mask";
+               break;
+       case XCB_KEY_BUT_MASK_BUTTON_5:
+               label = "Button5Mask";
+               break;
+       case 0:
+               label = "None";
+               break;
+       default:
+               label = "Unknown";
+       }
+
+       return label;
+}
 #endif
 
 void
@@ -9392,14 +10779,24 @@ enternotify(xcb_enter_notify_event_t *e)
        struct ws_win           *win;
        struct swm_region       *r;
 
+       last_event_time = e->time;
+
        DNPRINTF(SWM_D_FOCUS, "enternotify: time: %u, win (x,y): %#x "
            "(%d,%d), mode: %s(%d), detail: %s(%d), root (x,y): %#x (%d,%d), "
-           "child: %#x, same_screen_focus: %s, state: %d\n",
+           "child: %#x, same_screen_focus: %s, state: %s(%d)\n",
            e->time, e->event, e->event_x, e->event_y,
            get_notify_mode_label(e->mode), e->mode,
            get_notify_detail_label(e->detail), e->detail,
            e->root, e->root_x, e->root_y, e->child,
-           YESNO(e->same_screen_focus), e->state);
+           YESNO(e->same_screen_focus), get_state_mask_label(e->state),
+           e->state);
+
+       if (e->event == e->root && e->child == XCB_WINDOW_NONE &&
+           e->mode == XCB_NOTIFY_MODE_GRAB &&
+           e->detail == XCB_NOTIFY_DETAIL_INFERIOR) {
+               DNPRINTF(SWM_D_EVENT, "enternotify: grab inferior; ignoring.\n");
+               return;
+       }
 
        if (focus_mode == SWM_FOCUS_MANUAL &&
            e->mode == XCB_NOTIFY_MODE_NORMAL) {
@@ -9407,7 +10804,12 @@ enternotify(xcb_enter_notify_event_t *e)
                return;
        }
 
-       last_event_time = e->time;
+       if (focus_mode != SWM_FOCUS_FOLLOW &&
+           e->mode == XCB_NOTIFY_MODE_UNGRAB &&
+           e->detail != XCB_NOTIFY_DETAIL_ANCESTOR) {
+               DNPRINTF(SWM_D_EVENT, "enternotify: ungrab; ignoring.\n");
+               return;
+       }
 
        if ((win = find_window(e->event)) == NULL) {
                if (e->event == e->root) {
@@ -9426,9 +10828,18 @@ enternotify(xcb_enter_notify_event_t *e)
                        return;
                }
        } else {
+               if (e->mode == XCB_NOTIFY_MODE_NORMAL &&
+                   e->detail == XCB_NOTIFY_DETAIL_INFERIOR) {
+                       DNPRINTF(SWM_D_EVENT, "enternotify: entering from "
+                           "inferior; ignoring\n");
+                       return;
+               }
+
                focus_win(get_focus_magic(win));
        }
 
+       DNPRINTF(SWM_D_EVENT, "enternotify: done\n");
+
        xcb_flush(conn);
 }
 
@@ -9436,14 +10847,17 @@ enternotify(xcb_enter_notify_event_t *e)
 void
 leavenotify(xcb_leave_notify_event_t *e)
 {
+       last_event_time = e->time;
+
        DNPRINTF(SWM_D_FOCUS, "leavenotify: time: %u, win (x,y): %#x "
            "(%d,%d), mode: %s(%d), detail: %s(%d), root (x,y): %#x (%d,%d), "
-           "child: %#x, same_screen_focus: %s, state: %d\n",
+           "child: %#x, same_screen_focus: %s, state: %s(%d)\n",
            e->time, e->event, e->event_x, e->event_y,
            get_notify_mode_label(e->mode), e->mode,
            get_notify_detail_label(e->detail), e->detail,
            e->root, e->root_x, e->root_y, e->child,
-           YESNO(e->same_screen_focus), e->state);
+           YESNO(e->same_screen_focus), get_state_mask_label(e->state),
+           e->state);
 }
 #endif
 
@@ -9455,10 +10869,18 @@ mapnotify(xcb_map_notify_event_t *e)
 
        DNPRINTF(SWM_D_EVENT, "mapnotify: win %#x\n", e->window);
 
-       if ((win = manage_window(e->window, 1)) == NULL)
+       if ((win = manage_window(e->window, spawn_position, false)) == NULL)
                return;
        ws = win->ws;
 
+       if (win->state == SWM_WIN_STATE_REPARENTING)
+               return;
+
+       if (ws->r == NULL) {
+               unmap_window(win);
+               goto out;
+       }
+
        /* Need to know if win was mapped due to ws switch. */
        if (ws->state == SWM_WS_STATE_MAPPED) {
                if (ws->focus_pending && TRANS(ws->focus_pending))
@@ -9467,78 +10889,53 @@ mapnotify(xcb_map_notify_event_t *e)
                /* If window's parent is maximized, don't clear it. */
                if ((parent == NULL) || !MAXIMIZED(parent))
                        if (clear_maximized(ws) > 0)
-                               stack();
+                               stack(ws->r);
        }
 
-       win->mapped = 1;
+       win->mapped = true;
        set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL);
 
        if (focus_mode != SWM_FOCUS_FOLLOW && WS_FOCUSED(win->ws)) {
                if (ws->focus_pending == win) {
                        focus_win(win);
                        ws->focus_pending = NULL;
+                       center_pointer(ws->r);
                        focus_flush();
                }
        }
 
+out:
+       DNPRINTF(SWM_D_EVENT, "mapnotify: done\n");
+
        xcb_flush(conn);
 }
 
 void
 mappingnotify(xcb_mapping_notify_event_t *e)
 {
-       struct ws_win   *w;
-       int     i, j, num_screens;
-
-       xcb_refresh_keyboard_mapping(syms, e);
-
-       if (e->request == XCB_MAPPING_KEYBOARD) {
+       if (e->request != XCB_MAPPING_POINTER) {
+               xcb_refresh_keyboard_mapping(syms, e);
                grabkeys();
-
-               /* Regrab buttons on all managed windows. */
-               num_screens = get_screen_count();
-               for (i = 0; i < num_screens; i++)
-                       for (j = 0; j < workspace_limit; j++)
-                               TAILQ_FOREACH(w, &screens[i].ws[j].winlist,
-                                   entry)
-                                       grabbuttons(w);
        }
+
+       grabbuttons();
 }
 
 void
 maprequest(xcb_map_request_event_t *e)
 {
        struct ws_win           *win, *w = NULL;
-       xcb_get_window_attributes_reply_t *war;
 
        DNPRINTF(SWM_D_EVENT, "maprequest: win %#x\n",
            e->window);
 
-       war = xcb_get_window_attributes_reply(conn,
-           xcb_get_window_attributes(conn, e->window),
-           NULL);
-       if (war == NULL) {
-               DNPRINTF(SWM_D_EVENT, "maprequest: window lost.\n");
-               goto out;
-       }
-
-       if (war->override_redirect) {
-               DNPRINTF(SWM_D_EVENT, "maprequest: override_redirect; "
-                   "skipping.\n");
-               goto out;
-       }
-
-       win = manage_window(e->window,
-           (war->map_state == XCB_MAP_STATE_VIEWABLE));
+       win = manage_window(e->window, spawn_position, true);
        if (win == NULL)
                goto out;
 
        /* The new window should get focus; prepare. */
        if (focus_mode != SWM_FOCUS_FOLLOW &&
-           !(win->quirks & SWM_Q_NOFOCUSONMAP) &&
-           (!(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT) ||
-            (win->hints.flags & XCB_ICCCM_WM_HINT_INPUT &&
-             win->hints.input))) {
+           !(win->quirks & SWM_Q_NOFOCUSONMAP) && ACCEPTS_FOCUS(win)) {
                if (win->quirks & SWM_Q_FOCUSONMAP_SINGLE) {
                        /* See if other wins of same type are already mapped. */
                        TAILQ_FOREACH(w, &win->ws->winlist, entry) {
@@ -9562,14 +10959,12 @@ maprequest(xcb_map_request_event_t *e)
        }
 
        /* All windows need to be mapped if they are in the current workspace.*/
-       if (win->ws->r)
-               stack();
+       stack(win->ws->r);
 
        /* Ignore EnterNotify to handle the mapnotify without interference. */
        if (focus_mode == SWM_FOCUS_DEFAULT)
                event_drain(XCB_ENTER_NOTIFY);
 out:
-       free(war);
        DNPRINTF(SWM_D_EVENT, "maprequest: done.\n");
 }
 
@@ -9579,6 +10974,8 @@ motionnotify(xcb_motion_notify_event_t *e)
        struct swm_region       *r = NULL;
        int                     i, num_screens;
 
+       last_event_time = e->time;
+
        DNPRINTF(SWM_D_FOCUS, "motionnotify: time: %u, win (x,y): %#x "
            "(%d,%d), detail: %s(%d), root (x,y): %#x (%d,%d), "
            "child: %#x, same_screen_focus: %s, state: %d\n",
@@ -9587,8 +10984,6 @@ motionnotify(xcb_motion_notify_event_t *e)
            e->root, e->root_x, e->root_y, e->child,
            YESNO(e->same_screen), e->state);
 
-       last_event_time = e->time;
-
        if (focus_mode == SWM_FOCUS_MANUAL)
                return;
 
@@ -9653,19 +11048,20 @@ propertynotify(xcb_property_notify_event_t *e)
            e->state);
        free(name);
 #endif
+       last_event_time = e->time;
+
        win = find_window(e->window);
        if (win == NULL)
                return;
 
        ws = win->ws;
 
-       last_event_time = e->time;
-
        if (e->atom == a_state) {
                /* State just changed, make sure it gets focused if mapped. */
                if (e->state == XCB_PROPERTY_NEW_VALUE) {
                        if (focus_mode != SWM_FOCUS_FOLLOW && WS_FOCUSED(ws)) {
                                if (win->mapped &&
+                                   win->state == SWM_WIN_STATE_REPARENTED &&
                                    ws->focus_pending == win) {
                                        focus_win(ws->focus_pending);
                                        ws->focus_pending = NULL;
@@ -9674,14 +11070,46 @@ propertynotify(xcb_property_notify_event_t *e)
                }
        } else if (e->atom == XCB_ATOM_WM_CLASS ||
            e->atom == XCB_ATOM_WM_NAME) {
-               bar_draw();
+               if (ws->r)
+                       bar_draw(ws->r->bar);
        } else if (e->atom == a_prot) {
                get_wm_protocols(win);
+       } else if (e->atom == XCB_ATOM_WM_NORMAL_HINTS) {
+               xcb_icccm_get_wm_normal_hints_reply(conn,
+                   xcb_icccm_get_wm_normal_hints(conn, win->id),
+                   &win->sh, NULL);
        }
 
        xcb_flush(conn);
 }
 
+void
+reparentnotify(xcb_reparent_notify_event_t *e)
+{
+       struct ws_win   *win;
+
+       DNPRINTF(SWM_D_EVENT, "reparentnotify: event: %#x, win %#x, "
+           "parent: %#x, (x,y): (%u,%u), override_redirect: %u\n",
+           e->event, e->window, e->parent, e->x, e->y, e->override_redirect);
+
+       win = find_window(e->window);
+       if (win) {
+               if (win->state == SWM_WIN_STATE_REPARENTING) {
+                       win->state = SWM_WIN_STATE_REPARENTED;
+
+                       if (win->ws->r && !ICONIC(win))
+                               map_window(win);
+                       else
+                               unmap_window(win);
+
+                       update_window(win);
+                       update_win_stacking(win);
+               } else if (win->state == SWM_WIN_STATE_UNPARENTING) {
+                       win->state = SWM_WIN_STATE_UNPARENTED;
+               }
+       }
+}
+
 void
 unmapnotify(xcb_unmap_notify_event_t *e)
 {
@@ -9692,15 +11120,25 @@ unmapnotify(xcb_unmap_notify_event_t *e)
 
        /* If we aren't managing the window, then ignore. */
        win = find_window(e->window);
-       if (win == NULL || win->id != e->window)
+       if (win == NULL || win->id != e->window) {
+               DNPRINTF(SWM_D_EVENT, "unmapnotify: ignore unmanaged.\n");
                return;
+       }
 
        /* Do nothing if already withdrawn. */
-       if (!win->mapped && !ICONIC(win))
+       if (!win->mapped && !ICONIC(win)) {
+               DNPRINTF(SWM_D_EVENT, "unmapnotify: ignore withdrawn.\n");
                return;
+       }
 
        ws = win->ws;
-       win->mapped = 0;
+       win->mapped = false;
+
+       /* Ignore if reparenting-related. */
+       if (win->state != SWM_WIN_STATE_REPARENTED) {
+               DNPRINTF(SWM_D_EVENT, "unmapnotify: ignore not reparented.\n");
+               return;
+       }
 
        /* If win was focused, make sure to focus on something else. */
        if (win == ws->focus) {
@@ -9716,15 +11154,16 @@ unmapnotify(xcb_unmap_notify_event_t *e)
 
        if (ICONIC(win)) {
                /* Iconify. */
+               DNPRINTF(SWM_D_EVENT, "unmapnotify: iconify.\n");
                set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC);
        } else {
                /* Withdraw. */
+               DNPRINTF(SWM_D_EVENT, "unmapnotify: withdraw.\n");
                set_win_state(win, XCB_ICCCM_WM_STATE_WITHDRAWN);
                unmanage_window(win);
        }
 
-       if (ws->r)
-               stack();
+       stack(ws->r);
 
        /* Update focus if ws is active. */
        if (WS_FOCUSED(ws)) {
@@ -9734,12 +11173,17 @@ unmapnotify(xcb_unmap_notify_event_t *e)
                        focus_win(ws->focus_pending);
                        ws->focus_pending = NULL;
                } else if (ws->focus == NULL) {
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
+                       DNPRINTF(SWM_D_FOCUS, "unmapnotify: set_input_focus: "
+                           "%#x, revert-to: parent, time: 0\n",
+                           ws->r->id);
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
                            ws->r->id, XCB_CURRENT_TIME);
                }
        }
 
+       center_pointer(ws->r);
        focus_flush();
+       DNPRINTF(SWM_D_EVENT, "unmapnotify: done.\n");
 }
 
 #ifdef SWM_DEBUG
@@ -9772,7 +11216,7 @@ clientmessage(xcb_client_message_event_t *e)
        struct ws_win           *win;
        struct swm_region       *r = NULL;
        union arg               a;
-       uint32_t                val[2];
+       uint32_t                vals[4];
        int                     num_screens, i;
        xcb_map_request_event_t mre;
 #ifdef SWM_DEBUG
@@ -9794,10 +11238,18 @@ clientmessage(xcb_client_message_event_t *e)
 
                if (r && e->data.data32[0] < (uint32_t)workspace_limit) {
                        a.id = e->data.data32[0];
-                       switchws(r, &a);
+                       switchws(NULL, r, &a);
                        focus_flush();
                }
 
+               return;
+       } else if (e->type == ewmh[_NET_REQUEST_FRAME_EXTENTS].atom) {
+               DNPRINTF(SWM_D_EVENT,
+                   "clientmessage: set _NET_FRAME_EXTENTS on window.\n");
+               vals[0] = vals[1] = vals[2] = vals[3] = border_width;
+               xcb_change_property(conn, XCB_PROP_MODE_REPLACE, e->window,
+                   a_net_frame_extents, XCB_ATOM_CARDINAL, 32, 4, vals);
+               xcb_flush(conn);
                return;
        }
 
@@ -9823,7 +11275,8 @@ clientmessage(xcb_client_message_event_t *e)
                 * Allow focus changes that are a result of direct user
                 * action and from applications that use the old EWMH spec.
                 */
-               if (e->data.data32[0] != EWMH_SOURCE_TYPE_NORMAL) {
+               if (e->data.data32[0] != EWMH_SOURCE_TYPE_NORMAL ||
+                   win->quirks & SWM_Q_OBEYAPPFOCUSREQ) {
                        if (WS_FOCUSED(win->ws))
                                focus_win(win);
                        else
@@ -9856,11 +11309,13 @@ clientmessage(xcb_client_message_event_t *e)
                }
        } else if (e->type == ewmh[_NET_RESTACK_WINDOW].atom) {
                DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_RESTACK_WINDOW\n");
-               val[0] = e->data.data32[1]; /* Sibling window. */
-               val[1] = e->data.data32[2]; /* Stack mode detail. */
+               vals[0] = e->data.data32[1]; /* Sibling window. */
+               vals[1] = e->data.data32[2]; /* Stack mode detail. */
 
-               xcb_configure_window(conn, win->id, XCB_CONFIG_WINDOW_SIBLING |
-                   XCB_CONFIG_WINDOW_STACK_MODE, val);
+               if (win->frame != XCB_WINDOW_NONE)
+                       xcb_configure_window(conn, win->frame,
+                           XCB_CONFIG_WINDOW_SIBLING |
+                           XCB_CONFIG_WINDOW_STACK_MODE, vals);
        } else  if (e->type == ewmh[_NET_WM_STATE].atom) {
                DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_WM_STATE\n");
                ewmh_change_wm_state(win, e->data.data32[1], e->data.data32[0]);
@@ -9869,19 +11324,24 @@ clientmessage(xcb_client_message_event_t *e)
                            e->data.data32[0]);
 
                ewmh_update_wm_state(win);
-               stack();
+               stack(win->ws->r);
        } else if (e->type == ewmh[_NET_WM_DESKTOP].atom) {
                DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_WM_DESKTOP\n");
                r = win->ws->r;
 
-               win_to_ws(win, e->data.data32[0], 1);
+               win_to_ws(win, e->data.data32[0], true);
 
-               /* Restack if either the source or destination ws is mapped. */
-               if (r != NULL || win->ws->r != NULL) {
-                       if (FLOATING(win))
-                               load_float_geom(win);
+               /* Stack source and destination ws, if mapped. */
+               if (r != win->ws->r) {
+                       if (r)
+                               stack(r);
+
+                       if (win->ws->r) {
+                               if (FLOATING(win))
+                                       load_float_geom(win);
 
-                       stack();
+                               stack(win->ws->r);
+                       }
                }
        }
 
@@ -9926,7 +11386,16 @@ enable_wm(void)
 {
        int                     num_screens, i;
        const uint32_t          val = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-           XCB_EVENT_MASK_ENTER_WINDOW;
+           XCB_EVENT_MASK_ENTER_WINDOW |
+           XCB_EVENT_MASK_OWNER_GRAB_BUTTON |
+           XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+           XCB_EVENT_MASK_ENTER_WINDOW |
+           XCB_EVENT_MASK_LEAVE_WINDOW |
+           XCB_EVENT_MASK_BUTTON_PRESS |
+           XCB_EVENT_MASK_BUTTON_RELEASE |
+           XCB_EVENT_MASK_KEY_PRESS |
+           XCB_EVENT_MASK_KEY_RELEASE |
+           XCB_EVENT_MASK_PROPERTY_CHANGE;
        xcb_screen_t            *sc;
        xcb_void_cookie_t       wac;
        xcb_generic_error_t     *error;
@@ -9946,11 +11415,6 @@ enable_wm(void)
                        free(error);
                        return 1;
                }
-
-               /* click to focus on empty region */
-               xcb_grab_button(conn, 1, sc->root, BUTTONMASK,
-                   XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
-                   XCB_CURSOR_NONE, XCB_BUTTON_INDEX_1, XCB_BUTTON_MASK_ANY);
        }
 
        return 0;
@@ -9979,6 +11443,7 @@ new_region(struct swm_screen *s, int x, int y, int w, int h)
                        r->ws->r = NULL;
                        bar_cleanup(r);
                        xcb_destroy_window(conn, r->id);
+                       r->id = XCB_WINDOW_NONE;
                        TAILQ_REMOVE(&s->rl, r, entry);
                        TAILQ_INSERT_TAIL(&s->orl, r, entry);
                }
@@ -10026,6 +11491,7 @@ new_region(struct swm_screen *s, int x, int y, int w, int h)
        Y(r) = y;
        WIDTH(r) = w;
        HEIGHT(r) = h;
+       r->bar = NULL;
        r->s = s;
        r->ws = ws;
        r->ws_prior = NULL;
@@ -10050,14 +11516,13 @@ new_region(struct swm_screen *s, int x, int y, int w, int h)
 }
 
 void
-scan_xrandr(int i)
+scan_randr(int idx)
 {
 #ifdef SWM_XRR_HAS_CRTC
        int                                             c;
        int                                             ncrtc = 0;
 #endif /* SWM_XRR_HAS_CRTC */
        struct swm_region                               *r;
-       struct ws_win                                   *win;
        int                                             num_screens;
        xcb_randr_get_screen_resources_current_cookie_t src;
        xcb_randr_get_screen_resources_current_reply_t  *srr;
@@ -10066,34 +11531,35 @@ scan_xrandr(int i)
        xcb_randr_crtc_t                                *crtc;
        xcb_screen_t                                    *screen;
 
-       DNPRINTF(SWM_D_MISC, "scan_xrandr: screen: %d\n", i);
+       DNPRINTF(SWM_D_MISC, "scan_randr: screen: %d\n", idx);
 
-       if ((screen = get_screen(i)) == NULL)
-               errx(1, "ERROR: can't get screen %d.", i);
+       if ((screen = get_screen(idx)) == NULL)
+               errx(1, "ERROR: can't get screen %d.", idx);
 
        num_screens = get_screen_count();
-       if (i >= num_screens)
-               errx(1, "scan_xrandr: invalid screen");
+       if (idx >= num_screens)
+               errx(1, "scan_randr: invalid screen");
 
        /* remove any old regions */
-       while ((r = TAILQ_FIRST(&screens[i].rl)) != NULL) {
+       while ((r = TAILQ_FIRST(&screens[idx].rl)) != NULL) {
                r->ws->old_r = r->ws->r = NULL;
                bar_cleanup(r);
                xcb_destroy_window(conn, r->id);
-               TAILQ_REMOVE(&screens[i].rl, r, entry);
-               TAILQ_INSERT_TAIL(&screens[i].orl, r, entry);
+               r->id = XCB_WINDOW_NONE;
+               TAILQ_REMOVE(&screens[idx].rl, r, entry);
+               TAILQ_INSERT_TAIL(&screens[idx].orl, r, entry);
        }
        outputs = 0;
 
        /* map virtual screens onto physical screens */
 #ifdef SWM_XRR_HAS_CRTC
-       if (xrandr_support) {
+       if (randr_support) {
                src = xcb_randr_get_screen_resources_current(conn,
-                   screens[i].root);
+                   screens[idx].root);
                srr = xcb_randr_get_screen_resources_current_reply(conn, src,
                    NULL);
                if (srr == NULL) {
-                       new_region(&screens[i], 0, 0,
+                       new_region(&screens[idx], 0, 0,
                            screen->width_in_pixels,
                            screen->height_in_pixels);
                        goto out;
@@ -10113,11 +11579,11 @@ scan_xrandr(int i)
                        }
 
                        if (cir->mode == 0)
-                               new_region(&screens[i], 0, 0,
+                               new_region(&screens[idx], 0, 0,
                                    screen->width_in_pixels,
                                    screen->height_in_pixels);
                        else
-                               new_region(&screens[i],
+                               new_region(&screens[idx],
                                    cir->x, cir->y, cir->width, cir->height);
                        free(cir);
                }
@@ -10126,30 +11592,27 @@ scan_xrandr(int i)
 #endif /* SWM_XRR_HAS_CRTC */
 
        /* If detection failed, create a single region that spans the screen. */
-       if (TAILQ_EMPTY(&screens[i].rl))
-               new_region(&screens[i], 0, 0, screen->width_in_pixels,
+       if (TAILQ_EMPTY(&screens[idx].rl))
+               new_region(&screens[idx], 0, 0, screen->width_in_pixels,
                    screen->height_in_pixels);
 
 out:
-       /* Cleanup unused previously visible workspaces. */
-       TAILQ_FOREACH(r, &screens[i].orl, entry) {
-               TAILQ_FOREACH(win, &r->ws->winlist, entry)
-                       unmap_window(win);
-               r->ws->state = SWM_WS_STATE_HIDDEN;
-
-               /* The screen shouldn't focus on an unused region. */
-               if (screens[i].r_focus == r)
-                       screens[i].r_focus = NULL;
+       /* The screen shouldn't focus on unused regions. */
+       TAILQ_FOREACH(r, &screens[idx].orl, entry) {
+               if (screens[idx].r_focus == r)
+                       screens[idx].r_focus = NULL;
        }
 
-       DNPRINTF(SWM_D_MISC, "scan_xrandr: done.\n");
+       DNPRINTF(SWM_D_MISC, "scan_randr: done.\n");
 }
 
 void
 screenchange(xcb_randr_screen_change_notify_event_t *e)
 {
        struct swm_region               *r;
-       int                             i, num_screens;
+       struct workspace                *ws;
+       struct ws_win                   *win;
+       int                             i, j, num_screens;
 
        DNPRINTF(SWM_D_EVENT, "screenchange: root: %#x\n", e->root);
 
@@ -10162,51 +11625,55 @@ screenchange(xcb_randr_screen_change_notify_event_t *e)
                errx(1, "screenchange: screen not found");
 
        /* brute force for now, just re-enumerate the regions */
-       scan_xrandr(i);
+       scan_randr(i);
 
 #ifdef SWM_DEBUG
        print_win_geom(e->root);
 #endif
        /* add bars to all regions */
-       for (i = 0; i < num_screens; i++) {
-               TAILQ_FOREACH(r, &screens[i].rl, entry)
-                       bar_setup(r);
-       }
+       TAILQ_FOREACH(r, &screens[i].rl, entry)
+               bar_setup(r);
 
-       stack();
+       /* Stack all regions. */
+       TAILQ_FOREACH(r, &screens[i].rl, entry)
+               stack(r);
 
-       /* Make sure a region has focus on each screen. */
-       for (i = 0; i < num_screens; i++) {
-               if (screens[i].r_focus == NULL) {
-                       r = TAILQ_FIRST(&screens[i].rl);
-                       if (r != NULL)
-                               focus_region(r);
+       /* Make sure a region has focus. */
+       if (screens[i].r_focus == NULL) {
+               r = TAILQ_FIRST(&screens[i].rl);
+               if (r != NULL)
+                       focus_region(r);
+       }
+
+       /* Cleanup unused previously visible workspaces. */
+       for (j = 0; j < workspace_limit; j++) {
+               ws = &screens[i].ws[j];
+               if (ws->r == NULL && ws->state != SWM_WS_STATE_HIDDEN) {
+                       TAILQ_FOREACH(win, &ws->winlist, entry)
+                               unmap_window(win);
+                       ws->state = SWM_WS_STATE_HIDDEN;
                }
        }
 
-       bar_draw();
        focus_flush();
 
-       /* Update workspace state on all regions. */
-       for (i = 0; i < num_screens; i++)
-               TAILQ_FOREACH(r, &screens[i].rl, entry)
-                       r->ws->state = SWM_WS_STATE_MAPPED;
+       /* Update workspace state and bar on all regions. */
+       TAILQ_FOREACH(r, &screens[i].rl, entry) {
+               r->ws->state = SWM_WS_STATE_MAPPED;
+               bar_draw(r->bar);
+       }
 }
 
 void
 grab_windows(void)
 {
-       struct swm_region       *r = NULL;
-       xcb_window_t            *wins = NULL, trans, *cwins = NULL;
-       int                     i, j, k, n, no, num_screens, manage, mapped;
-       uint8_t                 state;
-
-       xcb_query_tree_cookie_t                 qtc;
-       xcb_query_tree_reply_t                  *qtr;
-       xcb_get_window_attributes_cookie_t      gac;
-       xcb_get_window_attributes_reply_t       *gar;
-       xcb_get_property_cookie_t               pc;
-       xcb_get_property_reply_t                *pr;
+       struct swm_region               *r = NULL;
+       xcb_query_tree_cookie_t         qtc;
+       xcb_query_tree_reply_t          *qtr;
+       xcb_get_property_cookie_t       pc;
+       xcb_get_property_reply_t        *pr;
+       xcb_window_t                    *wins = NULL, trans, *cwins = NULL;
+       int                             i, j, k, n, no, num_screens;
 
        DNPRINTF(SWM_D_INIT, "grab_windows: begin\n");
        num_screens = get_screen_count();
@@ -10240,9 +11707,9 @@ grab_windows(void)
                        free(pr);
                }
 
-               /* attach windows to a region */
-               /* normal windows */
-               DNPRINTF(SWM_D_INIT, "grab_windows: grab top level windows.\n");
+               /* Manage top-level windows first, then transients. */
+               /* TODO: allow transients to be managed before leader. */
+               DNPRINTF(SWM_D_INIT, "grab_windows: grab top-level windows.\n");
                for (j = 0; j < no; j++) {
                        TAILQ_FOREACH(r, &screens[i].rl, entry) {
                                if (r->id == wins[j]) {
@@ -10261,63 +11728,23 @@ grab_windows(void)
                        if (r)
                                continue;
 
-                       gac = xcb_get_window_attributes(conn, wins[j]);
-                       gar = xcb_get_window_attributes_reply(conn, gac, NULL);
-                       if (gar == NULL) {
-                               DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
-                                   "doesn't exist.\n", wins[j]);
-                               continue;
-                       }
-
-                       if (gar->override_redirect) {
-                               DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
-                                   "override_redirect set.\n", wins[j]);
-                               free(gar);
-                               continue;
-                       }
-
                        pc = xcb_icccm_get_wm_transient_for(conn, wins[j]);
                        if (xcb_icccm_get_wm_transient_for_reply(conn, pc,
                            &trans, NULL)) {
                                DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
                                    "is transient for %#x.\n", wins[j], trans);
-                               free(gar);
                                continue;
                        }
 
-                       state = get_win_state(wins[j]);
-                       manage = state != XCB_ICCCM_WM_STATE_WITHDRAWN;
-                       mapped = gar->map_state == XCB_MAP_STATE_VIEWABLE;
-                       if (mapped || manage)
-                               manage_window(wins[j], mapped);
-                       free(gar);
+                       manage_window(wins[j], SWM_STACK_TOP, false);
                }
-               /* transient windows */
+
                DNPRINTF(SWM_D_INIT, "grab_windows: grab transient windows.\n");
                for (j = 0; j < no; j++) {
-                       gac = xcb_get_window_attributes(conn, wins[j]);
-                       gar = xcb_get_window_attributes_reply(conn, gac, NULL);
-                       if (gar == NULL) {
-                               DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
-                                   "doesn't exist.\n", wins[j]);
-                               continue;
-                       }
-
-                       if (gar->override_redirect) {
-                               DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
-                                   "override_redirect set.\n", wins[j]);
-                               free(gar);
-                               continue;
-                       }
-
-                       state = get_win_state(wins[j]);
-                       manage = state != XCB_ICCCM_WM_STATE_WITHDRAWN;
-                       mapped = gar->map_state == XCB_MAP_STATE_VIEWABLE;
                        pc = xcb_icccm_get_wm_transient_for(conn, wins[j]);
                        if (xcb_icccm_get_wm_transient_for_reply(conn, pc,
-                           &trans, NULL) && manage)
-                               manage_window(wins[j], mapped);
-                       free(gar);
+                           &trans, NULL))
+                               manage_window(wins[j], SWM_STACK_TOP, false);
                }
                free(qtr);
        }
@@ -10336,21 +11763,20 @@ setup_screens(void)
        xcb_randr_query_version_reply_t         *r;
 
        num_screens = get_screen_count();
-       if ((screens = calloc(num_screens,
-            sizeof(struct swm_screen))) == NULL)
+       if ((screens = calloc(num_screens, sizeof(struct swm_screen))) == NULL)
                err(1, "setup_screens: calloc: failed to allocate memory for "
                    "screens");
 
-       /* initial Xrandr setup */
-       xrandr_support = 0;
+       /* Initial RandR setup. */
+       randr_support = false;
        qep = xcb_get_extension_data(conn, &xcb_randr_id);
        if (qep->present) {
                c = xcb_randr_query_version(conn, 1, 1);
                r = xcb_randr_query_version_reply(conn, c, NULL);
                if (r) {
                        if (r->major_version >= 1) {
-                               xrandr_support = 1;
-                               xrandr_eventbase = qep->first_event;
+                               randr_support = true;
+                               randr_eventbase = qep->first_event;
                        }
                        free(r);
                }
@@ -10371,15 +11797,16 @@ setup_screens(void)
                screens[i].root = screen->root;
 
                /* set default colors */
-               setscreencolor("red", i + 1, SWM_S_COLOR_FOCUS);
-               setscreencolor("rgb:88/88/88", i + 1, SWM_S_COLOR_UNFOCUS);
-               setscreencolor("rgb:00/80/80", i + 1, SWM_S_COLOR_BAR_BORDER);
-               setscreencolor("rgb:00/40/40", i + 1,
+               setscreencolor("red", i, SWM_S_COLOR_FOCUS);
+               setscreencolor("rgb:88/88/88", i, SWM_S_COLOR_UNFOCUS);
+               setscreencolor("rgb:00/80/80", i, SWM_S_COLOR_BAR_BORDER);
+               setscreencolor("rgb:00/40/40", i,
                    SWM_S_COLOR_BAR_BORDER_UNFOCUS);
-               setscreencolor("black", i + 1, SWM_S_COLOR_BAR);
-               setscreencolor("rgb:a0/a0/a0", i + 1, SWM_S_COLOR_BAR_FONT);
-               setscreencolor("red", i + 1, SWM_S_COLOR_FOCUS_MAXIMIZED);
-               setscreencolor("rgb:88/88/88", i + 1, SWM_S_COLOR_UNFOCUS_MAXIMIZED);
+               setscreencolor("black", i, SWM_S_COLOR_BAR);
+               setscreencolor("rgb:a0/a0/a0", i, SWM_S_COLOR_BAR_FONT);
+               setscreencolor("red", i, SWM_S_COLOR_FOCUS_MAXIMIZED);
+               setscreencolor("rgb:88/88/88", i,
+                   SWM_S_COLOR_UNFOCUS_MAXIMIZED);
 
                /* create graphics context on screen */
                screens[i].bar_gc = xcb_generate_id(conn);
@@ -10397,10 +11824,11 @@ setup_screens(void)
                        ws = &screens[i].ws[j];
                        ws->idx = j;
                        ws->name = NULL;
-                       ws->bar_enabled = 1;
+                       ws->bar_enabled = true;
                        ws->focus = NULL;
                        ws->focus_prev = NULL;
                        ws->focus_pending = NULL;
+                       ws->focus_raise = NULL;
                        ws->r = NULL;
                        ws->old_r = NULL;
                        ws->state = SWM_WS_STATE_HIDDEN;
@@ -10416,9 +11844,9 @@ setup_screens(void)
                        ws->cur_layout->l_string(ws);
                }
 
-               scan_xrandr(i);
+               scan_randr(i);
 
-               if (xrandr_support)
+               if (randr_support)
                        xcb_randr_select_input(conn, screens[i].root,
                            XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
        }
@@ -10439,6 +11867,7 @@ setup_globals(void)
        a_state = get_atom_from_string("WM_STATE");
        a_prot = get_atom_from_string("WM_PROTOCOLS");
        a_delete = get_atom_from_string("WM_DELETE_WINDOW");
+       a_net_frame_extents = get_atom_from_string("_NET_FRAME_EXTENTS");
        a_net_supported = get_atom_from_string("_NET_SUPPORTED");
        a_net_wm_check = get_atom_from_string("_NET_SUPPORTING_WM_CHECK");
        a_takefocus = get_atom_from_string("WM_TAKE_FOCUS");
@@ -10449,7 +11878,10 @@ setup_globals(void)
 void
 shutdown_cleanup(void)
 {
-       int i, num_screens;
+       struct swm_region       *r;
+       struct ws_win           *w;
+       struct workspace        *ws;
+       int                     i, num_screens;
 
        /* disable alarm because the following code may not be interrupted */
        alarm(0);
@@ -10461,28 +11893,78 @@ shutdown_cleanup(void)
 
        cursors_cleanup();
 
+       clear_quirks();
+       clear_spawns();
+       clear_bindings();
+
        teardown_ewmh();
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; ++i) {
+               int j;
+
+               DNPRINTF(SWM_D_FOCUS, "shutdown_cleanup: set_input_focus: "
+                   "%#x, revert-to: root, time: 0\n", screens[i].root);
                xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
                    screens[i].root, XCB_CURRENT_TIME);
 
                if (screens[i].bar_gc != XCB_NONE)
                        xcb_free_gc(conn, screens[i].bar_gc);
-               if (!bar_font_legacy)
+               if (!bar_font_legacy) {
                        XftColorFree(display, DefaultVisual(display, i),
                            DefaultColormap(display, i), &bar_font_color);
+                       XftColorFree(display, DefaultVisual(display, i),
+                           DefaultColormap(display, i), &search_font_color);
+               }
+
+               for (j = 0; j < SWM_S_COLOR_MAX; ++j) {
+                       free(screens[i].c[j].name);
+               }
+
+               /* Free window memory. */
+               for (j = 0; j < SWM_WS_MAX; ++j) {
+                       ws = &screens[i].ws[j];
+                       free(ws->name);
+
+                       while ((w = TAILQ_FIRST(&ws->winlist)) != NULL) {
+                               TAILQ_REMOVE(&ws->winlist, w, entry);
+                               free_window(w);
+                       }
+
+                       while ((w = TAILQ_FIRST(&ws->unmanagedlist)) != NULL) {
+                               TAILQ_REMOVE(&ws->unmanagedlist, w, entry);
+                               free_window(w);
+                       }
+               }
+
+               /* Free region memory. */
+               while ((r = TAILQ_FIRST(&screens[i].rl)) != NULL) {
+                       TAILQ_REMOVE(&screens[i].rl, r, entry);
+                       free(r->bar);
+                       free(r);
+               }
+
+               while ((r = TAILQ_FIRST(&screens[i].orl)) != NULL) {
+                       TAILQ_REMOVE(&screens[i].rl, r, entry);
+                       free(r->bar);
+                       free(r);
+               }
        }
+       free(screens);
 
-       if (bar_font_legacy)
+       free(bar_format);
+       free(bar_fonts);
+       free(clock_format);
+       free(startup_exception);
+
+       if (bar_fs)
                XFreeFontSet(display, bar_fs);
-       else {
+       if (bar_font)
                XftFontClose(display, bar_font);
-       }
 
        xcb_key_symbols_free(syms);
        xcb_flush(conn);
+       xcb_aux_sync(conn);
        xcb_disconnect(conn);
 }
 
@@ -10501,7 +11983,7 @@ event_error(xcb_generic_error_t *e)
 void
 event_handle(xcb_generic_event_t *evt)
 {
-       uint8_t type = XCB_EVENT_RESPONSE_TYPE(evt);
+       uint8_t                 type = XCB_EVENT_RESPONSE_TYPE(evt);
 
        DNPRINTF(SWM_D_EVENT, "XCB Event: %s(%d), seq %u\n",
            xcb_event_get_label(XCB_EVENT_RESPONSE_TYPE(evt)),
@@ -10511,7 +11993,7 @@ event_handle(xcb_generic_event_t *evt)
 #define EVENT(type, callback) case type: callback((void *)evt); return
        EVENT(0, event_error);
        EVENT(XCB_BUTTON_PRESS, buttonpress);
-       /*EVENT(XCB_BUTTON_RELEASE, buttonpress);*/
+       EVENT(XCB_BUTTON_RELEASE, buttonrelease);
        /*EVENT(XCB_CIRCULATE_NOTIFY, );*/
        /*EVENT(XCB_CIRCULATE_REQUEST, );*/
        EVENT(XCB_CLIENT_MESSAGE, clientmessage);
@@ -10522,14 +12004,14 @@ event_handle(xcb_generic_event_t *evt)
        EVENT(XCB_DESTROY_NOTIFY, destroynotify);
        EVENT(XCB_ENTER_NOTIFY, enternotify);
        EVENT(XCB_EXPOSE, expose);
-#ifdef SWM_DEBUG
        EVENT(XCB_FOCUS_IN, focusin);
+#ifdef SWM_DEBUG
        EVENT(XCB_FOCUS_OUT, focusout);
 #endif
        /*EVENT(XCB_GRAPHICS_EXPOSURE, );*/
        /*EVENT(XCB_GRAVITY_NOTIFY, );*/
        EVENT(XCB_KEY_PRESS, keypress);
-       /*EVENT(XCB_KEY_RELEASE, keypress);*/
+       EVENT(XCB_KEY_RELEASE, keyrelease);
        /*EVENT(XCB_KEYMAP_NOTIFY, );*/
 #ifdef SWM_DEBUG
        EVENT(XCB_LEAVE_NOTIFY, leavenotify);
@@ -10540,7 +12022,7 @@ event_handle(xcb_generic_event_t *evt)
        EVENT(XCB_MOTION_NOTIFY, motionnotify);
        /*EVENT(XCB_NO_EXPOSURE, );*/
        EVENT(XCB_PROPERTY_NOTIFY, propertynotify);
-       /*EVENT(XCB_REPARENT_NOTIFY, );*/
+       EVENT(XCB_REPARENT_NOTIFY, reparentnotify);
        /*EVENT(XCB_RESIZE_REQUEST, );*/
        /*EVENT(XCB_SELECTION_CLEAR, );*/
        /*EVENT(XCB_SELECTION_NOTIFY, );*/
@@ -10549,29 +12031,29 @@ event_handle(xcb_generic_event_t *evt)
        /*EVENT(XCB_VISIBILITY_NOTIFY, );*/
 #undef EVENT
        }
-       if (type - xrandr_eventbase == XCB_RANDR_SCREEN_CHANGE_NOTIFY)
+       if (type - randr_eventbase == XCB_RANDR_SCREEN_CHANGE_NOTIFY)
                screenchange((void *)evt);
 }
 
 int
 main(int argc, char *argv[])
 {
-       struct swm_region       *r;
-       char                    conf[PATH_MAX], *cfile = NULL;
-       struct stat             sb;
-       int                     xfd, i, num_screens, startup = 1;
+       struct pollfd           pfd[2];
        struct sigaction        sact;
+       struct stat             sb;
+       struct passwd           *pwd;
+       struct swm_region       *r;
        xcb_generic_event_t     *evt;
-       struct timeval          tv;
-       fd_set                  rd;
-       int                     rd_max;
-       int                     stdin_ready = 0;
-       int                     num_readable;
+       int                     xfd, i, num_screens, num_readable;
+       char                    conf[PATH_MAX], *cfile = NULL;
+       bool                    stdin_ready = false, startup = true;
 
        /* suppress unused warning since var is needed */
        (void)argc;
 
+#ifdef SWM_DEBUG
        time_started = time(NULL);
+#endif
 
        start_argv = argv;
        warnx("Welcome to spectrwm V%s Build: %s", SPECTRWM_VERSION, buildstr);
@@ -10613,7 +12095,7 @@ main(int argc, char *argv[])
        xcb_aux_sync(conn);
 
        /* flush all events */
-       while ((evt = xcb_poll_for_event(conn))) {
+       while ((evt = get_next_event(false))) {
                if (XCB_EVENT_RESPONSE_TYPE(evt) == 0)
                        event_handle(evt);
                free(evt);
@@ -10630,7 +12112,8 @@ main(int argc, char *argv[])
        setup_globals();
        setup_screens();
        setup_ewmh();
-       setup_keys();
+       setup_keybindings();
+       setup_btnbindings();
        setup_quirks();
        setup_spawn();
 
@@ -10689,21 +12172,31 @@ noconfig:
        grab_windows();
 
        grabkeys();
-       stack();
-       bar_draw();
+       grabbuttons();
+
+       /* Stack all regions to trigger mapping. */
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       stack(r);
 
        xcb_ungrab_server(conn);
        xcb_flush(conn);
 
-       /* Update state of each newly mapped workspace. */
+       /* Update state and bar of each newly mapped workspace. */
        for (i = 0; i < num_screens; i++)
-               TAILQ_FOREACH(r, &screens[i].rl, entry)
+               TAILQ_FOREACH(r, &screens[i].rl, entry) {
                        r->ws->state = SWM_WS_STATE_MAPPED;
+                       bar_draw(r->bar);
+               }
 
-       rd_max = xfd > STDIN_FILENO ? xfd : STDIN_FILENO;
+       memset(&pfd, 0, sizeof(pfd));
+       pfd[0].fd = xfd;
+       pfd[0].events = POLLIN;
+       pfd[1].fd = STDIN_FILENO;
+       pfd[1].events = POLLIN;
 
        while (running) {
-               while ((evt = xcb_poll_for_event(conn))) {
+               while ((evt = get_next_event(false))) {
                        if (!running)
                                goto done;
                        event_handle(evt);
@@ -10712,7 +12205,7 @@ noconfig:
 
                /* If just (re)started, set default focus if needed. */
                if (startup) {
-                       startup = 0;
+                       startup = false;
 
                        if (focus_mode != SWM_FOCUS_FOLLOW) {
                                r = TAILQ_FIRST(&screens[0].rl);
@@ -10724,36 +12217,34 @@ noconfig:
                        }
                }
 
-               FD_ZERO(&rd);
-
-               if (bar_extra)
-                       FD_SET(STDIN_FILENO, &rd);
+               if (search_resp)
+                       search_do_resp();
 
-               FD_SET(xfd, &rd);
-               tv.tv_sec = 1;
-               tv.tv_usec = 0;
-               num_readable = select(rd_max + 1, &rd, NULL, NULL, &tv);
-               if (num_readable == -1 && errno != EINTR) {
-                       DNPRINTF(SWM_D_MISC, "select failed");
-               } else if (num_readable > 0 && FD_ISSET(STDIN_FILENO, &rd)) {
-                       stdin_ready = 1;
+               num_readable = poll(pfd, bar_extra ? 2 : 1, 1000);
+               if (num_readable == -1) {
+                       DNPRINTF(SWM_D_MISC, "poll failed: %s",
+                           strerror(errno));
+               } else if (num_readable > 0 && bar_extra &&
+                   pfd[1].revents & POLLIN) {
+                       stdin_ready = true;
                }
 
                if (restart_wm)
-                       restart(NULL, NULL);
-
-               if (search_resp)
-                       search_do_resp();
+                       restart(NULL, NULL, NULL);
 
                if (!running)
                        goto done;
 
                if (stdin_ready) {
-                       stdin_ready = 0;
+                       stdin_ready = false;
                        bar_extra_update();
                }
 
-               bar_draw();
+               /* Need to ensure the bar(s) are always updated. */
+               for (i = 0; i < num_screens; i++)
+                       TAILQ_FOREACH(r, &screens[i].rl, entry)
+                               bar_draw(r->bar);
+
                xcb_flush(conn);
        }
 done: