]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Trim trailing whitespace when loading config
[spectrwm] / spectrwm.c
index 0e29167b06c353aadb07da09f9e5751ca1f76650..4cd953c5596725f377a3386ad9a3faec616cacc9 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,7 +305,7 @@ bool                        cycle_empty = false;
 bool                   cycle_visible = false;
 int                    term_width = 0;
 int                    font_adjusted = 0;
-unsigned int           mod_key = MODKEY;
+uint16_t               mod_key = MODKEY;
 bool                   warp_focus = false;
 bool                   warp_pointer = false;
 bool                   workspace_clamp = false;
@@ -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;
@@ -423,19 +396,20 @@ int                region_padding = 0;
 int             tile_gap = 0;
 bool            java_workaround = true;
 bool            verbose_layout = false;
+bool            track_pid_ws = true;
 #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 +427,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 +442,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 +476,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,12 +493,13 @@ 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);
 void   horizontal_stack(struct workspace *, struct swm_geometry *);
 void   max_stack(struct workspace *, struct swm_geometry *);
+void   max_config(struct workspace *, int);
 void   plain_stacker(struct workspace *);
 void   fancy_stacker(struct workspace *);
 
@@ -525,7 +514,7 @@ struct layout {
        /* stack,               configure */
        { vertical_stack,       vertical_config,        0,      plain_stacker },
        { horizontal_stack,     horizontal_config,      0,      plain_stacker },
-       { max_stack,            NULL,
+       { max_stack,            max_config,
          SWM_L_MAPONFOCUS | SWM_L_FOCUSPREV,                   plain_stacker },
        { NULL,                 NULL,                   0,      NULL  },
 };
@@ -548,6 +537,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 */
@@ -611,6 +601,16 @@ struct swm_screen {
 };
 struct swm_screen      *screens;
 
+struct layout_config {
+       bool configured;
+       int layout;
+       int master_grow;
+       int master_add;
+       int stack_inc;
+       int always_raise;
+       bool apply_flip;
+} initial_layouts[SWM_WS_MAX];
+
 /* args to functions */
 union arg {
        int                     id;
@@ -627,7 +627,6 @@ union arg {
 #define SWM_ARG_ID_MASTERADD   (22)
 #define SWM_ARG_ID_MASTERDEL   (23)
 #define SWM_ARG_ID_FLIPLAYOUT  (24)
-#define SWM_ARG_ID_STACKRESET  (30)
 #define SWM_ARG_ID_STACKINIT   (31)
 #define SWM_ARG_ID_STACKBALANCE        (32)
 #define SWM_ARG_ID_CYCLEWS_UP  (40)
@@ -640,8 +639,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 +651,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,19 +669,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 when fullscreen */
-#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) /* Remove border when floating/unfocused */
+#define SWM_Q_MINIMALBORDER    (1<<12) /* No border when floating/unfocused. */
 };
 TAILQ_HEAD(quirk_list, quirk);
 struct quirk_list              quirks = TAILQ_HEAD_INITIALIZER(quirks);
@@ -705,6 +700,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,
@@ -724,6 +720,7 @@ enum {
        _NET_WM_STATE_SKIP_PAGER,
        _NET_WM_STATE_SKIP_TASKBAR,
        _NET_WM_WINDOW_TYPE,
+       _NET_WM_WINDOW_TYPE_DESKTOP,
        _NET_WM_WINDOW_TYPE_DIALOG,
        _NET_WM_WINDOW_TYPE_DOCK,
        _NET_WM_WINDOW_TYPE_NORMAL,
@@ -748,6 +745,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},
@@ -767,6 +765,7 @@ struct ewmh_hint {
     {"_NET_WM_STATE_SKIP_PAGER", XCB_ATOM_NONE},
     {"_NET_WM_STATE_SKIP_TASKBAR", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE", XCB_ATOM_NONE},
+    {"_NET_WM_WINDOW_TYPE_DESKTOP", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_DIALOG", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_DOCK", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_NORMAL", XCB_ATOM_NONE},
@@ -822,137 +821,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_INIT,
+       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 *);
@@ -963,26 +988,34 @@ 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 *);
-void    bar_window_float(char *, size_t, struct swm_region *);
+void    bar_window_index_count(char *, size_t, struct swm_region *);
 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 *);
+void    bar_workspace_state(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);
@@ -997,11 +1030,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);
@@ -1011,6 +1048,7 @@ void       ewmh_apply_flags(struct ws_win *, uint32_t);
 void    ewmh_autoquirk(struct ws_win *);
 void    ewmh_get_desktop_names(void);
 void    ewmh_get_wm_state(struct ws_win *);
+int     ewmh_handle_special_types(xcb_window_t, struct swm_region *);
 void    ewmh_update_actions(struct ws_win *);
 void    ewmh_update_client_list(void);
 void    ewmh_update_current_desktop(void);
@@ -1021,27 +1059,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);
@@ -1054,25 +1098,27 @@ 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 *);
+void    initlayout(struct workspace *);
 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
