]> code.delx.au - spectrwm/commitdiff
Improve the bind[] option to accept pointer buttons.
authorReginald Kennedy <rk@rejii.com>
Fri, 13 Mar 2015 14:09:27 +0000 (22:09 +0800)
committerReginald Kennedy <rk@rejii.com>
Fri, 22 May 2015 00:41:30 +0000 (08:41 +0800)
Buttons are specified as Button<n> (case sensitive) where n is 1 to 255.

Example:
bind[move] = MOD+Button8

Existing pointer actions are now available for bind[]:
focus - Focus window/region under pointer.
move - Move window with pointer while binding is pressed.
resize - Resize window with pointer while binding is pressed.
resize_centered - Same as resize but keep window centered.

Defaults:
bind[focus] = ANYMOD+REPLAY+Button1
bind[move] = MOD+Button1
bind[resize] = MOD+Button3
bind[resize_centered] = MOD+Shift+Button3

Button binds can be unbound the same as key binds:

Example:
bind[] = MOD+Button1 # unbind default move binding.

Add new special bind arguments:
ANYMOD
Binds all modifier combinations not handled by another binding.

REPLAY
Allows other programs to receive press/release events for bind.
Unavailable for move, resize and resize_centered.

linux/tree.h
spectrwm.1
spectrwm.c

index 7d20047a00f2acf65269f39e5a8801daa96c88cb..7f5d49e048df2ebb1daf5131206a76d6369c0bad 100644 (file)
@@ -731,6 +731,11 @@ name##_RB_MINMAX(struct name *head, int val)                               \
             (x) != NULL;                                               \
             (x) = name##_RB_NEXT(x))
 
