]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Fix man errors
[spectrwm] / spectrwm.c
index 2c839d9f86984fa78e1f17512bd4f373d2ab0ee3..285bac959e8cdee7b8b29241543dc0e0ab14d1dc 100644 (file)
@@ -5,7 +5,7 @@
  * 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-2015 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-2015 David Hill <dhill@mindcry.org>
  * 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__)
@@ -97,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>
@@ -235,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)
@@ -261,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)
@@ -288,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)
 
@@ -310,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;
@@ -332,10 +305,10 @@ 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;
-unsigned int           mouse_button_move = XCB_BUTTON_INDEX_1;
-unsigned int           mouse_button_resize = XCB_BUTTON_INDEX_3;
+bool                   workspace_clamp = false;
 
 /* dmenu search */
 struct swm_region      *search_r;
@@ -401,7 +374,7 @@ bool                 bar_at_bottom = false;
 bool            bar_extra = false;
 int             bar_height = 0;
 int             bar_justify = SWM_BAR_JUSTIFY_LEFT;
-char            *bar_format = NULL;
+char           *bar_format = NULL;
 bool            stack_enabled = true;
 bool            clock_enabled = true;
 bool            iconic_enabled = false;
@@ -424,18 +397,18 @@ int                tile_gap = 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;
+XftFont                *bar_font = NULL;
 bool            bar_font_legacy = true;
-char           *bar_fonts;
+char           *bar_fonts = NULL;
 XftColor        bar_font_color;
 XftColor        search_font_color;
-struct passwd  *pwd;
-char           *startup_exception;
+char           *startup_exception = NULL;
 unsigned int    nr_exceptions = 0;
 
 /* layout manager data */
@@ -453,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" */
@@ -467,16 +441,26 @@ 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 */
        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];
@@ -491,6 +475,9 @@ struct ws_win {
        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);
@@ -505,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);
@@ -548,6 +535,7 @@ struct workspace {
        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 */
@@ -640,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)
@@ -654,8 +640,6 @@ 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)
@@ -674,18 +658,19 @@ struct quirk {
        regex_t                 regex_name;
        uint32_t                quirk;
        int                     ws;             /* Initial workspace. */
-#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 */
+#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);
@@ -704,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,
@@ -747,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},
@@ -821,137 +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_MOVE_NEXT,
-       KF_RG_MOVE_PREV,
-       KF_RG_NEXT,
-       KF_RG_PREV,
-       KF_SCREEN_NEXT,
-       KF_SCREEN_PREV,
-       KF_SEARCH_WIN,
-       KF_SEARCH_WORKSPACE,
-       KF_SPAWN_CUSTOM,
-       KF_STACK_BALANCE,
-       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 *);
@@ -962,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 *);
@@ -978,11 +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);
@@ -994,11 +1016,15 @@ 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);
@@ -1018,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);
@@ -1051,25 +1083,26 @@ 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(struct ws_win *);
-void    grabbuttons(struct ws_win *);
-void    grabkeys(void);
 void    grab_windows(void);
-void    iconify(struct swm_region *, union arg *);
+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
@@ -1082,36 +1115,41 @@ 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 *, uint32_t *, int *);
 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 *, uint32_t, int);
 void    quirk_remove(struct quirk *);
 void    quirk_replace(struct quirk *, const char *, const char *, const char *,
             uint32_t, int);
-void    quit(struct swm_region *, union arg *);
-void    raise_toggle(struct swm_region *, union arg *);
+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_randr(int);
@@ -1120,31 +1158,32 @@ 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);
-int     setconfmousebuttonmove(const char *, const char *, int);
-int     setconfmousebuttonresize(const char *, const char *, int);
 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 *, 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);
@@ -1162,43 +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_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);
-unsigned char  update_mousebutton(unsigned char, 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    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)
@@ -1423,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);
@@ -1664,7 +1694,7 @@ ewmh_apply_flags(struct ws_win *win, uint32_t pending)
                        }
                }
 
-               update_window_color(win);
+               draw_frame(win);
                raise_window(win);
        }
 
