]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Prepare for release 2.7.0.
[spectrwm] / spectrwm.c
index 4fbbe0ba4622951ff345bbeb562178ed0267ab02..d82c0eb69594423172176216b9fd8ff3926d9e5a 100644 (file)
 #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__)
@@ -235,7 +239,11 @@ 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))
 #define BUTTONMASK             (XCB_EVENT_MASK_BUTTON_PRESS |          \
     XCB_EVENT_MASK_BUTTON_RELEASE)
 #define MOUSEMASK              (BUTTONMASK|XCB_EVENT_MASK_POINTER_MOTION)
@@ -332,9 +340,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;
-bool                   visible_noswap = false;
+bool                   workspace_clamp = false;
 
 /* dmenu search */
 struct swm_region      *search_r;
@@ -400,7 +409,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;
@@ -426,15 +435,14 @@ bool               verbose_layout = false;
 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 */
@@ -673,18 +681,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);
@@ -820,137 +829,156 @@ 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_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,
+       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_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,
+       FN_DUMPWINS, /* MUST BE LAST */
+       FN_INVALID
 };
 
-struct key {
-        RB_ENTRY(key)           entry;
-        unsigned int            mod;
-        KeySym                  keysym;
-        enum keyfuncid          funcid;
-        char                    *spawn_name;
+enum binding_type {
+       KEYBIND,
+       BTNBIND
 };
-RB_HEAD(key_tree, key);
+
+enum {
+       BINDING_F_REPLAY = 0x1,
+};
+
+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(binding_tree, binding);
 
 /* function prototypes */
 void    adjust_font(struct ws_win *);
@@ -968,7 +996,7 @@ 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 *);
@@ -977,11 +1005,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);
@@ -993,11 +1030,11 @@ 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 *);
 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);
@@ -1020,16 +1057,17 @@ void     fake_keypress(struct ws_win *, xcb_keysym_t, uint16_t);
 struct pid_e   *find_pid(pid_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 *);
+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    focus_win(struct ws_win *);
 #ifdef SWM_DEBUG
 void    focusin(xcb_focus_in_event_t *);
 void    focusout(xcb_focus_out_event_t *);
 #endif
-void    focus_flush(void);
-void    focus_region(struct swm_region *);
-void    focusrg(struct swm_region *, union arg *);
-void    focus_win(struct ws_win *);
+void    focusrg(struct binding *, struct swm_region *, union arg *);
 void    fontset_init(void);
 void    free_window(struct ws_win *);
 xcb_atom_t get_atom_from_string(const char *);
@@ -1038,6 +1076,7 @@ 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);
@@ -1050,6 +1089,7 @@ 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 *);
@@ -1057,18 +1097,15 @@ 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
@@ -1081,36 +1118,39 @@ 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);
+void    print_win_input_model(struct ws_win *);
 #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_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    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);
@@ -1119,13 +1159,15 @@ 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_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);
@@ -1133,15 +1175,15 @@ int      setconfquirk(const char *, const char *, int);
 int     setconfregion(const char *, const char *, int);
 int     setconfspawn(const char *, const char *, int);
 int     setconfvalue(const char *, const char *, int);
-void    setkeybinding(unsigned int, KeySym, enum keyfuncid, const char *);
 int     setkeymapping(const char *, const char *, int);
 int     setlayout(const char *, const char *, int);
 void    setquirk(const char *, const char *, const char *, 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);
@@ -1159,42 +1201,42 @@ 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    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    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)
@@ -1743,7 +1785,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;
@@ -1751,6 +1793,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) {
@@ -1799,8 +1842,9 @@ dumpwins(struct swm_region *r, union arg *args)
 }
 #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;
 }
@@ -1965,6 +2009,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, "
@@ -2277,7 +2323,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);
@@ -2555,12 +2601,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;
 
@@ -2706,13 +2753,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;
@@ -2736,7 +2783,7 @@ xft_init(struct swm_region *r)
                                break;
                        }
                }
-               free(d);
+               free(str);
        }
 
        if (bar_font == NULL)