+#define RB_FOREACH_SAFE(x, name, head, y)                              \
+       for ((x) = RB_MIN(name, head);                                  \
+           ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);    \
+            (x) = (y))
+
 #define RB_FOREACH_REVERSE(x, name, head)                              \
        for ((x) = RB_MAX(name, head);                                  \
             (x) != NULL;                                               \
index 71c53de787e964baaffe85f4924183cd409804e4..cdf91f9370bac3a515778f5691e3f46fa09c4bde 100644 (file)
@@ -50,16 +50,12 @@ Meta
 .It Cm S
 Shift
 .It Aq Cm Name
-Named key
-.It Cm M1
-Mouse button 1
-.It Cm M3
-Mouse button 3
+Named key or button
 .El
 .Pp
 .Nm
 is very simple in its use.
-Most of the actions are initiated via key or mouse bindings.
+Most of the actions are initiated via key or pointer bindings.
 See the
 .Sx BINDINGS
 section below for defaults and customizations.
@@ -235,7 +231,7 @@ See the
 .Ic bar_format
 option for more details.
 .It Ic bind Ns Bq Ar x
-Bind key combo to action
+Bind key or button combo to action
 .Ar x .
 See the
 .Sx BINDINGS
@@ -245,9 +241,8 @@ Set window border thickness in pixels.
 Disable all borders by setting to 0.
 .It Ic boundary_width
 Set region containment boundary width in pixels.
-This is how far a window must be dragged/resized beyond the region edge
-before it is allowed outside the region.
-This has no effect when manipulating the window with key bindings.
+This is how far a window must be dragged/resized (with the pointer)
+beyond the region edge before it is allowed outside the region.
 Disable the window containment effect by setting to 0.
 .It Ic clock_enabled
 Enable or disable displaying the clock in the status bar.
@@ -302,7 +297,7 @@ and
 .Ar last
 (default).
 .It Ic focus_mode
-Window focus behavior with respect to the mouse cursor.
+Window focus behavior with respect to the pointer.
 Possible values:
 .Pp
 .Bl -tag -width "default" -offset indent -compact
@@ -325,7 +320,8 @@ Workaround a Java GUI rendering issue on non-reparenting window managers by
 impersonating the LG3D window manager, written by Sun.
 Default is 1.
 .It Ic keyboard_mapping
-Clear all key bindings and load new key bindings from the specified file.
+Clear all key bindings (not button bindings) and load new bindings from the
+specified file.
 This allows you to load pre-defined key bindings for your keyboard layout.
 See the
 .Sx KEYBOARD MAPPING FILES
@@ -506,7 +502,7 @@ region and
 is enabled, focus on the region with the target workspace.
 Enable by setting to 1.
 .It Ic warp_pointer
-Centers the mouse pointer on the focused window when using key bindings to
+Centers the pointer on the focused window when using bindings to
 change focus, switch workspaces, change regions, etc.
 Enable by setting to 1.
 .It Ic workspace_limit
@@ -585,7 +581,7 @@ screenshot.sh window # optional
 Note that optional default programs will not be validated unless overridden.
 If a default program fails validation, you can resolve the exception
 by installing the program, modifying the program call or disabling the program
-by freeing the respective key binding.
+by freeing the respective binding.
 .Pp
 For example, to override
 .Ic lock :
@@ -601,24 +597,19 @@ bind[] = MOD+Shift+Delete
 .Ed
 .Sh BINDINGS
 .Nm
-provides many functions (or actions) accessed via key or mouse bindings.
-.Pp
-The current mouse bindings are described below:
-.Pp
-.Bl -tag -width "M-j, M-<TAB>XXX" -offset indent -compact
-.It Cm M1
-Focus window
-.It Cm M-M1
-Move window
-.It Cm M-M3
-Resize window
-.It Cm M-S-M3
-Resize window while maintaining it centered
-.El
+provides many functions (or actions) accessed via key or pointer bindings.
 .Pp
-The default key bindings are described below:
+The default bindings are listed below:
 .Pp
 .Bl -tag -width "M-j, M-<TAB>XXXXXX" -offset indent -compact
+.It Cm Ns Aq Cm Button1
+focus
+.It Cm M- Ns Aq Cm Button1
+move
+.It Cm M- Ns Aq Cm Button3
+resize
+.It Cm M-S- Ns Aq Cm Button3
+resize_centered
 .It Cm M-S- Ns Aq Cm Return
 term
 .It Cm M-p
@@ -754,6 +745,16 @@ search_win
 The action names and descriptions are listed below:
 .Pp
 .Bl -tag -width "M-j, M-<TAB>XXXX" -offset indent -compact
+.It Cm focus
+Focus window/region under pointer.
+.It Cm move
+Move window with pointer while binding is pressed.
+.It Cm resize
+Resize window with pointer while binding is pressed.
+.It Cm resize_centered
+Same as
+.Ic resize
+but keep window centered.
 .It Cm term
 Spawn a new terminal
 (see
@@ -896,7 +897,7 @@ Toggle maximization of focused window.
 .It Cm always_raise
 When set tiled windows are allowed to obscure floating windows.
 .It Cm button2
-Fake a middle mouse button click (mouse button 2).
+Fake a middle mouse button click (Button2).
 .It Cm width_shrink
 Shrink the width of a floating window.
 .It Cm width_grow
@@ -923,27 +924,67 @@ Search the windows in the current workspace.
 .Pp
 Custom bindings in the configuration file are specified as follows:
 .Pp
-.Dl bind Ns Bo Ar action Bc = Ar keys
+.Dl bind Ns Bo Ar action Bc = Ar combo
 .Pp
 .Ar action
 is one of the actions listed above (or empty to unbind) and
-.Ar keys
-is in the form of zero or more modifier keys
-(MOD, Mod1, Shift, etc.) and one or more normal keys
-(b, Space, etc.), separated by
+.Ar combo
+is in the form of zero or more modifier keys and/or special arguments
+(Mod1, Shift, MOD, etc.) and a normal key (b, Space, etc.)
+or a button (Button1 .. Button255), separated by
 .Ql + .
+Multiple key/button combinations may be bound to the same action.
+.Pp
+Special arguments:
+.Bl -tag -width "anymodxxxx" -offset indent -compact
+.It Cm MOD
+Substituted for the currently defined
+.Ic modkey .
+.It Cm ANYMOD
+Select all modifier combinations not handled by another binding.
+.It Cm REPLAY
+Reprocess binding press/release events for other programs to handle.  Unavailable for
+.Ic move ,
+.Ic resize
+and
+.Ic resize_centered .
+.El
 .Pp
-Example:
+.Cm MOD
+example:
 .Bd -literal -offset indent
 bind[reset] = Mod4+q # bind Windows-key + q to reset
 bind[] = Mod1+q # unbind Alt + q
+bind[move] = MOD+Button3 # Bind move to M-Button3
+bind[] = MOD+Button1 # Unbind default move binding.
 .Ed
 .Pp
-To use the currently defined
-.Ic modkey ,
-specify MOD as the modifier key.
+.Cm ANYMOD
+example:
+.Bd -literal -offset indent
+bind[focus] = ANYMOD+Button3
+bind[move] = MOD+Button3
+.Ed
+.Pp
+In the above example,
+.Cm M- Ns Aq Cm Button3
+initiates
+.Ic move
+and
+.Cm Aq Cm Button3
+pressed with any other combination of modifiers
+sets focus to the window/region under the pointer.
+.Pp
+.Cm REPLAY
+example:
+.Bd -literal -offset indent
+bind[focus] = REPLAY+Button3
+.Ed
 .Pp
-Multiple key combinations may be bound to the same action.
+In the above example, when
+.Cm Aq Cm Button3
+is pressed without any modifier(s), focus is set to the window under the
+pointer and the button press is passed to the window.
 .Pp
 To bind non-latin characters such as \[oa] or \[*p] you must enter the xkb
 character name instead of the character itself.
index 6a70b6a511b10f751526e32337909fea11216465..056550bfb12bbf78986586ddf1bd7786cfbadf14 100644 (file)
@@ -235,7 +235,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,7 +336,7 @@ bool                        cycle_empty = false;
 bool                   cycle_visible = false;
 int                    term_width = 0;
 int                    font_adjusted = 0;
-unsigned int           mod_key = MODKEY;
+uint16_t               mod_key = MODKEY;
 bool                   warp_focus = false;
 bool                   warp_pointer = false;
 bool                   workspace_clamp = false;
@@ -821,137 +825,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
+};
+
+enum {
+       BINDING_F_REPLAY = 0x1,
 };
-RB_HEAD(key_tree, key);
+
+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 *);
@@ -969,7 +992,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 *);
@@ -978,10 +1001,17 @@ 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);
@@ -996,11 +1026,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);
@@ -1023,16 +1053,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 *);
@@ -1041,6 +1072,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);
@@ -1053,6 +1085,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 *);
@@ -1060,18 +1093,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
@@ -1084,36 +1114,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);
@@ -1122,13 +1155,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);
@@ -1136,15 +1171,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);
@@ -1162,42 +1197,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)
@@ -1746,7 +1781,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;
@@ -1754,6 +1789,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) {
@@ -1802,8 +1838,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;
 }
