diff --git a/include/sway/config.h b/include/sway/config.h
index b8da29c53..92536d103 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -22,14 +22,28 @@ struct sway_variable {
 	char *value;
 };
 
+
+enum binding_input_type {
+	BINDING_KEYCODE,
+	BINDING_KEYSYM,
+	BINDING_MOUSE,
+};
+
+enum binding_flags {
+	BINDING_RELEASE=1,
+	BINDING_LOCKED=2, // keyboard only
+	BINDING_BORDER=4, // mouse only; trigger on container border
+	BINDING_CONTENTS=8, // mouse only; trigger on container contents
+	BINDING_TITLEBAR=16 // mouse only; trigger on container titlebar
+};
+
 /**
  * A key binding and an associated command.
  */
 struct sway_binding {
+	enum binding_input_type type;
 	int order;
-	bool release;
-	bool locked;
-	bool bindcode;
+	uint32_t flags;
 	list_t *keys; // sorted in ascending order
 	uint32_t modifiers;
 	char *command;
@@ -50,6 +64,7 @@ struct sway_mode {
 	char *name;
 	list_t *keysym_bindings;
 	list_t *keycode_bindings;
+	list_t *mouse_bindings;
 	bool pango;
 };
 
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index 83e9e432a..6910237f2 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -34,11 +34,14 @@ void free_sway_binding(struct sway_binding *binding) {
  */
 static bool binding_key_compare(struct sway_binding *binding_a,
 		struct sway_binding *binding_b) {
-	if (binding_a->release != binding_b->release) {
+	if (binding_a->type != binding_b->type) {
 		return false;
 	}
 
-	if (binding_a->bindcode != binding_b->bindcode) {
+	uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER
+			| BINDING_CONTENTS | BINDING_TITLEBAR;
+	if ((binding_a->flags & conflict_generating_flags) !=
+			(binding_b->flags & conflict_generating_flags)) {
 		return false;
 	}
 
@@ -69,6 +72,66 @@ static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) {
 	return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0);
 }
 
+
+/**
+ * From a keycode, bindcode, or bindsym name and the most likely binding type,
+ * identify the appropriate numeric value corresponding to the key. Return NULL
+ * and set *key_val if successful, otherwise return a specific error. Change
+ * the value of *type if the initial type guess was incorrect and if this
+ * was the first identified key.
+ */
+static struct cmd_results *identify_key(const char* name, bool first_key,
+		uint32_t* key_val, enum binding_input_type* type) {
+	if (*type == BINDING_KEYCODE) {
+		// check for keycode
+		xkb_keycode_t keycode = strtol(name, NULL, 10);
+		if (!xkb_keycode_is_legal_ext(keycode)) {
+			return cmd_results_new(CMD_INVALID, "bindcode",
+					"Invalid keycode '%s'", name);
+		}
+		*key_val = keycode;
+	} else {
+		// check for keysym
+		xkb_keysym_t keysym = xkb_keysym_from_name(name,
+				XKB_KEYSYM_CASE_INSENSITIVE);
+
+		// Check for mouse binding
+		uint32_t button = 0;
+		if (strncasecmp(name, "button", strlen("button")) == 0 &&
+				strlen(name) == strlen("button0")) {
+			button = name[strlen("button")] - '1' + BTN_LEFT;
+		}
+
+		if (*type == BINDING_KEYSYM) {
+			if (button) {
+				if (first_key) {
+					*type = BINDING_MOUSE;
+					*key_val = button;
+				} else {
+					return cmd_results_new(CMD_INVALID, "bindsym",
+							"Mixed button '%s' into key sequence", name);
+				}
+			} else if (keysym) {
+				*key_val = keysym;
+			} else {
+				return cmd_results_new(CMD_INVALID, "bindsym",
+						"Unknown key '%s'", name);
+			}
+		} else {
+			if (button) {
+				*key_val = button;
+			} else if (keysym) {
+				return cmd_results_new(CMD_INVALID, "bindsym",
+						"Mixed keysym '%s' into button sequence", name);
+			} else {
+				return cmd_results_new(CMD_INVALID, "bindsym",
+						"Unknown button '%s'", name);
+			}
+		}
+	}
+	return NULL;
+}
+
 static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
 		bool bindcode) {
 	const char *bindtype = bindcode ? "bindcode" : "bindsym";
@@ -85,22 +148,34 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
 	}
 	binding->keys = create_list();
 	binding->modifiers = 0;
-	binding->release = false;
-	binding->locked = false;
-	binding->bindcode = bindcode;
+	binding->flags = 0;
+	binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM;
+
+	bool exclude_titlebar = false;
 
 	// Handle --release and --locked
 	while (argc > 0) {
 		if (strcmp("--release", argv[0]) == 0) {
-			binding->release = true;
+			binding->flags |= BINDING_RELEASE;
 		} else if (strcmp("--locked", argv[0]) == 0) {
-			binding->locked = true;
+			binding->flags |= BINDING_LOCKED;
+		} else if (strcmp("--whole-window", argv[0]) == 0) {
+			binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR;
+		} else if (strcmp("--border", argv[0]) == 0) {
+			binding->flags |= BINDING_BORDER;
+		} else if (strcmp("--exclude-titlebar", argv[0]) == 0) {
+			exclude_titlebar = true;
 		} else {
 			break;
 		}
 		argv++;
 		argc--;
 	}
+	if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR)
+			|| exclude_titlebar) {
+		binding->type = BINDING_MOUSE;
+	}
+
 	if (argc < 2) {
 		free_sway_binding(binding);
 		return cmd_results_new(CMD_FAILURE, bindtype,
@@ -119,64 +194,47 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
 			continue;
 		}
 
-		xkb_keycode_t keycode;
-		xkb_keysym_t keysym;
-		if (bindcode) {
-			// parse keycode
-			keycode = (int)strtol(split->items[i], NULL, 10);
-			if (!xkb_keycode_is_legal_ext(keycode)) {
-				error =
-					cmd_results_new(CMD_INVALID, "bindcode",
-						"Invalid keycode '%s'", (char *)split->items[i]);
-				free_sway_binding(binding);
-				list_free(split);
-				return error;
-			}
-		} else {
-			// Check for xkb key
-			 keysym = xkb_keysym_from_name(split->items[i],
-					XKB_KEYSYM_CASE_INSENSITIVE);
-
-			// Check for mouse binding
-			if (strncasecmp(split->items[i], "button", strlen("button")) == 0 &&
-					strlen(split->items[i]) == strlen("button0")) {
-				keysym = ((char *)split->items[i])[strlen("button")] - '1' + BTN_LEFT;
-			}
-			if (!keysym) {
-				struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym",
-						"Unknown key '%s'", (char *)split->items[i]);
-				free_sway_binding(binding);
-				free_flat_list(split);
-				return ret;
-			}
+		// Identify the key and possibly change binding->type
+		uint32_t key_val = 0;
+		error = identify_key(split->items[i], binding->keys->length == 0,
+				     &key_val, &binding->type);
+		if (error) {
+			free_sway_binding(binding);
+			list_free(split);
+			return error;
 		}
+
 		uint32_t *key = calloc(1, sizeof(uint32_t));
 		if (!key) {
 			free_sway_binding(binding);
 			free_flat_list(split);
 			return cmd_results_new(CMD_FAILURE, bindtype,
-					"Unable to allocate binding");
+					"Unable to allocate binding key");
 		}