@@ -2843,7 +2890,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;
@@ -2873,9 +2920,10 @@ 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)
 {
        /* suppress unused warnings since vars are needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -3020,9 +3068,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;
 
@@ -3036,13 +3085,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;
@@ -3097,11 +3146,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))
@@ -3263,9 +3313,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;
 
@@ -3275,7 +3326,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 *
@@ -3490,23 +3541,29 @@ 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 (TRANS(win))
+                               TAILQ_FOREACH(w, &ws->winlist, entry)
+                                       if (win == w->focus_child)
+                                               w->focus_child = NULL;
+               }
+       }
 }
 
 int
@@ -3603,6 +3660,15 @@ unfocus_win(struct ws_win *win)
        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);
 
@@ -3642,6 +3708,16 @@ focus_win(struct ws_win *win)
                                    &cfw->s->c[(MAXIMIZED(cfw) ?
                                    SWM_S_COLOR_UNFOCUS_MAXIMIZED :
                                    SWM_S_COLOR_UNFOCUS)].pixel);
+
+                               /* Update border width */
+                               if (cfw->bordered &&
+                                   (cfw->quirks & SWM_Q_MINIMALBORDER) &&
+                                   FLOATING(cfw)) {
+                                       cfw->bordered = 0;
+                                       X(cfw) += border_width;
+                                       Y(cfw) += border_width;
+                                       update_window(cfw);
+                               }
                        } else {
                                unfocus_win(cfw);
                        }
@@ -3655,9 +3731,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)) {
@@ -3669,8 +3744,7 @@ focus_win(struct ws_win *win)
        if (cfw != win && ws->r != NULL) {
                /* 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))
+                   win->hints.input)
                        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
                                        win->id, last_event_time);
                else
@@ -3730,10 +3804,20 @@ focus_win(struct ws_win *win)
                    &win->id);
        }
 
-       if (cfw != win)
+       if (cfw != win) {
                /* Update window border even if workspace is hidden. */
                update_window_color(win);
 
+               /* Update border width */
+               if (!win->bordered && WS_FOCUSED(win->ws) &&
+                   (win->quirks & SWM_Q_MINIMALBORDER) && FLOATING(win)) {
+                       win->bordered = 1;
+                       X(win) -= border_width;
+                       Y(win) -= border_width;
+                       update_window(win);
+               }
+       }
+
 out:
        bar_draw();
 
@@ -3781,7 +3865,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);
 
@@ -3857,7 +3941,7 @@ focus_region(struct swm_region *r)
 }
 
 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;
@@ -3866,6 +3950,8 @@ switchws(struct swm_region *r, union arg *args)
        int                     wsid = args->id;
        bool                    unmap_old = false;
 
+       (void)bp;
+
        if (!(r && r->s))
                return;
 
@@ -3884,6 +3970,20 @@ switchws(struct swm_region *r, union arg *args)
        if (new_ws == old_ws)
                return;
 
+       other_r = new_ws->r;
+       if (other_r && workspace_clamp) {
+               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);
 
@@ -3892,22 +3992,17 @@ switchws(struct swm_region *r, union arg *args)
                    &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 (visible_noswap) {
-                       center_pointer(other_r);
-                       return;
-               }
-
+       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;
@@ -3955,7 +4050,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;
@@ -3997,16 +4092,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;
 
@@ -4019,16 +4114,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))
@@ -4050,7 +4147,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;
@@ -4093,7 +4190,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;
@@ -4126,12 +4223,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);
 
@@ -4378,7 +4477,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;
@@ -4480,7 +4579,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:
@@ -4499,11 +4598,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);
@@ -4524,10 +4636,12 @@ 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);
 
@@ -4653,6 +4767,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;
 