@@ -1968,6 +2005,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, "
@@ -2558,12 +2597,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;
 
@@ -2876,9 +2916,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;
 
@@ -3023,9 +3064,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;
 
@@ -3267,9 +3309,10 @@ fake_keypress(struct ws_win *win, xcb_keysym_t keysym, uint16_t modifiers)
 }
 
 void
-restart(struct swm_region *r, union arg *args)
+restart(struct binding *bp, struct swm_region *r, union arg *args)
 {
        /* suppress unused warning since var is needed */
+       (void)bp;
        (void)r;
        (void)args;
 
@@ -3279,7 +3322,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 *
@@ -3697,8 +3740,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
@@ -3819,7 +3861,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);
 
@@ -3895,7 +3937,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;
@@ -3904,6 +3946,8 @@ switchws(struct swm_region *r, union arg *args)
        int                     wsid = args->id;
        bool                    unmap_old = false;
 
+       (void)bp;
+
        if (!(r && r->s))
                return;
 
@@ -4002,7 +4046,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;
@@ -4044,16 +4088,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;
 
@@ -4066,16 +4110,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))
@@ -4097,7 +4143,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;
@@ -4140,7 +4186,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;
@@ -4173,12 +4219,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);
 
@@ -4425,7 +4473,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;
@@ -4527,7 +4575,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:
@@ -4546,11 +4594,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);
@@ -4571,10 +4632,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);
 
@@ -5211,7 +5274,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;
@@ -5233,7 +5296,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 *
@@ -5255,11 +5318,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
@@ -5409,9 +5474,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,
@@ -5421,9 +5487,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)
@@ -5441,11 +5508,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)
@@ -5493,13 +5561,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)
@@ -5542,10 +5612,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)
@@ -5564,12 +5636,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)
@@ -5607,7 +5681,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;
@@ -5624,6 +5698,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;
@@ -5972,7 +6048,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
@@ -6068,8 +6144,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);
 
@@ -6103,11 +6181,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)
@@ -6135,11 +6214,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)
@@ -6293,22 +6373,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;
@@ -6344,27 +6489,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);
@@ -6390,7 +6535,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 :
@@ -6401,18 +6546,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;
@@ -6421,7 +6579,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;
 
@@ -6441,7 +6599,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;
 
@@ -6468,8 +6626,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);
@@ -6500,17 +6666,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();
 }
 
@@ -6543,15 +6727,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;
@@ -6589,28 +6772,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);
@@ -6630,21 +6812,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;
 
@@ -6658,6 +6859,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,
@@ -6667,7 +6874,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;
@@ -6697,202 +6904,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
@@ -7227,18 +7413,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;
 
