From 4a8b50603ede4a3735f426d5426134de051c9ef7 Mon Sep 17 00:00:00 2001
From: Consolatis <35009135+Consolatis@users.noreply.github.com>
Date: Sun, 29 Jan 2023 04:06:46 +0100
Subject: [PATCH] src/config/rcxml.c: allow clearing key/mouse bindings
Fixes #567
---
 docs/rc.xml                |  7 ++++
 include/config/keybind.h   |  2 +
 include/config/mousebind.h |  1 +
 src/action.c               | 10 +++--
 src/config/keybind.c       | 18 ++++++++-
 src/config/mousebind.c     | 11 ++++++
 src/config/rcxml.c         | 77 ++++++++++++++++++++++++++++++++------
 src/keyboard.c             |  2 +-
 src/menu/menu.c            |  5 ++-
 9 files changed, 115 insertions(+), 18 deletions(-)
diff --git a/docs/rc.xml b/docs/rc.xml
index fe95e2f7..3bf2d116 100644
--- a/docs/rc.xml
+++ b/docs/rc.xml
@@ -23,6 +23,13 @@
     
       
     
+    
+    
+      
+    
   
 
   
diff --git a/include/config/keybind.h b/include/config/keybind.h
index b11914ed..e6c2f324 100644
--- a/include/config/keybind.h
+++ b/include/config/keybind.h
@@ -28,4 +28,6 @@ struct keybind *keybind_create(const char *keybind);
  */
 uint32_t parse_modifier(const char *symname);
 
+bool keybind_the_same(struct keybind *a, struct keybind *b);
+
 #endif /* __LABWC_KEYBIND_H */
diff --git a/include/config/mousebind.h b/include/config/mousebind.h
index f7dec7de..b6335bdf 100644
--- a/include/config/mousebind.h
+++ b/include/config/mousebind.h
@@ -48,5 +48,6 @@ enum mouse_event mousebind_event_from_str(const char *str);
 uint32_t mousebind_button_from_str(const char *str, uint32_t *modifiers);
 enum direction mousebind_direction_from_str(const char *str, uint32_t *modifiers);
 struct mousebind *mousebind_create(const char *context);
+bool mousebind_the_same(struct mousebind *a, struct mousebind *b);
 
 #endif /* __LABWC_MOUSEBIND_H */
diff --git a/src/action.c b/src/action.c
index 09992ef1..5668a628 100644
--- a/src/action.c
+++ b/src/action.c
@@ -157,8 +157,14 @@ action_create(const char *action_name)
 		wlr_log(WLR_ERROR, "action name not specified");
 		return NULL;
 	}
+
+	enum action_type action_type = action_type_from_str(action_name);
+	if (action_type == ACTION_TYPE_NONE) {
+		return NULL;
+	}
+
 	struct action *action = znew(*action);
-	action->type = action_type_from_str(action_name);
+	action->type = action_type;
 	wl_list_init(&action->args);
 	return action;
 }
@@ -445,8 +451,6 @@ actions_run(struct view *activator, struct server *server,
 				wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
 			}
 			break;
-		case ACTION_TYPE_NONE:
-			break;
 		case ACTION_TYPE_INVALID:
 			wlr_log(WLR_ERROR, "Not executing unknown action");
 			break;
diff --git a/src/config/keybind.c b/src/config/keybind.c
index 858e0207..5700c54c 100644
--- a/src/config/keybind.c
+++ b/src/config/keybind.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #define _POSIX_C_SOURCE 200809L
+#include 
 #include 
 #include 
 #include 
@@ -26,13 +27,28 @@ parse_modifier(const char *symname)
 	}
 }
 