@@ -4661,8 +4777,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)) {
@@ -4697,9 +4811,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)) {
@@ -4768,11 +4884,12 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool flip)
 {
        struct swm_geometry     win_g, 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;
 
        DNPRINTF(SWM_D_STACK, "stack_master: workspace: %d, rot: %s, "
@@ -5161,7 +5278,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;
@@ -5183,7 +5300,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 *
@@ -5205,11 +5322,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
@@ -5359,9 +5478,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,
@@ -5371,9 +5491,10 @@ pressbutton(struct swm_region *r, union arg *args)
 }
 
 void
-raise_toggle(struct swm_region *r, union arg *args)
+raise_toggle(struct binding *bp, struct swm_region *r, union arg *args)
 {
        /* Suppress warning. */
+       (void)bp;
        (void)args;
 
        if (r == NULL || r->ws == NULL)
@@ -5391,11 +5512,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)
@@ -5443,13 +5565,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)
@@ -5492,10 +5616,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)
@@ -5514,12 +5640,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)
@@ -5557,7 +5685,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;
@@ -5574,6 +5702,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;
@@ -5848,10 +5978,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
@@ -5919,7 +6052,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
@@ -6015,8 +6148,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);
 
@@ -6050,11 +6185,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)
@@ -6082,11 +6218,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)
@@ -6240,22 +6377,87 @@ update_window(struct ws_win *win)
        xcb_configure_window(conn, win->id, mask, wc);
 }
 
+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 wait)
+{
+       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 (wait)
+               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;
@@ -6291,27 +6493,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);
@@ -6337,7 +6539,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 :
@@ -6348,18 +6550,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;
@@ -6368,7 +6583,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;
 
@@ -6388,7 +6603,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;
 
@@ -6415,8 +6630,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);
@@ -6447,17 +6670,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();
 }
 
@@ -6490,15 +6731,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;
@@ -6536,28 +6776,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);
@@ -6577,21 +6816,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;
 
@@ -6605,6 +6863,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,
@@ -6614,7 +6878,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;
@@ -6644,202 +6908,181 @@ 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))
+       if (args->id) {
+               /* move_* uses focus window. */
+               if (r->ws)
+                       win = r->ws->focus;
+       } 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);
+       }
+
+       if (win == NULL)
                return;
 
-       move(win, args);
-       center_pointer(r);
+       move_win(win, bp, args->id);
+
+       if (args->id && bp->type == KEYBIND)
+               center_pointer(r);
+
        focus_flush();
 }
 
-/* key definitions */
-struct keyfunc {
+/* action definitions */
+struct action {
        char                    name[SWM_FUNCNAME_LEN];
-       void                    (*func)(struct swm_region *r, union arg *);
+       void                    (*func)(struct binding *, struct swm_region *,
+                                   union arg *);
+       uint32_t                flags;
        union arg               args;
-} keyfuncs[KF_INVALID + 1] = {
+} actions[FN_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} },
-};
-
-int
-key_cmp(struct key *kp1, struct key *kp2)
-{
-       if (kp1->keysym < kp2->keysym)
-               return (-1);
-       if (kp1->keysym > kp2->keysym)
-               return (1);
-
-       if (kp1->mod < kp2->mod)
-               return (-1);
-       if (kp1->mod > kp2->mod)
-               return (1);
-
-       return (0);
-}
-
-/* mouse */
-enum { client_click, root_click };
-struct button {
-       unsigned int            action;
-       unsigned int            mask;
-       unsigned int            button;
-       void                    (*func)(struct ws_win *, union arg *);
-       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
+       { "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} },
+       { "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} },
+       { "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_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} },
+       { "dumpwins",           dumpwins,       0, {0} }, /* MUST BE LAST */
+       { "invalid action",     NULL,           0, {0} },
 };
 
 void
-update_modkey(unsigned int mod)
+update_modkey(uint16_t mod)
 {
-       int                     i;
-       struct key              *kp;
+       struct binding          *bp;
 
+       /* Replace all instances of the old mod key. */
+       RB_FOREACH(bp, binding_tree, &bindings)
+               if (bp->mod & mod_key)
+                       bp->mod = (bp->mod & ~mod_key) | mod;
        mod_key = mod;
-       RB_FOREACH(kp, key_tree, &keys)
-               if (kp->mod & XCB_MOD_MASK_SHIFT)
-                       kp->mod = mod | XCB_MOD_MASK_SHIFT;
-               else
-                       kp->mod = mod;
-
-       for (i = 0; i < LENGTH(buttons); i++)
-               if (buttons[i].mask & XCB_MOD_MASK_SHIFT)
-                       buttons[i].mask = mod | XCB_MOD_MASK_SHIFT;
-               else
-                       buttons[i].mask = mod;
 }
 
 int
@@ -7108,6 +7351,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)
 {
@@ -7164,18 +7417,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;
 
@@ -7234,40 +7486,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)
@@ -7278,21 +7533,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);
 }
 