@@ -1085,36 +1131,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);
@@ -1123,13 +1174,16 @@ 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);
@@ -1137,15 +1191,15 @@ int      setconfquirk(const char *, const char *, int);
 int     setconfregion(const char *, const char *, int);
 int     setconfspawn(const char *, const char *, int);
 int     setconfvalue(const char *, const char *, int);
-void    setkeybinding(unsigned int, KeySym, enum keyfuncid, const char *);
 int     setkeymapping(const char *, const char *, int);
 int     setlayout(const char *, const char *, int);
 void    setquirk(const char *, const char *, const char *, 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);
@@ -1163,42 +1217,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);
+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 +1478,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);
@@ -1479,6 +1525,51 @@ teardown_ewmh(void)
        }
 }
 
+int
+ewmh_handle_special_types(xcb_window_t id, struct swm_region *region)
+{
+       xcb_get_property_reply_t        *r;
+       xcb_get_property_cookie_t       c;
+       xcb_atom_t                      *type;
+       int                             i, n;
+       uint16_t                        configure_mask = 0;
+       uint32_t                        wa[2];
+
+       c = xcb_get_property(conn, 0, id,
+           ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX);
+       r = xcb_get_property_reply(conn, c, NULL);
+       if (r == NULL)
+               return 0;
+
+       type = xcb_get_property_value(r);
+       n = xcb_get_property_value_length(r) / sizeof(xcb_atom_t);
+
+       for (i = 0; i < n; i++) {
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DESKTOP].atom) {
+                       configure_mask = XCB_CONFIG_WINDOW_STACK_MODE |
+                               XCB_CONFIG_WINDOW_SIBLING;
+                       wa[0] = region->id;
+                       wa[1] = XCB_STACK_MODE_ABOVE;
+                       break;
+               }
+
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom) {
+                       configure_mask = XCB_CONFIG_WINDOW_STACK_MODE;
+                       wa[0] = XCB_STACK_MODE_ABOVE;
+                       break;
+               }
+       }
+       free(r);
+
+       if (configure_mask != 0) {
+               xcb_map_window(conn, id);
+               xcb_configure_window (conn, id, configure_mask, wa);
+               return 1;
+       }
+
+       return 0;
+}
+
 void
 ewmh_autoquirk(struct ws_win *win)
 {
@@ -1499,8 +1590,7 @@ ewmh_autoquirk(struct ws_win *win)
        for (i = 0; i < n; i++) {
                if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom)
                        break;
-               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom ||
-                   type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom ||
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom ||
                    type[i] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom) {
                        win->quirks = SWM_Q_FLOAT | SWM_Q_ANYWHERE;
                        break;
@@ -1512,6 +1602,25 @@ ewmh_autoquirk(struct ws_win *win)
                }
        }
        free(r);
+
+
+       c = xcb_get_property(conn, 0, win->id,
+           ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX);
+       r = xcb_get_property_reply(conn, c, NULL);
+       if (r == NULL)
+               return;
+
+       type = xcb_get_property_value(r);
+       n = xcb_get_property_value_length(r) / sizeof(xcb_atom_t);
+
+       for (i = 0; i < n; i++) {
+               if (type[i] == ewmh[_NET_WM_STATE_SKIP_PAGER].atom ||
+                   type[i] == ewmh[_NET_WM_STATE_SKIP_TASKBAR].atom) {
+                       win->quirks = SWM_Q_FLOAT | SWM_Q_ANYWHERE;
+                       break;
+               }
+       }
+       free(r);
 }
 
 void
@@ -1664,7 +1773,7 @@ ewmh_apply_flags(struct ws_win *win, uint32_t pending)
                        }
                }
 
-               update_window_color(win);
+               draw_frame(win);
                raise_window(win);
        }
 
@@ -1747,7 +1856,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 +1864,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 +1878,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 +1889,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 +1911,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 +2238,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, "
@@ -2161,6 +2432,30 @@ bar_window_class_instance(char *s, size_t sz, struct swm_region *r)
        bar_window_instance(s, sz, r);
 }
 