+bool
+keybind_the_same(struct keybind *a, struct keybind *b)
+{
+	assert(a && b);
+	if (a->modifiers != b->modifiers || a->keysyms_len != b->keysyms_len) {
+		return false;
+	}
+	for (size_t i = 0; i < a->keysyms_len; i++) {
+		if (a->keysyms[i] != b->keysyms[i]) {
+			return false;
+		}
+	}
+	return true;
+}
+
 struct keybind *
 keybind_create(const char *keybind)
 {
 	struct keybind *k = znew(*k);
 	xkb_keysym_t keysyms[MAX_KEYSYMS];
 	gchar **symnames = g_strsplit(keybind, "-", -1);
-	for (int i = 0; symnames[i]; i++) {
+	for (size_t i = 0; symnames[i]; i++) {
 		char *symname = symnames[i];
 		uint32_t modifier = parse_modifier(symname);
 		if (modifier != 0) {
diff --git a/src/config/mousebind.c b/src/config/mousebind.c
index 4c37ad5f..c578c46b 100644
--- a/src/config/mousebind.c
+++ b/src/config/mousebind.c
@@ -137,6 +137,17 @@ context_from_str(const char *str)
 	return LAB_SSD_NONE;
 }
 
+bool
+mousebind_the_same(struct mousebind *a, struct mousebind *b)
+{
+	assert(a && b);
+	return a->context == b->context
+		&& a->button == b->button
+		&& a->direction == b->direction
+		&& a->mouse_event == b->mouse_event
+		&& a->modifiers == b->modifiers;
+}
+
 struct mousebind *
 mousebind_create(const char *context)
 {
diff --git a/src/config/rcxml.c b/src/config/rcxml.c
index cbb0ca83..dd33750e 100644
--- a/src/config/rcxml.c
+++ b/src/config/rcxml.c
@@ -111,8 +111,10 @@ fill_keybind(char *nodename, char *content)
 			"nodename: '%s' content: '%s'", nodename, content);
 	} else if (!strcmp(nodename, "name.action")) {
 		current_keybind_action = action_create(content);
-		wl_list_append(¤t_keybind->actions,
-			¤t_keybind_action->link);
+		if (current_keybind_action) {
+			wl_list_append(¤t_keybind->actions,
+				¤t_keybind_action->link);
+		}
 	} else if (!current_keybind_action) {
 		wlr_log(WLR_ERROR, "expect  element first. "
 			"nodename: '%s' content: '%s'", nodename, content);
@@ -164,8 +166,10 @@ fill_mousebind(char *nodename, char *content)
 			mousebind_event_from_str(content);
 	} else if (!strcmp(nodename, "name.action")) {
 		current_mousebind_action = action_create(content);
-		wl_list_append(¤t_mousebind->actions,
-			¤t_mousebind_action->link);
+		if (current_mousebind_action) {
+			wl_list_append(¤t_mousebind->actions,
+				¤t_mousebind_action->link);
+		}
 	} else if (!current_mousebind_action) {
 		wlr_log(WLR_ERROR, "expect  element first. "
 			"nodename: '%s' content: '%s'", nodename, content);
@@ -683,20 +687,17 @@ load_default_mouse_bindings(void)
 }
 
 static void
-merge_mouse_bindings(void)
+deduplicate_mouse_bindings(void)
 {
 	uint32_t replaced = 0;
+	uint32_t cleared = 0;
 	struct mousebind *current, *tmp, *existing;
 	wl_list_for_each_safe(existing, tmp, &rc.mousebinds, link) {
 		wl_list_for_each_reverse(current, &rc.mousebinds, link) {
 			if (existing == current) {
 				break;
 			}
-			if (existing->context == current->context
-					&& existing->button == current->button
-					&& existing->direction == current->direction
-					&& existing->mouse_event == current->mouse_event
-					&& existing->modifiers == current->modifiers) {
+			if (mousebind_the_same(existing, current)) {
 				wl_list_remove(&existing->link);
 				action_list_free(&existing->actions);
 				free(existing);
@@ -705,9 +706,54 @@ merge_mouse_bindings(void)
 			}
 		}
 	}
+	wl_list_for_each_safe(current, tmp, &rc.mousebinds, link) {
+		if (wl_list_empty(¤t->actions)) {
+			wl_list_remove(¤t->link);
+			free(current);
+			cleared++;
+		}
+	}
 	if (replaced) {
 		wlr_log(WLR_DEBUG, "Replaced %u mousebinds", replaced);
 	}
+	if (cleared) {
+		wlr_log(WLR_DEBUG, "Cleared %u mousebinds", cleared);
+	}
+}
+
+static void
+deduplicate_key_bindings(void)
+{
+	uint32_t replaced = 0;
+	uint32_t cleared = 0;
+	struct keybind *current, *tmp, *existing;
+	wl_list_for_each_safe(existing, tmp, &rc.keybinds, link) {
+		wl_list_for_each_reverse(current, &rc.keybinds, link) {
+			if (existing == current) {
+				break;
+			}
+			if (keybind_the_same(existing, current)) {
+				wl_list_remove(&existing->link);
+				action_list_free(&existing->actions);
+				free(existing);
+				replaced++;
+				break;
+			}
+		}
+	}
+	wl_list_for_each_safe(current, tmp, &rc.keybinds, link) {
+		if (wl_list_empty(¤t->actions)) {
+			wl_list_remove(¤t->link);
+			free(current);
+			cleared++;
+		}
+	}
+	if (replaced) {
+		wlr_log(WLR_DEBUG, "Replaced %u keybinds", replaced);
+	}
+	if (cleared) {
+		wlr_log(WLR_DEBUG, "Cleared %u keybinds", cleared);
+	}
 }
 
 static void
@@ -723,8 +769,15 @@ post_processing(void)
 		load_default_mouse_bindings();
 	}
 
-	/* Replace all earlier mousebindings by later ones */
-	merge_mouse_bindings();
+	/*
+	 * Replace all earlier bindings by later ones
+	 * and clear the ones with an empty action list.
+	 *
+	 * This is required so users are able to remove
+	 * a default binding by using the "None" action.
+	 */
+	deduplicate_key_bindings();
+	deduplicate_mouse_bindings();
 
 	if (!rc.font_activewindow.name) {
 		rc.font_activewindow.name = xstrdup("sans");
diff --git a/src/keyboard.c b/src/keyboard.c
index 1749aaf3..894ae639 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -81,7 +81,7 @@ static bool
 handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym)
 {
 	struct keybind *keybind;
-	wl_list_for_each_reverse(keybind, &rc.keybinds, link) {
+	wl_list_for_each(keybind, &rc.keybinds, link) {
 		if (modifiers ^ keybind->modifiers) {
 			continue;
 		}
diff --git a/src/menu/menu.c b/src/menu/menu.c
index cd463429..ecbeb50b 100644
--- a/src/menu/menu.c
+++ b/src/menu/menu.c
@@ -279,7 +279,10 @@ fill_item(char *nodename, char *content)
 		 */
 	} else if (!strcmp(nodename, "name.action")) {
 		current_item_action = action_create(content);
-		wl_list_append(¤t_item->actions, ¤t_item_action->link);
+		if (current_item_action) {
+			wl_list_append(¤t_item->actions,
+				¤t_item_action->link);
+		}
 	} else if (!current_item_action) {
 		wlr_log(WLR_ERROR, "expect  element first. "
 			"nodename: '%s' content: '%s'", nodename, content);