@@ -1747,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;
@@ -1755,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) {
@@ -1768,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
@@ -1779,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)));
        }
 
@@ -1801,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;
 }
@@ -1969,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, "
@@ -2227,7 +2419,7 @@ bar_urgent(char *s, size_t sz)
                        strlcat(s, "- ", sz);
                }
        }
-       if(urgent_collapse && s[0])
+       if (urgent_collapse && s[0])
                s[strlen(s) - 1] = 0;
 }
 
@@ -2281,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);
@@ -2468,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);
 }
 
 /*
@@ -2559,12 +2748,13 @@ bar_extra_update(void)
 }
 
 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;
 
@@ -2594,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();
 }
@@ -2675,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);
@@ -2710,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;
@@ -2740,7 +2934,7 @@ xft_init(struct swm_region *r)
                                break;
                        }
                }
-               free(d);
+               free(str);
        }
 
        if (bar_font == NULL)
@@ -2787,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;
@@ -2847,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;
@@ -2877,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;
 
@@ -2890,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
@@ -2940,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 */
@@ -2991,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;
        }
 
@@ -3024,9 +3228,10 @@ count_win(struct workspace *ws, bool 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;
 
@@ -3040,13 +3245,13 @@ lower_window(struct ws_win *win)
        struct ws_win           *target = NULL;
        struct workspace        *ws;
 
+       DNPRINTF(SWM_D_EVENT, "lower_window: win %#x\n", WINID(win));
+
        if (win == NULL)
                return;
 
        ws = win->ws;
 
-       DNPRINTF(SWM_D_EVENT, "lower_window: win %#x\n", win->id);
-
        TAILQ_FOREACH(target, &ws->stack, stack_entry) {
                if (target == win || ICONIC(target))
                        continue;
@@ -3101,11 +3306,12 @@ raise_window(struct ws_win *win)
        struct ws_win           *target = NULL;
        struct workspace        *ws;
 
+       DNPRINTF(SWM_D_EVENT, "raise_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))
@@ -3156,29 +3362,43 @@ 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->id;
+               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: %#x, sibling %#x\n",
-           win->id, val[0]);
+       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->id, XCB_CONFIG_WINDOW_SIBLING |
+       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
@@ -3193,6 +3413,7 @@ map_window(struct ws_win *win)
        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;
@@ -3211,6 +3432,7 @@ 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 = false;
 }
@@ -3267,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;
 
@@ -3279,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 *
@@ -3318,7 +3541,7 @@ center_pointer(struct swm_region *r)
        DNPRINTF(SWM_D_EVENT, "center_pointer: win %#x.\n", WINID(win));
 
        if (win && win->mapped)
-               xcb_warp_pointer(conn, XCB_NONE, win->id, 0, 0, 0, 0,
+               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,
@@ -3383,73 +3606,126 @@ 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 ws_win *
-find_window(xcb_window_t id)
+struct swm_bar *
+find_bar(xcb_window_t id)
 {
-       struct ws_win           *win;
-       int                     i, j, num_screens;
-       xcb_query_tree_reply_t  *r;
+       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].winlist, entry)
-                               if (id == win->id)
-                                       return (win);
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       if (r->bar && r->bar->id == id)
+                               return r->bar;
 
-       r = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
-       if (r == NULL)
-               return (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);
-       }
+struct ws_win *
+find_frame_window(xcb_window_t id) {
+       struct swm_region       *r;
+       struct ws_win           *w;
+       int                     i, num_screens;
 
-       /* look for parent */
+       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);
-                                       return (win);
-                               }
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       TAILQ_FOREACH(w, &r->ws->winlist, entry)
+                               if (w->frame == id)
+                                       return w;
 
-       free(r);
-       return (NULL);
+       return NULL;
 }
 