+void
+bar_window_index_count(char *s, size_t sz, struct swm_region *r)
+{
+       struct ws_win           *w;
+       int                     count, index;
+
+       if (r == NULL || r->ws == NULL || r->ws->focus == NULL) {
+               strlcat(s, "0/0", sz);
+               return;
+       }
+
+       count = 0;
+       index = 0;
+
+       TAILQ_FOREACH(w, &r->ws->winlist, entry) {
+               ++count;
+               if (w->id == r->ws->focus->id) {
+                       index = count;
+               }
+       }
+
+       snprintf(s, sz, "%d/%d", index, count);
+}
+
 void
 bar_window_state(char *s, size_t sz, struct swm_region *r)
 {
@@ -2227,7 +2522,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;
 }
 
@@ -2240,6 +2535,44 @@ bar_workspace_name(char *s, size_t sz, struct swm_region *r)
                strlcat(s, r->ws->name, sz);
 }
 
+void
+bar_workspace_state(char *s, size_t sz, struct swm_region *r)
+{
+       struct ws_win           *win;
+       int                     i, j, num_screens;
+       bool                    used_workspaces[SWM_WS_MAX];
+       char                    tmp[8];
+       int                     first = 1;
+       char                    *fmt;
+
+       if (r == NULL || r->ws == NULL)
+               return;
+
+       for (i = 0; i < SWM_WS_MAX; ++i)
+               used_workspaces[i] = false;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               for (j = 0; j < workspace_limit; j++)
+                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
+                               ++used_workspaces[win->ws->idx];
+
+       for (i = 0; i < SWM_WS_MAX; ++i) {
+               fmt = NULL;
+               if (i == r->ws->idx) {
+                       fmt = " [%d]";
+               } else if (used_workspaces[i]) {
+                       fmt = " %d";
+               }
+               if (fmt) {
+                       fmt = fmt + first;
+                       first = 0;
+                       snprintf(tmp, sizeof tmp, fmt, i + 1);
+                       strlcat(s, tmp, sz);
+               }
+       }
+}
+
 /* build the default bar format according to the defined enabled options */
 void
 bar_fmt(const char *fmtexp, char *fmtnew, struct swm_region *r, size_t sz)
@@ -2281,7 +2614,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);
@@ -2362,6 +2695,9 @@ bar_replace_seq(char *fmt, char *fmtrep, struct swm_region *r, size_t *offrep,
        case 'I':
                snprintf(tmp, sizeof tmp, "%d", r->ws->idx + 1);
                break;
+       case 'i':
+               bar_workspace_state(tmp, sizeof tmp, r);
+               break;
        case 'M':
                count = 0;
                TAILQ_FOREACH(w, &r->ws->winlist, entry)
@@ -2373,6 +2709,9 @@ bar_replace_seq(char *fmt, char *fmtrep, struct swm_region *r, size_t *offrep,
        case 'N':
                snprintf(tmp, sizeof tmp, "%d", r->s->idx + 1);
                break;
+       case 'p':
+               bar_window_index_count(tmp, sizeof tmp, r);
+               break;
        case 'P':
                bar_window_class_instance(tmp, sizeof tmp, r);
                break;
@@ -2468,46 +2807,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 +2895,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 +2931,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 +3015,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 +3051,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 +3081,7 @@ xft_init(struct swm_region *r)
                                break;
                        }
                }
-               free(d);
+               free(str);
        }
 
        if (bar_font == NULL)
@@ -2787,6 +3128,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 +3189,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 +3219,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,11 +3236,16 @@ version(struct swm_region *r, union arg *args)
        else
                strlcpy(bar_vertext, "", sizeof bar_vertext);
 
-       bar_draw();
-       xcb_flush(conn);
-}
-
-void
+       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
 client_msg(struct ws_win *win, xcb_atom_t a, xcb_timestamp_t t)
 {
        xcb_client_message_event_t      ev;
@@ -2940,13 +3291,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 +3342,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 +3375,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 +3392,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 +3453,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 +3509,45 @@ 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;
-       else if (FLOATING(win) || (win->ws->always_raise &&
-           win->ws->focus == win))
+           (win->ws->always_raise && win->ws->focus == win))) {
+               val[0] = sibling->frame;
+               val[1] = XCB_STACK_MODE_ABOVE;
+       } 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]);
+               val[1] = XCB_STACK_MODE_ABOVE;
+       } else {
+               val[0] = r->bar->id;
+               val[1] = XCB_STACK_MODE_BELOW;
+       }
 