@@ -7297,40 +7482,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)
@@ -7341,21 +7529,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,
-                                   "parsekeys: invalid key %s\n",
-                                   name);
+                                   "parsebinding: invalid button %u\n", *val);
+                               return (1);
+                       }
+               } else {
+                       /* TODO: do this without Xlib. */
+                       ks = XStringToKeysym(name);
+                       if (ks == NoSymbol) {
+                               DNPRINTF(SWM_D_KEY,
+                                   "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);
 }
 
@@ -7368,97 +7574,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;
 
-       kp.keysym = ks;
-       kp.mod = mod;
+       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);
 
-       return (RB_FIND(key_tree, &keys, &kp));
+       if ((bp = malloc(sizeof *bp)) == NULL)
+               err(1, "binding_insert: malloc");
+
+       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);
 
-       key_insert(mod, ks, kfid, spawn_name);
-       DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
+       if (aid != FN_INVALID)
+               binding_insert(mod, type, val, aid, flags, spawn_name);
+
+       DNPRINTF(SWM_D_KEY, "setbinding: leave\n");
 }
 
 int
 setconfbinding(const char *selector, const char *value, int flags)
 {
-       enum keyfuncid          kfid;
-       unsigned int            mod;
-       KeySym                  ks;
        struct spawn_prog       *sp;
+       uint32_t                keybtn, opts;
+       uint16_t                mod;
+       enum actionid           aid;
+       enum binding_type       type;
 
        /* suppress unused warning since var is needed */
        (void)flags;
@@ -7467,21 +7680,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);
@@ -7491,8 +7704,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
@@ -7502,143 +7715,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_keys(void)
+clear_bindings(void)
 {
-       struct key              *kp;
+       struct binding          *bp;
 
-       while (RB_EMPTY(&keys) == 0) {
-               kp = RB_ROOT(&keys);
-               key_remove(kp);
+       while ((bp = RB_ROOT(&bindings)))
+               binding_remove(bp);
+}
+
+void
+clear_keybindings(void)
+{
+       struct binding          *bp, *bptmp;
+
+       RB_FOREACH_SAFE(bp, binding_tree, &bindings, bptmp) {
+               if (bp->type != KEYBIND)
+                       continue;
+               binding_remove(bp);
        }
 }
 
@@ -7655,11 +7895,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);
@@ -7703,9 +7943,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");
@@ -7722,38 +7962,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;
@@ -7761,14 +8026,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[] = {
@@ -8839,6 +9147,7 @@ conf_load(const char *filename, int keymapping)
        if (line)
                free(line);
        fclose(config);
+
        DNPRINTF(SWM_D_CONF, "conf_load: end\n");
 
        return (0);
@@ -9096,6 +9405,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);
 
@@ -9223,8 +9537,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,
@@ -9311,8 +9623,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);
 
@@ -9322,33 +9638,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, "
@@ -9357,22 +9725,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);
@@ -9383,36 +9742,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));
+       replay = bp->flags & BINDING_F_REPLAY;
 
-       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;
-               }
+       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);
        }
@@ -9420,7 +9788,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)
 {
@@ -9731,6 +10150,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
@@ -9739,14 +10213,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) {
@@ -9755,13 +10232,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. */
@@ -9796,14 +10272,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
 
@@ -9848,22 +10327,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
@@ -9940,6 +10409,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",
@@ -9948,8 +10419,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;
 
@@ -10014,14 +10483,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) {
@@ -10156,7 +10625,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();
                }
 
@@ -10309,11 +10778,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;
@@ -10830,7 +11294,7 @@ shutdown_cleanup(void)
 
        clear_quirks();
        clear_spawns();
-       clear_keys();
+       clear_bindings();
 
        teardown_ewmh();
 
@@ -10925,7 +11389,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);
@@ -10943,7 +11407,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);
@@ -11027,7 +11491,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);
@@ -11044,7 +11508,8 @@ main(int argc, char *argv[])
        setup_globals();
        setup_screens();
        setup_ewmh();
-       setup_keys();
+       setup_keybindings();
+       setup_btnbindings();
        setup_quirks();
        setup_spawn();
 
@@ -11103,6 +11568,7 @@ noconfig:
        grab_windows();
 
        grabkeys();
+       grabbuttons();
        stack();
        bar_draw();
 
@@ -11121,7 +11587,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);
@@ -11152,7 +11618,7 @@ noconfig:
                }
 
                if (restart_wm)
-                       restart(NULL, NULL);
+                       restart(NULL, NULL, NULL);
 
                if (search_resp)
                        search_do_resp();