-void
-spawn(int ws_idx, union arg *args, bool close_fd)
+struct ws_win *
+find_window(xcb_window_t id)
 {
-       int                     fd;
-       char                    *ret = NULL;
+       struct ws_win           *win = NULL;
+       int                     i, j, num_screens;
+       xcb_query_tree_reply_t  *qtr;
 
-       DNPRINTF(SWM_D_MISC, "spawn: %s\n", args->argv[0]);
+       DNPRINTF(SWM_D_MISC, "find_window: id: %#x\n", id);
 
-       close(xcb_get_file_descriptor(conn));
+       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 || id == win->frame)
+                                       return (win);
 
-       setenv("LD_PRELOAD", SWM_LIB, 1);
 
-       if (asprintf(&ret, "%d", ws_idx) == -1) {
-               warn("spawn: asprintf SWM_WS");
+       /* 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);
+       }
+
+       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].unmanagedlist,
+                           entry)
+                               if (id == win->id)
+                                       return (win);
+       return (NULL);
+}
+
+void
+spawn(int ws_idx, union arg *args, bool close_fd)
+{
+       int                     fd;
+       char                    *ret = NULL;
+
+       DNPRINTF(SWM_D_MISC, "spawn: %s\n", args->argv[0]);
+
+       close(xcb_get_file_descriptor(conn));
+
+       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");
                _exit(1);
        }
        setenv("_SWM_WS", ret, 1);
@@ -3494,23 +3770,33 @@ spawn(int ws_idx, union arg *args, bool 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
@@ -3589,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)) {
@@ -3601,12 +3890,21 @@ 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);
 
@@ -3618,7 +3916,8 @@ focus_win(struct ws_win *win)
 {
        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));
 
@@ -3637,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) {
@@ -3659,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)) {
@@ -3670,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) {
@@ -3732,15 +4050,13 @@ focus_win(struct ws_win *win)
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
                    ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1,
                    &win->id);
-       }
 
-       if (cfw != win)
-               /* Update window border even if workspace is hidden. */
-               update_window_color(win);
+               bar_draw(ws->r->bar);
+       }
 
 out:
-       bar_draw();
-
+       free(gifr);
+       free(war);
        DNPRINTF(SWM_D_FOCUS, "focus_win: done.\n");
 }
 
@@ -3785,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);
 
@@ -3829,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();
 }
 
@@ -3849,19 +4169,22 @@ 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;
@@ -3888,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 = true;
-       } 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;
@@ -3920,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) {
@@ -3939,9 +4279,11 @@ 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();
@@ -3954,7 +4296,7 @@ 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;
@@ -3996,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;
 
@@ -4018,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))
@@ -4049,7 +4393,7 @@ focusrg(struct swm_region *r, union arg *args)
 }
 
 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;
@@ -4092,7 +4436,7 @@ cyclerg(struct swm_region *r, union arg *args)
        case SWM_ARG_ID_CYCLERG_MOVE_UP:
        case SWM_ARG_ID_CYCLERG_MOVE_DOWN:
                a.id = rr->ws->idx;
-               switchws(r, &a);
+               switchws(bp, r, &a);
                break;
        default:
                return;
@@ -4125,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);
 
@@ -4229,7 +4575,7 @@ swapwin(struct swm_region *r, union arg *args)
        sort_windows(wl);
        ewmh_update_client_list();
 
-       stack();
+       stack(r);
        center_pointer(r);
        focus_flush();
 out:
@@ -4377,7 +4723,7 @@ 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;
@@ -4479,7 +4825,7 @@ focus(struct swm_region *r, union arg *args)
                /* Switch ws if new focus is on a different ws. */
                if (winfocus && winfocus->ws != ws) {
                        a.id = winfocus->ws->idx;
-                       switchws(r, &a);
+                       switchws(bp, r, &a);
                }
                break;
        default:
@@ -4487,7 +4833,7 @@ 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);
@@ -4498,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);
@@ -4513,8 +4872,8 @@ 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));
 
@@ -4523,87 +4882,81 @@ cycle_layout(struct swm_region *r, union arg *args)
 }
 
 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--;
 
@@ -4652,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;
 
@@ -4660,8 +5015,6 @@ 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)) {
@@ -4687,6 +5040,9 @@ update_floater(struct ws_win *win)
                }
 
                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;
                }
@@ -4696,9 +5052,11 @@ 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. */
+               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)) {
@@ -4713,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);
                        }
                }