-       val[1] = XCB_STACK_MODE_ABOVE;
+       DNPRINTF(SWM_D_EVENT, "update_win_stacking: win %#x (%#x), "
+           "sibling %#x mode %#x\n", win->frame, win->id, val[0], val[1]);
 
-       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 +3562,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 +3581,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 +3638,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 +3651,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 +3690,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,56 +3755,100 @@ root_to_region(xcb_window_t root, int check)
        return (r);
 }
 
-struct ws_win *
-find_unmanaged_window(xcb_window_t id)
+struct swm_region *
+find_region(xcb_window_t id)
 {
-       struct ws_win           *win;
-       int                     i, j, num_screens;
+       struct swm_region       *r;
+       int                     i, num_screens;
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
-               for (j = 0; j < workspace_limit; j++)
-                       TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
-                           entry)
-                               if (id == win->id)
-                                       return (win);
-       return (NULL);
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       if (r->id == id)
+                               return r;
+
+       return NULL;
+}
+
+struct swm_bar *
+find_bar(xcb_window_t id)
+{
+       struct swm_region       *r;
+       int                     i, num_screens;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       if (r->bar && r->bar->id == id)
+                               return r->bar;
+
+       return NULL;
+}
+
+struct ws_win *
+find_frame_window(xcb_window_t id) {
+       struct swm_region       *r;
+       struct ws_win           *w;
+       int                     i, num_screens;
+
+       num_screens = get_screen_count();
+       for (i = 0; i < num_screens; i++)
+               TAILQ_FOREACH(r, &screens[i].rl, entry)
+                       TAILQ_FOREACH(w, &r->ws->winlist, entry)
+                               if (w->frame == id)
+                                       return w;
+
+       return NULL;
 }
 
 struct ws_win *
 find_window(xcb_window_t id)
 {
-       struct ws_win           *win;
+       struct ws_win           *win = NULL;
        int                     i, j, num_screens;
-       xcb_query_tree_reply_t  *r;
+       xcb_query_tree_reply_t  *qtr;
+
+       DNPRINTF(SWM_D_MISC, "find_window: id: %#x\n", id);
 
        num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
-                               if (id == win->id)
+                               if (id == win->id || id == win->frame)
                                        return (win);
 
-       r = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
-       if (r == NULL)
-               return (NULL);
 
-       /* if we were looking for the parent return that window instead */
-       if (r->parent == 0 || r->root == r->parent) {
-               free(r);
-               return (NULL);
+       /* If window isn't top-level, try to find managed ancestor. */
+       qtr = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
+       if (qtr) {
+               if (qtr->parent != XCB_WINDOW_NONE && qtr->parent != qtr->root)
+                       win = find_window(qtr->parent);
+
+#ifdef SWM_DEBUG
+               if (win)
+                       DNPRINTF(SWM_D_MISC, "find_window: found child %#x "
+                           "of %#x.\n", win->id, qtr->parent);
+#endif
+
+               free(qtr);
        }
 
-       /* look for parent */
+       return (win);
+}
+
+struct ws_win *
+find_unmanaged_window(xcb_window_t id)
+{
+       struct ws_win           *win;
+       int                     i, j, num_screens;
+
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
-                       TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
-                               if (r->parent == win->id) {
-                                       free(r);
+                       TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
+                           entry)
+                               if (id == win->id)
                                        return (win);
-                               }
-
-       free(r);
        return (NULL);
 }
 
@@ -3446,23 +3862,34 @@ spawn(int ws_idx, union arg *args, bool close_fd)
 
        close(xcb_get_file_descriptor(conn));
 
-       setenv("LD_PRELOAD", SWM_LIB, 1);
+       if (track_pid_ws) {
+               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);
-       free(ret);
-       ret = NULL;
+               if (asprintf(&ret, "%d", ws_idx) == -1) {
+                       warn("spawn: asprintf SWM_WS");
+                       _exit(1);
+               }
+               setenv("_SWM_WS", ret, 1);
+               free(ret);
+               ret = NULL;
 
-       if (asprintf(&ret, "%d", getpid()) == -1) {
-               warn("spawn: asprintf _SWM_PID");
-               _exit(1);
+               if (asprintf(&ret, "%d", getpid()) == -1) {
+                       warn("spawn: asprintf _SWM_PID");
+                       _exit(1);
+               }
+               setenv("_SWM_PID", ret, 1);
+               free(ret);
+               ret = NULL;
        }
-       setenv("_SWM_PID", ret, 1);
-       free(ret);
-       ret = NULL;
 
        if (setsid() == -1) {
                warn("spawn: setsid");
@@ -3494,23 +3921,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;
-           &nbs