@@ -7305,97 +7578,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;
@@ -7404,21 +7684,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);
@@ -7428,8 +7708,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
@@ -7439,143 +7719,170 @@ 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(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(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(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);
        }
 }
 
@@ -7592,11 +7899,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);
@@ -7640,9 +7947,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");
@@ -7659,38 +7966,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);
                                        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;
@@ -7698,14 +8030,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[] = {
@@ -7722,6 +8097,7 @@ const char *quirkname[] = {
        "IGNOREPID",
        "IGNORESPAWNWS",
        "NOFOCUSCYCLE",
+       "MINIMALBORDER",
 };
 
 /* SWM_Q_DELIM: retain '|' for back compat for now (2009-08-11) */
@@ -7735,10 +8111,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)
@@ -7862,6 +8237,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)
@@ -8049,11 +8434,12 @@ enum {
        SWM_S_URGENT_COLLAPSE,
        SWM_S_URGENT_ENABLED,
        SWM_S_VERBOSE_LAYOUT,
-       SWM_S_VISIBLE_NOSWAP,
+       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,
 };
@@ -8063,7 +8449,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:
@@ -8108,11 +8494,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)) {
@@ -8268,8 +8654,8 @@ setconfvalue(const char *selector, const char *value, int flags)
                                layouts[i].l_string = plain_stacker;
                }
                break;
-       case SWM_S_VISIBLE_NOSWAP:
-               visible_noswap = (atoi(value) != 0);
+       case SWM_S_WARP_FOCUS:
+               warp_focus = (atoi(value) != 0);
                break;
        case SWM_S_WARP_POINTER:
                warp_pointer = (atoi(value) != 0);
@@ -8283,6 +8669,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)
@@ -8404,7 +8793,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;
@@ -8424,7 +8813,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
@@ -8443,7 +8832,7 @@ setautorun(const char *selector, const char *value, int flags)
                        err(1, "setautorun: realloc");
                a.argv[argc - 1] = ap;
        }
-       free(sp);
+       free(str);
 
        if ((a.argv = realloc(a.argv, (argc + 1) * sizeof(char *))) == NULL)
                err(1, "setautorun: realloc");
@@ -8611,11 +9000,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 },
-       { "visible_noswap",             setconfvalue,   SWM_S_VISIBLE_NOSWAP },
+       { "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 },
 };
@@ -8761,6 +9151,7 @@ conf_load(const char *filename, int keymapping)
        if (line)
                free(line);
        fclose(config);
+
        DNPRINTF(SWM_D_CONF, "conf_load: end\n");
 
        return (0);
@@ -9018,6 +9409,11 @@ 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. */
+       print_win_input_model(win);
+#endif
+
        /* Set initial quirks based on EWMH. */
        ewmh_autoquirk(win);
 
@@ -9145,8 +9541,6 @@ out:
        /* Set initial _NET_WM_ALLOWED_ACTIONS */
        ewmh_update_actions(win);
 
-       grabbuttons(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,
@@ -9158,17 +9552,13 @@ out:
 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 */
 
@@ -9181,11 +9571,11 @@ 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)
@@ -9237,8 +9627,12 @@ 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);
 
@@ -9248,33 +9642,85 @@ keypress(xcb_key_press_event_t *e)
            e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y,
            e->child, e->state, YESNO(e->same_screen));
 
-       if ((kp = key_lookup(CLEANMASK(e->state), keysym)) == NULL)
+       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 ((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;
+
+       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));
+
+       keysym = xcb_key_release_lookup_keysym(syms, e, 0);
+
+       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);
+       }
+
+       xcb_flush(conn);
+
+       DNPRINTF(SWM_D_EVENT, "keyrelease: done.\n");
+}
+
 void
 buttonpress(xcb_button_press_event_t *e)
 {
        struct ws_win           *win = NULL;
        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, "
@@ -9283,22 +9729,13 @@ 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);
@@ -9309,36 +9746,45 @@ buttonpress(xcb_button_press_event_t *e)
                                /* Clear bar since empty. */
                                bar_draw();
 