@@ -4765,18 +5121,28 @@ adjust_font(struct ws_win *win)
 void
 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;
+       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, false)) > 0) {
                /* Find first tiled window. */
@@ -4799,7 +5165,10 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool 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;
@@ -4812,26 +5181,27 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool 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;
        }
 
@@ -4855,24 +5225,24 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool 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;
@@ -4883,55 +5253,62 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool 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;
 
-               if (disable_border && !(bar_enabled && ws->bar_enabled) &&
-                   winno == 1) {
-                       bordered = false;
-                       win_g.w += 2 * border_width;
-                       win_g.h += 2 * border_width;
-               } else {
+               /* Window coordinates exclude frame. */
+
+               if (winno > 1 || !disable_border ||
+                   (bar_enabled && ws->bar_enabled)) {
                        bordered = true;
+               } else {
+                       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) {
+                       if (X(win) != cell.y || Y(win) != cell.x ||
+                           WIDTH(win) != cell.h || HEIGHT(win) != cell.w) {
                                reconfigure = true;
-                               X(win) = win_g.y;
-                               Y(win) = win_g.x;
-                               WIDTH(win) = win_g.h;
-                               HEIGHT(win) = win_g.w;
+                               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) {
+                       if (X(win) != cell.x || Y(win) != cell.y ||
+                           WIDTH(win) != cell.w || HEIGHT(win) != cell.h) {
                                reconfigure = true;
-                               X(win) = win_g.x;
-                               Y(win) = win_g.y;
-                               WIDTH(win) = win_g.w;
-                               HEIGHT(win) = win_g.h;
+                               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 = true;
                        win->bordered = bordered;
@@ -4942,7 +5319,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool flip)
                        update_window(win);
                }
 
-               last_h = win_g.h;
+               last_h = cell.h;
                i++;
                j++;
        }
@@ -5130,6 +5507,8 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
                                HEIGHT(w) += 2 * border_width;
                        } else {
                                w->bordered = true;
+                               X(w) += border_width;
+                               Y(w) += border_width;
                        }
 
                        update_window(w);
@@ -5160,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;
@@ -5182,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 *
@@ -5204,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
@@ -5231,7 +5612,7 @@ send_to_ws(struct swm_region *r, union arg *args)
                win->ws->focus_pending = NULL;
 
                if (win->ws->focus_prev)
-                       update_window_color(win->ws->focus_prev);
+                       draw_frame(win->ws->focus_prev);
        }
 
        DNPRINTF(SWM_D_STACK, "send_to_ws: focus_pending: %#x, focus: %#x, "
@@ -5243,19 +5624,27 @@ 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 on current ws. */
-       if (FLOATING(win))
-               load_float_geom(win);
+       /* Restack focused region. */
+       stack(r);
+
+       /* If destination ws has a region, restack. */
+       if (win->ws->r) {
+               if (FLOATING(win))
+                       load_float_geom(win);
 
-       stack();
+               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);
                }
        }
 
@@ -5263,6 +5652,30 @@ send_to_ws(struct swm_region *r, union arg *args)
        focus_flush();
 }
 
+/* Transfer focused window to region-relative workspace and focus. */
+void
+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)
 {
@@ -5358,9 +5771,10 @@ win_to_ws(struct ws_win *win, int wsid, bool 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,
@@ -5370,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)
+{
+       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)
@@ -5390,11 +5830,12 @@ raise_toggle(struct swm_region *r, union arg *args)
 }
 
 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 warning. */
+       (void)bp;
        (void)args;
 
        if ((w = r->ws->focus) == NULL)
@@ -5403,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();
 }
@@ -5442,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)
@@ -5491,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)
@@ -5513,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)
@@ -5556,7 +6003,7 @@ 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;
@@ -5573,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;
@@ -5622,7 +6071,7 @@ search_win(struct swm_region *r, union arg *args)
                        height = bar_font->height + 4;
                }
 
-               xcb_create_window(conn, screen->root_depth, 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,
                    screen->root_visual, XCB_CW_BACK_PIXEL |
                    XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, wa);