-
-		if (bindcode) {
-			*key = (uint32_t)keycode;
-		} else {
-			*key = (uint32_t)keysym;
-		}
-
+		*key = key_val;
 		list_add(binding->keys, key);
 	}
 	free_flat_list(split);
 	binding->order = binding_order++;
 
+	// refine region of interest for mouse binding once we are certain
+	// that this is one
+	if (exclude_titlebar) {
+		binding->flags &= ~BINDING_TITLEBAR;
+	} else if (binding->type == BINDING_MOUSE) {
+		binding->flags |= BINDING_TITLEBAR;
+	}
+
 	// sort ascending
 	list_qsort(binding->keys, key_qsort_cmp);
 
 	list_t *mode_bindings;
-	if (bindcode) {
+	if (binding->type == BINDING_KEYCODE) {
 		mode_bindings = config->current_mode->keycode_bindings;
-	} else {
+	} else if (binding->type == BINDING_KEYSYM) {
 		mode_bindings = config->current_mode->keysym_bindings;
+	} else {
+		mode_bindings = config->current_mode->mouse_bindings;
 	}
 
 	// overwrite the binding if it already exists
diff --git a/sway/config.c b/sway/config.c
index ed624bfad..c2310ff72 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -56,6 +56,12 @@ static void free_mode(struct sway_mode *mode) {
 		}
 		list_free(mode->keycode_bindings);
 	}
+	if (mode->mouse_bindings) {
+		for (i = 0; i < mode->mouse_bindings->length; i++) {
+			free_sway_binding(mode->mouse_bindings->items[i]);
+		}
+		list_free(mode->mouse_bindings);
+	}
 	free(mode);
 }
 
@@ -172,6 +178,7 @@ static void config_defaults(struct sway_config *config) {
 	strcpy(config->current_mode->name, "default");
 	if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup;
 	if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
+	if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
 	list_add(config->modes, config->current_mode);
 
 	config->floating_mod = 0;
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index ede38519c..e6c5c3354 100644
--- a/sway/input/keyboard.c
+++ b/sway/input/keyboard.c
@@ -88,11 +88,13 @@ static void get_active_binding(const struct sway_shortcut_state *state,
 		uint32_t modifiers, bool release, bool locked) {
 	for (int i = 0; i < bindings->length; ++i) {
 		struct sway_binding *binding = bindings->items[i];
+		bool binding_locked = binding->flags | BINDING_LOCKED;
+		bool binding_release = binding->flags | BINDING_RELEASE;
 
 		if (modifiers ^ binding->modifiers ||
 				state->npressed != (size_t)binding->keys->length ||
-				locked > binding->locked ||
-				release != binding->release) {
+				release != binding_release ||
+				locked > binding_locked) {
 			continue;
 		}