-                               handled = true;
-                               goto out;
+                               /* No need to replay event. */
+                               replay = false;
                        }
                }
        } else {
                win = find_window(e->event);
        }
 
-       if (win == NULL)
-               goto out;
+       if (win)
+               focus_win(get_focus_magic(win));
 
-       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);
        }
@@ -9346,7 +9792,58 @@ out:
        xcb_flush(conn);
 }
 
+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
+void
+print_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 (!(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT) || win->hints.input)
+               inputmodel = (win->take_focus) ? "Locally Active" : "Passive";
+       else
+               inputmodel = (win->take_focus) ? "Globally Active" : "No Input";
+
+       DNPRINTF(SWM_D_FOCUS, "print_win_input_model: win %#x, model:  %s\n",
+           win->id, inputmodel);
+}
+
 void
 print_win_geom(xcb_window_t w)
 {
@@ -9558,35 +10055,41 @@ destroynotify(xcb_destroy_notify_event_t *e)
 
        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;
-               free_window(win);
-               return;
-       }
-
-       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 = find_window(e->window))) {
+               /* Managed window cleanup. */
+               if (focus_mode != SWM_FOCUS_FOLLOW) {
+                       /* If focused, focus on something else. */
+                       if (win == win->ws->focus)
+                               win->ws->focus_pending = get_focus_prev(win);
+               }
 
-       unmanage_window(win);
-       stack();
+               unmanage_window(win);
+               stack();
 
-       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(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);
+                       }
                }
+
+               kill_refs(win);
+               focus_flush();
+       } else {
+               win = find_unmanaged_window(e->window);
        }
 
-       free_window(win);
+       if (win) {
+               /* unmanage_window() puts win into unmanaged list. */
+               TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
+               free_window(win);
+       }
 
-       focus_flush();
+       DNPRINTF(SWM_D_EVENT, "destroynotify: done.\n");
 }
 
 #ifdef SWM_DEBUG
@@ -9651,6 +10154,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
@@ -9659,14 +10217,17 @@ 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 (focus_mode == SWM_FOCUS_MANUAL &&
            e->mode == XCB_NOTIFY_MODE_NORMAL) {
@@ -9675,13 +10236,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. */
@@ -9716,14 +10276,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
 
@@ -9768,22 +10331,12 @@ mapnotify(xcb_map_notify_event_t *e)
 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
@@ -9860,6 +10413,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",
@@ -9868,8 +10423,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;
 
@@ -9934,14 +10487,14 @@ 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) {
@@ -10076,7 +10629,7 @@ 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();
                }
 
@@ -10229,11 +10782,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;
@@ -10620,8 +11168,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");
 
@@ -10734,6 +11281,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 */
@@ -10746,10 +11296,16 @@ 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;
+
                xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT,
                    screens[i].root, XCB_CURRENT_TIME);
 
@@ -10761,13 +11317,51 @@ 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);
@@ -10799,7 +11393,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);
@@ -10817,7 +11411,7 @@ event_handle(xcb_generic_event_t *evt)
        /*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);
@@ -10844,14 +11438,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 */
@@ -10901,7 +11495,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);
@@ -10918,7 +11512,8 @@ main(int argc, char *argv[])
        setup_globals();
        setup_screens();
        setup_ewmh();
-       setup_keys();
+       setup_keybindings();
+       setup_btnbindings();
        setup_quirks();
        setup_spawn();
 
@@ -10977,6 +11572,7 @@ noconfig:
        grab_windows();
 
        grabkeys();
+       grabbuttons();
        stack();
        bar_draw();
 
@@ -10995,7 +11591,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);
@@ -11018,13 +11614,15 @@ noconfig:
 
                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);
+                       restart(NULL, NULL, NULL);
 
                if (search_resp)
                        search_do_resp();