@@ -5690,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;
                }
@@ -5741,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.");
 
@@ -5847,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
@@ -5918,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
@@ -6014,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);
 
@@ -6049,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)
@@ -6070,7 +6525,7 @@ 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);
@@ -6081,11 +6536,12 @@ maximize_toggle(struct swm_region *r, union arg *args)
 }
 
 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)
@@ -6102,7 +6558,7 @@ 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);
@@ -6112,6 +6568,32 @@ floating_toggle(struct swm_region *r, union arg *args)
        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)
 {
@@ -6125,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,
@@ -6163,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) {
@@ -6200,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) ?
@@ -6213,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
@@ -6223,38 +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;
        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;
+       bool                    resizing, step = false;
 
        if (win == NULL)
                return;
@@ -6280,7 +6897,7 @@ resize(struct ws_win *win, union arg *args)
            ~EWMH_F_MAXIMIZED);
        ewmh_update_wm_state(win);
 
-       stack();
+       stack(r);
 
        focus_flush();
 
@@ -6290,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);
@@ -6336,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 :
@@ -6347,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 = true;
-       while (resizing && (evt = xcb_wait_for_event(conn))) {
+       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 = false;
+                       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;
@@ -6367,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;
 
@@ -6387,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;
 
@@ -6414,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);
@@ -6446,17 +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;
 
-       resize(win, args);
-       center_pointer(r);
+       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(win, bp, args->id);
+
+       if (args->id && bp->type == KEYBIND)
+               center_pointer(r);
+
        focus_flush();
 }
 
@@ -6471,12 +7127,15 @@ 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, false);
 
+               /* Stack old and new region. */
+               stack(r);
+               stack(win->ws->r);
+
                /* Set focus on new ws. */
                unfocus_win(r->ws->focus);
                r->ws->focus = win;
@@ -6489,15 +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;
        xcb_query_pointer_reply_t       *qpr = NULL;
        xcb_generic_event_t             *evt;
        xcb_motion_notify_event_t       *mne;
-       bool                            moving, restack = false;
+       bool                            moving, restack = false, step = false;
 
        if (win == NULL)
                return;
@@ -6525,7 +7183,7 @@ move(struct ws_win *win, union arg *args)
        ewmh_update_wm_state(win);
 
        if (restack)
-               stack();
+               stack(r);
 
        focus_flush();
 
@@ -6535,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);
@@ -6576,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 = true;
-       while (moving && (evt = xcb_wait_for_event(conn))) {
+       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 = false;
+                       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;
 
@@ -6604,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,
@@ -6613,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;
@@ -6631,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();
        }
 
@@ -6643,227 +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);
-       center_pointer(r);
-       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_move_next",       cyclerg,        {.id = SWM_ARG_ID_CYCLERG_MOVE_UP} },
-       { "rg_move_prev",       cyclerg,        {.id = SWM_ARG_ID_CYCLERG_MOVE_DOWN} },
-       { "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_balance",      stack_config,   {.id = SWM_ARG_ID_STACKBALANCE} },
-       { "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;
-}
-
-unsigned char
-update_mousebutton(unsigned char type, unsigned int but)
-{
-       int                     i;
-
-       switch (type) {
-               case 0:
-                       mouse_button_move = but;
-                       break;
-               case 1:
-                       mouse_button_resize = but;
-                       break;
-       }
-
-       for (i = 0; i < LENGTH(buttons); i++) {
-               if (buttons[i].func == move)
-                       buttons[i].button = mouse_button_move;
-
-               if (buttons[i].func == resize)
-                       buttons[i].button = mouse_button_resize;
-       }
-
-       return(1);
 }
 
 int
@@ -7132,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)
 {
@@ -7188,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;
 
@@ -7258,40 +7914,43 @@ 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)
@@ -7302,21 +7961,39 @@ parsekeys(const char *keystr, unsigned int currmod, unsigned int *mod, KeySym *k
                        *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);
 }
 
@@ -7329,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);
 
-       kp.keysym = ks;
-       kp.mod = mod;
+       if ((bp = malloc(sizeof *bp)) == NULL)
+               err(1, "binding_insert: malloc");
 
-       return (RB_FIND(key_tree, &keys, &kp));
+       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);
+
+       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);
+
+       if (aid != FN_INVALID)
+               binding_insert(mod, type, val, aid, flags, spawn_name);
 
-       key_insert(mod, ks, kfid, spawn_name);
-       DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
+       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;
@@ -7428,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);
@@ -7452,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
@@ -7463,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
+setup_btnbindings(void)
+{
+       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_keys(void)
+clear_keybindings(void)
 {
-       struct key              *kp;
+       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);
        }
 }
 
@@ -7616,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);
@@ -7664,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");
@@ -7683,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;
@@ -7722,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[] = {
@@ -7746,6 +8528,7 @@ const char *quirkname[] = {
        "IGNOREPID",
        "IGNORESPAWNWS",
        "NOFOCUSCYCLE",
+       "MINIMALBORDER",
 };
 
 /* SWM_Q_DELIM: retain '|' for back compat for now (2009-08-11) */
@@ -7759,10 +8542,9 @@ parsequirks(const char *qstr, uint32_t *quirk, int *ws)
        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_DELIM)) != NULL) {
                if (cp)
@@ -7886,6 +8668,16 @@ 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, uint32_t quirk, int ws)
@@ -8073,10 +8865,12 @@ enum {
        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,
 };
@@ -8086,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:
@@ -8131,11 +8925,11 @@ 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)) {
@@ -8291,6 +9085,9 @@ 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;
@@ -8303,6 +9100,9 @@ setconfvalue(const char *selector, const char *value, int flags)
        case SWM_S_WINDOW_NAME_ENABLED:
                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);
                if (workspace_limit > SWM_WS_MAX)
@@ -8366,48 +9166,6 @@ setconfmodkey(const char *selector, const char *value, int flags)
        return (0);
 }
 
-int
-setconfmousebuttonmove(const char *selector, const char *value, int flags)
-{
-       /* suppress unused warnings since vars are needed */
-       (void)selector;
-       (void)flags;
-
-       if (strncasecmp(value, "But1", strlen("But1")) == 0) {
-               if (!update_mousebutton(0, XCB_BUTTON_INDEX_1))
-                       return (1);
-       } else if (strncasecmp(value, "But2", strlen("But2")) == 0) {
-               if (!update_mousebutton(0, XCB_BUTTON_INDEX_2))
-                       return (1);
-       } else if (strncasecmp(value, "But3", strlen("But3")) == 0) {
-               if (!update_mousebutton(0, XCB_BUTTON_INDEX_3))
-                       return (1);
-       } else
-               return (1);
-       return (0);
-}
-
-int
-setconfmousebuttonresize(const char *selector, const char *value, int flags)
-{
-       /* suppress unused warnings since vars are needed */
-       (void)selector;
-       (void)flags;
-
-       if (strncasecmp(value, "But1", strlen("But1")) == 0) {
-               if (!update_mousebutton(1, XCB_BUTTON_INDEX_1))
-                       return (1);
-       } else if (strncasecmp(value, "But2", strlen("But2")) == 0) {
-               if (!update_mousebutton(1, XCB_BUTTON_INDEX_2))
-                       return (1);
-       } else if (strncasecmp(value, "But3", strlen("But3")) == 0) {
-               if (!update_mousebutton(1, XCB_BUTTON_INDEX_3))
-                       return (1);
-       } else
-               return (1);
-       return (0);
-}
-
 int
 setconfcolor(const char *selector, const char *value, int flags)
 {
@@ -8466,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;
@@ -8486,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
@@ -8505,7 +9263,6 @@ 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");
@@ -8517,6 +9274,7 @@ setautorun(const char *selector, const char *value, int flags)
                _exit(1);
        }
        free(a.argv);
+       free(str);
 
        /* parent */
        p = find_pid(pid);
@@ -8657,8 +9415,6 @@ struct config_option configopt[] = {
        { "layout",                     setlayout,      0 },
        { "maximize_hide_bar",          setconfvalue,   SWM_S_MAXIMIZE_HIDE_BAR },
        { "modkey",                     setconfmodkey,  0 },
-       { "move_button",                setconfmousebuttonmove, 0 },
-       { "resize_button",              setconfmousebuttonresize, 0 },
        { "program",                    setconfspawn,   0 },
        { "quirk",                      setconfquirk,   0 },
        { "region",                     setconfregion,  0 },
@@ -8675,10 +9431,12 @@ struct config_option configopt[] = {
        { "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 },
 };
@@ -8825,11 +9583,6 @@ conf_load(const char *filename, int keymapping)
                free(line);
        fclose(config);
 
-       if (mouse_button_move == mouse_button_resize) {
-               add_startup_exception("%s: move and resize mouse buttons match",
-                   filename);
-       }
-
        DNPRINTF(SWM_D_CONF, "conf_load: end\n");
 
        return (0);
@@ -8993,18 +9746,84 @@ get_ws_idx(struct ws_win *win)
        return ws_idx;
 }
 
-struct ws_win *
-manage_window(xcb_window_t id, int spawn_pos, bool 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, force_ws = -1;
-       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 "
@@ -9021,16 +9840,36 @@ manage_window(xcb_window_t id, int spawn_pos, bool 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. */
@@ -9046,24 +9885,19 @@ manage_window(xcb_window_t id, int spawn_pos, bool 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 = true;
-       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,
@@ -9087,6 +9921,12 @@ manage_window(xcb_window_t id, int spawn_pos, bool mapped)
        /* Get WM_PROTOCOLS. */
        get_wm_protocols(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);
 
@@ -9171,7 +10011,7 @@ manage_window(xcb_window_t id, int spawn_pos, bool mapped)
                update_window(win);
        }
 
-out:
+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);
@@ -9214,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 */
 
@@ -9248,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);
@@ -9271,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)
 {
@@ -9306,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;
+       }
+
+       replay = bp->flags & BINDING_F_REPLAY;
 
-       if ((kp = key_lookup(CLEANMASK(e->state), keysym)) == NULL)
+       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;
-       bool                    handled = false;
+       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, "
@@ -9352,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 = true;
-                               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 = true;
-               }
+       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);
        }
 
+       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;
+}
+
 void
 print_win_geom(xcb_window_t w)
 {
@@ -9443,7 +10422,7 @@ get_stack_mode_name(uint8_t mode)
 {
        char    *name;
 
-       switch(mode) {
+       switch (mode) {
        case XCB_STACK_MODE_ABOVE:
                name = "Above";
                break;
@@ -9609,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);
                }
        }
@@ -9624,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
@@ -9720,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
@@ -9728,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) {
@@ -9744,13 +10805,12 @@ enternotify(xcb_enter_notify_event_t *e)
        }
 
        if (focus_mode != SWM_FOCUS_FOLLOW &&
-           e->mode == XCB_NOTIFY_MODE_UNGRAB) {
+           e->mode == XCB_NOTIFY_MODE_UNGRAB &&
+           e->detail != XCB_NOTIFY_DETAIL_ANCESTOR) {
                DNPRINTF(SWM_D_EVENT, "enternotify: ungrab; ignoring.\n");
                return;
        }
 
-       last_event_time = e->time;
-
        if ((win = find_window(e->event)) == NULL) {
                if (e->event == e->root) {
                        /* If no windows on pointer region, then focus root. */
@@ -9778,6 +10838,8 @@ enternotify(xcb_enter_notify_event_t *e)
                focus_win(get_focus_magic(win));
        }
 
+       DNPRINTF(SWM_D_EVENT, "enternotify: done\n");
+
        xcb_flush(conn);
 }
 
@@ -9785,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
 
@@ -9804,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, spawn_position, true)) == 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))
@@ -9816,7 +10889,7 @@ 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 = true;
@@ -9826,69 +10899,43 @@ mapnotify(xcb_map_notify_event_t *e)
                if (ws->focus_pending == win) {
                        focus_win(win);
                        ws->focus_pending = NULL;
-                       center_pointer(win->ws->r);
+                       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, spawn_position,
-           (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) {
@@ -9912,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");
 }
 
@@ -9929,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",
@@ -9937,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;
 
@@ -10003,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;
@@ -10024,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)
 {
@@ -10042,16 +11120,26 @@ 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 = 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) {
                if (focus_mode != SWM_FOCUS_FOLLOW) {
@@ -10066,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)) {
@@ -10084,13 +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
@@ -10123,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
@@ -10145,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;
        }
 
@@ -10208,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]);
@@ -10221,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], 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);
 
-                       stack();
+                       if (win->ws->r) {
+                               if (FLOATING(win))
+                                       load_float_geom(win);
+
+                               stack(win->ws->r);
+                       }
                }
        }
 
@@ -10278,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;
@@ -10298,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;
@@ -10331,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);
                }
@@ -10378,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;
@@ -10409,7 +11523,6 @@ scan_randr(int idx)
        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;
@@ -10432,6 +11545,7 @@ scan_randr(int idx)
                r->ws->old_r = r->ws->r = NULL;
                bar_cleanup(r);
                xcb_destroy_window(conn, r->id);
+               r->id = XCB_WINDOW_NONE;
                TAILQ_REMOVE(&screens[idx].rl, r, entry);
                TAILQ_INSERT_TAIL(&screens[idx].orl, r, entry);
        }
@@ -10483,13 +11597,8 @@ scan_randr(int idx)
                    screen->height_in_pixels);
 
 out:
-       /* Cleanup unused previously visible workspaces. */
+       /* The screen shouldn't focus on unused regions. */
        TAILQ_FOREACH(r, &screens[idx].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[idx].r_focus == r)
                        screens[idx].r_focus = NULL;
        }
@@ -10501,7 +11610,9 @@ 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);
 
@@ -10520,46 +11631,49 @@ screenchange(xcb_randr_screen_change_notify_event_t *e)
        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;
-       uint8_t                 state;
-       bool                    manage, mapped;
-
-       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();
@@ -10593,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]) {
@@ -10614,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], SWM_STACK_TOP, 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], SWM_STACK_TOP, mapped);
-                       free(gar);
+                           &trans, NULL))
+                               manage_window(wins[j], SWM_STACK_TOP, false);
                }
                free(qtr);
        }
@@ -10689,8 +11763,7 @@ 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");
 
@@ -10755,6 +11828,7 @@ setup_screens(void)
                        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;
@@ -10793,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");
@@ -10803,6 +11878,9 @@ setup_globals(void)
 void
 shutdown_cleanup(void)
 {
+       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 */
@@ -10815,10 +11893,18 @@ 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);
 
@@ -10830,16 +11916,55 @@ shutdown_cleanup(void)
                        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);
 }
 
@@ -10868,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);
@@ -10879,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);
@@ -10897,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, );*/
@@ -10913,14 +12038,14 @@ event_handle(xcb_generic_event_t *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;
+       struct pollfd           pfd[2];
        struct sigaction        sact;
+       struct stat             sb;
+       struct passwd           *pwd;
+       struct swm_region       *r;
        xcb_generic_event_t     *evt;
-       int                     num_readable;
-       struct pollfd           pfd[2];
+       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 */
@@ -10970,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);
@@ -10987,7 +12112,8 @@ main(int argc, char *argv[])
        setup_globals();
        setup_screens();
        setup_ewmh();
-       setup_keys();
+       setup_keybindings();
+       setup_btnbindings();
        setup_quirks();
        setup_spawn();
 
@@ -11046,16 +12172,22 @@ 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);
+               }
 
        memset(&pfd, 0, sizeof(pfd));
        pfd[0].fd = xfd;
@@ -11064,7 +12196,7 @@ noconfig:
        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);
@@ -11085,18 +12217,20 @@ noconfig:
                        }
                }
 
+               if (search_resp)
+                       search_do_resp();
+
                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) {
+                       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;
@@ -11106,7 +12240,11 @@ noconfig:
                        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: