commit 210aa3d51d8dcc1e36c73d3404e032937271e0d6
parent 71f3528512ae0666e68994bd70dccb933bed55f8
Author: lumidify <nobody@lumidify.org>
Date:   Wed,  1 May 2024 21:24:44 +0200
Restructure configuration parsing for keybindings
It's still really ugly.
Diffstat:
| M | Makefile |  |  | 1 | - | 
| M | src/ltk/config.c |  |  | 158 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- | 
| M | src/ltk/config.h |  |  | 40 | ++++++++++++++++++++++++++-------------- | 
| M | src/ltk/entry.c |  |  | 209 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- | 
| M | src/ltk/entry.h |  |  | 16 | +++++++++------- | 
| D | src/ltk/keys.h |  |  | 78 | ------------------------------------------------------------------------------ | 
| M | src/ltk/ltk.c |  |  | 66 | ++---------------------------------------------------------------- | 
| A | src/ltk/sort_search.h |  |  | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | src/ltk/window.c |  |  | 155 | +++++++++++++++++++++++++++++++------------------------------------------------ | 
| M | src/ltk/window.h |  |  | 17 | +++++++++-------- | 
10 files changed, 416 insertions(+), 400 deletions(-)
diff --git a/Makefile b/Makefile
@@ -117,7 +117,6 @@ HDR_LTK = \
 	src/ltk/label.h \
 	src/ltk/config.h \
 	src/ltk/array.h \
-	src/ltk/keys.h \
 	src/ltk/clipboard_xlib.h \
 	src/ltk/clipboard.h \
 	src/ltk/txtbuf.h \
diff --git a/src/ltk/config.c b/src/ltk/config.c
@@ -21,10 +21,122 @@
 #include <limits.h>
 
 #include "util.h"
-#include "keys.h"
 #include "memory.h"
 #include "config.h"
+#include "sort_search.h"
+
+#include "entry.h"
+#include "window.h"
+
+GEN_SORT_SEARCH_HELPERS(keybinding, ltk_keybinding_cb, text)
+//GEN_SORT_SEARCH_HELPERS(theme, ltk_theme_parseinfo, key)
+LTK_ARRAY_INIT_IMPL(keypress, ltk_keypress_cfg)
+LTK_ARRAY_INIT_IMPL(keyrelease, ltk_keyrelease_cfg)
+
+static struct {
+	const char *name;
+	void (*get_parseinfo)(
+		ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
+		ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
+		ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
+	);
+} keybinding_handlers[] = {
+	{"entry", <k_entry_get_keybinding_parseinfo},
+	{"window", <k_window_get_keybinding_parseinfo},
+};
+
+static int
+register_keypress(ltk_array(keypress) *bindings, ltk_keybinding_cb *arr, size_t arrlen, const char *func_name, size_t func_len, ltk_keypress_binding b) {
+	ltk_keybinding_cb *cb = keybinding_get_entry(arr, arrlen, func_name, func_len);
+	if (!cb)
+		return 1;
+	ltk_keypress_cfg cfg = {b, *cb};
+	ltk_array_append(keypress, bindings, cfg);
+	return 0;
+}
+
+static int
+register_keyrelease(ltk_array(keyrelease) *bindings, ltk_keybinding_cb *arr, size_t arrlen, const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
+	ltk_keybinding_cb *cb = keybinding_get_entry(arr, arrlen, func_name, func_len);
+	if (!cb)
+		return 1;
+	ltk_keyrelease_cfg cfg = {b, *cb};
+	ltk_array_append(keyrelease, bindings, cfg);
+	return 0;
+}
+
+static int
+handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) {
+	ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL;
+	size_t press_len = 0, release_len = 0;
+	ltk_array(keypress) *presses = NULL;
+	ltk_array(keyrelease) *releases = NULL;
+	for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) {
+		if (str_array_equal(keybinding_handlers[i].name, widget_name, wlen)) {
+			keybinding_handlers[i].get_parseinfo(
+				&press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases
+			);
+			if (!press_cbs || !presses)
+				return 1;
+			return register_keypress(presses, press_cbs, press_len, name, nlen, b);
+		}
+	}
+	return 1;
+}
+
+static int
+handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) {
+	ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL;
+	size_t press_len = 0, release_len = 0;
+	ltk_array(keypress) *presses = NULL;
+	ltk_array(keyrelease) *releases = NULL;
+	for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) {
+		if (str_array_equal(keybinding_handlers[i].name, widget_name, wlen)) {
+			keybinding_handlers[i].get_parseinfo(
+				&press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases
+			);
+			if (!release_cbs || !releases)
+				return 1;
+			return register_keyrelease(releases, release_cbs, release_len, name, nlen, b);
+		}
+	}
+	return 1;
+}
+
+static void
+sort_keybindings(void) {
+	ltk_keybinding_cb *press_cbs = NULL, *release_cbs = NULL;
+	size_t press_len = 0, release_len = 0;
+	ltk_array(keypress) *presses = NULL;
+	ltk_array(keyrelease) *releases = NULL;
+	for (size_t i = 0; i < LENGTH(keybinding_handlers); i++) {
+		keybinding_handlers[i].get_parseinfo(
+			&press_cbs, &press_len, &release_cbs, &release_len, &presses, &releases
+		);
+		if (press_cbs)
+			keybinding_sort(press_cbs, press_len);
+		if (release_cbs)
+			keybinding_sort(release_cbs, release_len);
+	}
+}
+
+static void
+destroy_keypress_cfg(ltk_keypress_cfg cfg) {
+	ltk_free(cfg.b.text);
+	ltk_free(cfg.b.rawtext);
+}
+
+void
+ltk_keypress_bindings_destroy(ltk_array(keypress) *arr) {
+        ltk_array_destroy_deep(keypress, arr, &destroy_keypress_cfg);
+}
+
+void
+ltk_keyrelease_bindings_destroy(ltk_array(keyrelease) *arr) {
+        ltk_array_destroy(keyrelease, arr);
+}
 
+static void sort_keysyms(void);
 static int parse_keysym(char *text, size_t len, ltk_keysym *sym_ret);
 
 ltk_config *global_config = NULL;
@@ -361,8 +473,6 @@ parse_keybinding(
     struct token *tok,
     char *widget,
     size_t len,
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
     char **errstr) {
 	char *msg = NULL;
 	*tok = next_token(s);
@@ -379,7 +489,7 @@ parse_keybinding(
 		size_t nlen;
 		if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr))
 			return 1;
-		if (press_handler(widget, len, name, nlen, b)) {
+		if (handle_keypress_binding(widget, len, name, nlen, b)) {
 			msg = "Invalid key binding";
 			goto error;
 		}
@@ -395,7 +505,7 @@ parse_keybinding(
 			msg = "Text and rawtext may only be specified for keypress bindings";
 			goto error;
 		}
-		if (release_handler(widget, len, name, nlen, (ltk_keyrelease_binding){b.sym, b.mods, b.flags})) {
+		if (handle_keyrelease_binding(widget, len, name, nlen, (ltk_keyrelease_binding){b.sym, b.mods, b.flags})) {
 			msg = "Invalid key binding";
 			goto error;
 		}
@@ -502,8 +612,6 @@ load_from_text(
     const char *filename,
     char *file_contents,
     size_t len,
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
     char **errstr) {
 	ltk_config *config = ltk_malloc(sizeof(ltk_config));
 	config->mappings = NULL;
@@ -629,7 +737,7 @@ load_from_text(
 				char *widget = secttok.text + strlen("key-binding:");
 				size_t len = secttok.len - strlen("key-binding:");
 				while (1) {
-					if ((ret = parse_keybinding(&s, &tok, widget, len, press_handler, release_handler, errstr)) > 0) {
+					if ((ret = parse_keybinding(&s, &tok, widget, len, errstr)) > 0) {
 						goto errornomsg;
 					} else if (ret < 0) {
 						start_of_line = 1;
@@ -715,19 +823,17 @@ errornomsg:
 }
 
 int
-ltk_config_parsefile(
-    const char *filename,
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
-    char **errstr) {
+ltk_config_parsefile(const char *filename, char **errstr) {
 	unsigned long len = 0;
 	char *ferrstr = NULL;
+	sort_keysyms();
+	sort_keybindings();
 	char *file_contents = ltk_read_file(filename, &len, &ferrstr);
 	if (!file_contents) {
 		*errstr = ltk_print_fmt("Unable to open file \"%s\": %s", filename, ferrstr);
 		return 1;
 	}
-	int ret = load_from_text(filename, file_contents, len, press_handler, release_handler, errstr);
+	int ret = load_from_text(filename, file_contents, len, errstr);
 	ltk_free(file_contents);
 	return ret;
 }
@@ -750,22 +856,15 @@ const char *default_config = "[general]\n"
 
 /* FIXME: improve this configuration */
 int
-ltk_config_load_default(
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
-    char **errstr) {
+ltk_config_load_default(char **errstr) {
+	sort_keysyms();
+	sort_keybindings();
 	char *config_copied = ltk_strdup(default_config);
-	int ret = load_from_text("<default config>", config_copied, strlen(config_copied), press_handler, release_handler, errstr);
+	int ret = load_from_text("<default config>", config_copied, strlen(config_copied), errstr);
 	ltk_free(config_copied);
 	return ret;
 }
 
-void
-ltk_keypress_binding_destroy(ltk_keypress_binding b) {
-	ltk_free(b.text);
-	ltk_free(b.rawtext);
-}
-
 /* FIXME: which additional ones are needed here? */
 static struct keysym_mapping {
 	char *name;
@@ -859,11 +958,16 @@ static struct keysym_mapping {
 	{"undo", LTK_KEY_UNDO},
 };
 
-GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name)
+GEN_SORT_SEARCH_HELPERS(keysym, struct keysym_mapping, name)
+
+static void
+sort_keysyms(void) {
+	keysym_sort(keysym_map, LENGTH(keysym_map));
+}
 
 static int
 parse_keysym(char *keysym_str, size_t len, ltk_keysym *sym) {
-	struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len);
+	struct keysym_mapping *km = keysym_get_entry(keysym_map, LENGTH(keysym_map), keysym_str, len);
 	if (!km)
 		return 1;
 	*sym = km->keysym;
diff --git a/src/ltk/config.h b/src/ltk/config.h
@@ -19,6 +19,8 @@
 
 #include <stddef.h>
 
+#include "array.h"
+#include "widget.h"
 #include "eventdefs.h"
 
 typedef enum{
@@ -66,24 +68,34 @@ typedef struct {
 	ltk_general_config general;
 } ltk_config;
 
-typedef int (*keypress_binding_handler)(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b);
-typedef int (*keyrelease_binding_handler)(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b);
+typedef int (*ltk_keybinding_func)(ltk_widget *, ltk_key_event *);
+
+typedef struct {
+	char *text;
+	ltk_keybinding_func func;
+} ltk_keybinding_cb;
+
+typedef struct {
+	ltk_keypress_binding b;
+	ltk_keybinding_cb cb;
+} ltk_keypress_cfg;
+
+typedef struct {
+	ltk_keyrelease_binding b;
+	ltk_keybinding_cb cb;
+} ltk_keyrelease_cfg;
+
+LTK_ARRAY_INIT_DECL(keypress, ltk_keypress_cfg)
+LTK_ARRAY_INIT_DECL(keyrelease, ltk_keyrelease_cfg)
 
 void ltk_config_cleanup(void);
 ltk_config *ltk_config_get(void);
 int ltk_config_get_language_index(char *lang, size_t *idx_ret);
 ltk_language_mapping *ltk_config_get_language_mapping(size_t idx);
-int ltk_config_parsefile(
-    const char *filename,
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
-    char **errstr
-);
-int ltk_config_load_default(
-    keypress_binding_handler press_handler,
-    keyrelease_binding_handler release_handler,
-    char **errstr
-);
-void ltk_keypress_binding_destroy(ltk_keypress_binding b);
+int ltk_config_parsefile(const char *filename, char **errstr);
+int ltk_config_load_default(char **errstr);
+
+void ltk_keypress_bindings_destroy(ltk_array(keypress) *arr);
+void ltk_keyrelease_bindings_destroy(ltk_array(keyrelease) *arr);
 
 #endif /* LTK_CONFIG_H */
diff --git a/src/ltk/entry.c b/src/ltk/entry.c
@@ -31,7 +31,6 @@
 #include "event.h"
 #include "eventdefs.h"
 #include "graphics.h"
-#include "keys.h"
 #include "ltk.h"
 #include "memory.h"
 #include "rect.h"
@@ -40,6 +39,7 @@
 #include "txtbuf.h"
 #include "util.h"
 #include "widget.h"
+#include "config.h"
 
 #define MAX_ENTRY_BORDER_WIDTH 10000
 #define MAX_ENTRY_PADDING 50000
@@ -63,31 +63,27 @@ typedef void (*cb_func)(ltk_entry *, ltk_key_event *);
 
 /* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */
 
-static void cursor_to_beginning(ltk_entry *entry, ltk_key_event *event);
-static void cursor_to_end(ltk_entry *entry, ltk_key_event *event);
-static void cursor_left(ltk_entry *entry, ltk_key_event *event);
-static void cursor_right(ltk_entry *entry, ltk_key_event *event);
-static void expand_selection_left(ltk_entry *entry, ltk_key_event *event);
-static void expand_selection_right(ltk_entry *entry, ltk_key_event *event);
-static void selection_to_primary(ltk_entry *entry, ltk_key_event *event);
-static void selection_to_clipboard(ltk_entry *entry, ltk_key_event *event);
-static void switch_selection_side(ltk_entry *entry, ltk_key_event *event);
-static void paste_primary(ltk_entry *entry, ltk_key_event *event);
-static void paste_clipboard(ltk_entry *entry, ltk_key_event *event);
-static void select_all(ltk_entry *entry, ltk_key_event *event);
-static void delete_char_backwards(ltk_entry *entry, ltk_key_event *event);
-static void delete_char_forwards(ltk_entry *entry, ltk_key_event *event);
-static void edit_external(ltk_entry *entry, ltk_key_event *event);
+static int cursor_to_beginning(ltk_widget *self, ltk_key_event *event);
+static int cursor_to_end(ltk_widget *self, ltk_key_event *event);
+static int cursor_left(ltk_widget *self, ltk_key_event *event);
+static int cursor_right(ltk_widget *self, ltk_key_event *event);
+static int expand_selection_left(ltk_widget *self, ltk_key_event *event);
+static int expand_selection_right(ltk_widget *self, ltk_key_event *event);
+static int selection_to_primary(ltk_widget *self, ltk_key_event *event);
+static int selection_to_clipboard(ltk_widget *self, ltk_key_event *event);
+static int switch_selection_side(ltk_widget *self, ltk_key_event *event);
+static int paste_primary(ltk_widget *self, ltk_key_event *event);
+static int paste_clipboard(ltk_widget *self, ltk_key_event *event);
+static int select_all(ltk_widget *self, ltk_key_event *event);
+static int delete_char_backwards(ltk_widget *self, ltk_key_event *event);
+static int delete_char_forwards(ltk_widget *self, ltk_key_event *event);
+static int edit_external(ltk_widget *self, ltk_key_event *event);
+
 static void recalc_ideal_size(ltk_entry *entry);
 static void ensure_cursor_shown(ltk_entry *entry);
 static void insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor);
 
-struct key_cb {
-	char *text;
-	cb_func func;
-};
-
-static struct key_cb cb_map[] = {
+static ltk_keybinding_cb cb_map[] = {
 	{"cursor-left", &cursor_left},
 	{"cursor-right", &cursor_right},
 	{"cursor-to-beginning", &cursor_to_beginning},
@@ -105,61 +101,29 @@ static struct key_cb cb_map[] = {
 	{"switch-selection-side", &switch_selection_side},
 };
 
-struct keypress_cfg {
-	ltk_keypress_binding b;
-	struct key_cb cb;
-};
-
-struct keyrelease_cfg {
-	ltk_keyrelease_binding b;
-	struct key_cb cb;
-};
-
-LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
-
+/* FIXME: also support keyreleases */
 static ltk_array(keypress) *keypresses = NULL;
-static ltk_array(keyrelease) *keyreleases = NULL;
 
-GEN_CB_MAP_HELPERS(cb_map, struct key_cb, text)
-
-int
-ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
+void
+ltk_entry_get_keybinding_parseinfo(
+	ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
+	ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
+	ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
+) {
+	*press_cbs_ret = cb_map;
+	*press_len_ret = LENGTH(cb_map);
+	*release_cbs_ret = NULL;
+	*release_len_ret = 0;
 	if (!keypresses)
 		keypresses = ltk_array_create(keypress, 1);
-	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
-	if (!cb)
-		return 1;
-	struct keypress_cfg cfg = {b, *cb};
-	ltk_array_append(keypress, keypresses, cfg);
-	return 0;
-}
-
-int
-ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
-	if (!keyreleases)
-		keyreleases = ltk_array_create(keyrelease, 1);
-	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
-	if (!cb)
-		return 1;
-	struct keyrelease_cfg cfg = {b, *cb};
-	ltk_array_append(keyrelease, keyreleases, cfg);
-	return 0;
-}
-
-static void
-destroy_keypress_cfg(struct keypress_cfg cfg) {
-	ltk_keypress_binding_destroy(cfg.b);
+	*presses_ret = keypresses;
+	*releases_ret = NULL;
 }
 
 void
 ltk_entry_cleanup(void) {
-	ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
-	ltk_array_destroy(keyrelease, keyreleases);
+	ltk_keypress_bindings_destroy(keypresses);
 	keypresses = NULL;
-	keyreleases = NULL;
 }
 
 static struct ltk_widget_vtable vtable = {
@@ -335,42 +299,50 @@ wipe_selection(ltk_entry *entry) {
 	set_selection(entry, 0, 0);
 }
 
-static void
-cursor_to_beginning(ltk_entry *entry, ltk_key_event *event) {
+static int
+cursor_to_beginning(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	wipe_selection(entry);
 	entry->pos = 0;
 	ensure_cursor_shown(entry);
+	return 0;
 }
 
-static void
-cursor_to_end(ltk_entry *entry, ltk_key_event *event) {
+static int
+cursor_to_end(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	wipe_selection(entry);
 	entry->pos = entry->len;
 	ensure_cursor_shown(entry);
+	return 0;
 }
 
-static void
-cursor_left(ltk_entry *entry, ltk_key_event *event) {
+static int
+cursor_left(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_start != entry->sel_end)
 		entry->pos = entry->sel_start;
 	else
 		entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, -1, NULL);
 	wipe_selection(entry);
 	ensure_cursor_shown(entry);
+	return 0;
 }
 
-static void
-cursor_right(ltk_entry *entry, ltk_key_event *event) {
+static int
+cursor_right(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_start != entry->sel_end)
 		entry->pos = entry->sel_end;
 	else
 		entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, 1, NULL);
 	wipe_selection(entry);
 	ensure_cursor_shown(entry);
+	return 0;
 }
 
 static void
@@ -392,64 +364,79 @@ expand_selection(ltk_entry *entry, int dir) {
 		entry->pos = new;
 		wipe_selection(entry);
 	}
-	selection_to_primary(entry, NULL);
+	selection_to_primary(LTK_CAST_WIDGET(entry), NULL);
 }
 
 /* FIXME: different programs have different behaviors when they set the selection */
 /* FIXME: sometimes, it might be more useful to wipe the selection when sel_end == sel_start */
-static void
-selection_to_primary(ltk_entry *entry, ltk_key_event *event) {
+static int
+selection_to_primary(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_end == entry->sel_start)
-		return;
+		return 0;
 	txtbuf *primary = ltk_clipboard_get_primary_buffer(ltk_get_clipboard());
 	txtbuf_clear(primary);
 	txtbuf_appendn(primary, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
 	ltk_clipboard_set_primary_selection_owner(ltk_get_clipboard());
+	return 0;
 }
 
-static void
-selection_to_clipboard(ltk_entry *entry, ltk_key_event *event) {
+static int
+selection_to_clipboard(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_end == entry->sel_start)
-		return;
+		return 0;
 	txtbuf *clip = ltk_clipboard_get_clipboard_buffer(ltk_get_clipboard());
 	txtbuf_clear(clip);
 	txtbuf_appendn(clip, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
 	ltk_clipboard_set_clipboard_selection_owner(ltk_get_clipboard());
+	return 0;
 }
-static void
-switch_selection_side(ltk_entry *entry, ltk_key_event *event) {
+
+static int
+switch_selection_side(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	entry->sel_side = !entry->sel_side;
+	return 0;
 }
 
-static void
-paste_primary(ltk_entry *entry, ltk_key_event *event) {
+static int
+paste_primary(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	txtbuf *buf = ltk_clipboard_get_primary_text(ltk_get_clipboard());
 	if (buf)
 		insert_text(entry, buf->text, buf->len, 1);
+	return 0;
 }
 
-static void
-paste_clipboard(ltk_entry *entry, ltk_key_event *event) {
+static int
+paste_clipboard(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	txtbuf *buf = ltk_clipboard_get_clipboard_text(ltk_get_clipboard());
 	if (buf)
 		insert_text(entry, buf->text, buf->len, 1);
+	return 0;
 }
 
-static void
-expand_selection_left(ltk_entry *entry, ltk_key_event *event) {
+static int
+expand_selection_left(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	expand_selection(entry, -1);
+	return 0;
 }
 
-static void
-expand_selection_right(ltk_entry *entry, ltk_key_event *event) {
+static int
+expand_selection_right(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	expand_selection(entry, 1);
+	return 0;
 }
 
 static void
@@ -466,35 +453,41 @@ delete_text(ltk_entry *entry, size_t start, size_t end) {
 	ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget);
 }
 
-static void
-delete_char_backwards(ltk_entry *entry, ltk_key_event *event) {
+static int
+delete_char_backwards(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_start != entry->sel_end) {
 		delete_text(entry, entry->sel_start, entry->sel_end);
 	} else {
 		size_t new = prev_utf8(entry->text, entry->pos);
 		delete_text(entry, new, entry->pos);
 	}
+	return 0;
 }
 
-static void
-delete_char_forwards(ltk_entry *entry, ltk_key_event *event) {
+static int
+delete_char_forwards(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	if (entry->sel_start != entry->sel_end) {
 		delete_text(entry, entry->sel_start, entry->sel_end);
 	} else {
 		size_t new = next_utf8(entry->text, entry->len, entry->pos);
 		delete_text(entry, entry->pos, new);
 	}
+	return 0;
 }
 
-static void
-select_all(ltk_entry *entry, ltk_key_event *event) {
+static int
+select_all(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	set_selection(entry, 0, entry->len);
 	if (entry->len)
-		selection_to_primary(entry, NULL);
+		selection_to_primary(LTK_CAST_WIDGET(entry), NULL);
 	entry->sel_side = 0;
+	return 0;
 }
 
 static void
@@ -579,9 +572,10 @@ ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len) {
 	insert_text(e, text, len, 0);
 }
 
-static void
-edit_external(ltk_entry *entry, ltk_key_event *event) {
+static int
+edit_external(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
+	ltk_entry *entry = LTK_CAST_ENTRY(self);
 	ltk_config *config = ltk_config_get();
 	/* FIXME: allow arguments to key mappings - this would allow to have different key mappings
 	   for different editors instead of just one command */
@@ -592,8 +586,11 @@ edit_external(ltk_entry *entry, ltk_key_event *event) {
 		/* FIXME: change interface to not require length of cmd */
 		ltk_call_cmd(LTK_CAST_WIDGET(entry), config->general.line_editor, strlen(config->general.line_editor), entry->text, entry->len);
 	}
+	return 0;
 }
 
+/* FIXME: return values of callbacks are currently ignored - could this be used for anything useful? */
+/* -> maybe if multiple bindings are configured for the same key? */
 static int
 ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) {
 	ltk_entry *entry = LTK_CAST_ENTRY(self);
@@ -607,7 +604,7 @@ ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) {
 		    (b.mods == (event->modmask & ~LTK_MOD_SHIFT) &&
 		     ((b.text && event->mapped && !strcmp(b.text, event->mapped)) ||
 		      (b.rawtext && event->text && !strcmp(b.rawtext, event->text))))) {
-			ltk_array_get(keypresses, i).cb.func(entry, event);
+			ltk_array_get(keypresses, i).cb.func(LTK_CAST_WIDGET(entry), event);
 			self->dirty = 1;
 			ltk_window_invalidate_widget_rect(self->window, self);
 			return 1;
@@ -641,7 +638,7 @@ ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
 	}
 	if (event->button == LTK_BUTTONL) {
 		if (event->type == LTK_3BUTTONPRESS_EVENT) {
-			select_all(e, NULL);
+			select_all(LTK_CAST_WIDGET(e), NULL);
 		} else if (event->type == LTK_2BUTTONPRESS_EVENT) {
 			/* FIXME: use proper unicode stuff */
 			/* Note: If pango is used to determine what a word is, maybe at least
@@ -695,7 +692,7 @@ ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
 		   (see behavior in ledit) */
 		wipe_selection(e);
 		e->pos = xy_to_pos(e, event->x, event->y, 1);
-		paste_primary(e, NULL);
+		paste_primary(LTK_CAST_WIDGET(e), NULL);
 	}
 	return 0;
 }
@@ -705,7 +702,7 @@ ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) {
 	ltk_entry *e = LTK_CAST_ENTRY(self);
 	if (event->button == LTK_BUTTONL) {
 		e->selecting = 0;
-		selection_to_primary(e, NULL);
+		selection_to_primary(LTK_CAST_WIDGET(e), NULL);
 	}
 	return 0;
 }
diff --git a/src/ltk/entry.h b/src/ltk/entry.h
@@ -38,15 +38,17 @@ typedef struct {
 	char selecting;
 } ltk_entry;
 
+ltk_entry *ltk_entry_create(ltk_window *window, char *text);
+
+/* FIXME: these should be private to ltk */
+void ltk_entry_cleanup(void);
+void ltk_entry_get_keybinding_parseinfo(
+        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
+        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
+        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
+);
 int ltk_entry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
 int ltk_entry_fill_theme_defaults(ltk_renderdata *data);
 void ltk_entry_uninitialize_theme(ltk_renderdata *data);
 
-/* FIXME: document that pointers inside binding are taken over! */
-int ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
-int ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
-void ltk_entry_cleanup(void);
-
-ltk_entry *ltk_entry_create(ltk_window *window, char *text);
-
 #endif /* LTK_ENTRY_H */
diff --git a/src/ltk/keys.h b/src/ltk/keys.h
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2023-2024 lumidify <nobody@lumidify.org>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef LTK_KEYS_H
-#define LTK_KEYS_H
-
-#include <stddef.h>
-
-#include "util.h"
-
-/* FIXME: replace with proper string type */
-struct ltk_search_cmp_helper {
-	const char *text;
-	size_t len;
-};
-
-/* FIXME: documentation */
-#define GEN_CB_MAP_HELPERS(name, typename, cmp_entry)                             \
-                                                                                  \
-static int name##_sorted = 0;                                                     \
-                                                                                  \
-/*                                                                                \
- * IMPORTANT: The text passed to *_get_entry may not be nul-terminated,           \
- * so ltk_search_cmp_helper has to be used for the bsearch comparison             \
- * helper.                                                                        \
- */                                                                               \
-                                                                                  \
-static int                                                                        \
-name##_search_helper(const void *keyv, const void *entryv) {                      \
-	struct ltk_search_cmp_helper *key = (struct ltk_search_cmp_helper *)keyv; \
-	typename *entry = (typename *)entryv;                                     \
-	int ret = strncmp(key->text, entry->cmp_entry, key->len);                 \
-	if (ret == 0) {                                                           \
-		if (entry->cmp_entry[key->len] == '\0')                           \
-			return 0;                                                 \
-		else                                                              \
-			return -1;                                                \
-	}                                                                         \
-	return ret;                                                               \
-}                                                                                 \
-                                                                                  \
-static int                                                                        \
-name##_sort_helper(const void *entry1v, const void *entry2v) {                    \
-	typename *entry1 = (typename *)entry1v;                                   \
-	typename *entry2 = (typename *)entry2v;                                   \
-	return strcmp(entry1->cmp_entry, entry2->cmp_entry);                      \
-}                                                                                 \
-                                                                                  \
-static typename *                                                                 \
-name##_get_entry(const char *text, size_t len) {                                  \
-	/* just in case */                                                        \
-	if (!name##_sorted) {                                                     \
-		qsort(                                                            \
-		    name, LENGTH(name),                                           \
-		    sizeof(name[0]), &name##_sort_helper);                        \
-		name##_sorted = 1;                                                \
-	}                                                                         \
-	struct ltk_search_cmp_helper tmp = {.len = len, .text = text};            \
-	return bsearch(                                                           \
-	    &tmp, name, LENGTH(name),                                             \
-	    sizeof(name[0]), &name##_search_helper                                \
-	);                                                                        \
-}
-
-#endif /* LTK_KEYS_H */
diff --git a/src/ltk/ltk.c b/src/ltk/ltk.c
@@ -90,8 +90,6 @@ static void ltk_handle_event(ltk_event *event);
 static void ltk_load_theme(const char *path);
 static void ltk_uninitialize_theme(void);
 static int ltk_ini_handler(void *renderdata, const char *widget, const char *prop, const char *value);
-static int handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b);
-static int handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b);
 
 static short running = 1;
 
@@ -100,8 +98,6 @@ typedef struct {
 	int (*ini_handler)(ltk_renderdata *, const char *, const char *);
 	int (*fill_theme_defaults)(ltk_renderdata *);
 	void (*uninitialize_theme)(ltk_renderdata *);
-	int (*register_keypress)(const char *, size_t, ltk_keypress_binding);
-	int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding);
 	void (*cleanup)(void);
 } ltk_widget_funcs;
 
@@ -112,8 +108,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = NULL,
 		.fill_theme_defaults = NULL,
 		.uninitialize_theme = NULL,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -121,8 +115,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_button_ini_handler,
 		.fill_theme_defaults = <k_button_fill_theme_defaults,
 		.uninitialize_theme = <k_button_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -130,8 +122,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_entry_ini_handler,
 		.fill_theme_defaults = <k_entry_fill_theme_defaults,
 		.uninitialize_theme = <k_entry_uninitialize_theme,
-		.register_keypress = <k_entry_register_keypress,
-		.register_keyrelease = <k_entry_register_keyrelease,
 		.cleanup = <k_entry_cleanup,
 	},
 	{
@@ -139,8 +129,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = NULL,
 		.fill_theme_defaults = NULL,
 		.uninitialize_theme = NULL,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -148,8 +136,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_label_ini_handler,
 		.fill_theme_defaults = <k_label_fill_theme_defaults,
 		.uninitialize_theme = <k_label_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -158,8 +144,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = NULL,
 		.fill_theme_defaults = NULL,
 		.uninitialize_theme = NULL,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -167,8 +151,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_menu_ini_handler,
 		.fill_theme_defaults = <k_menu_fill_theme_defaults,
 		.uninitialize_theme = <k_menu_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -176,8 +158,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_menuentry_ini_handler,
 		.fill_theme_defaults = <k_menuentry_fill_theme_defaults,
 		.uninitialize_theme = <k_menuentry_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -185,8 +165,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_submenu_ini_handler,
 		.fill_theme_defaults = <k_submenu_fill_theme_defaults,
 		.uninitialize_theme = <k_submenu_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -194,8 +172,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_submenuentry_ini_handler,
 		.fill_theme_defaults = <k_submenuentry_fill_theme_defaults,
 		.uninitialize_theme = <k_submenuentry_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 		 /*
 		 This "widget" is only needed to have separate styles for regular
@@ -213,18 +189,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_scrollbar_ini_handler,
 		.fill_theme_defaults = <k_scrollbar_fill_theme_defaults,
 		.uninitialize_theme = <k_scrollbar_uninitialize_theme,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
-		.cleanup = NULL,
-	},
-	{
-		/* Handler for general widget key bindings. */
-		.name = "widget",
-		.ini_handler = NULL,
-		.fill_theme_defaults = NULL,
-		.uninitialize_theme = NULL,
-		.register_keypress = NULL,
-		.register_keyrelease = NULL,
 		.cleanup = NULL,
 	},
 	{
@@ -233,8 +197,6 @@ static ltk_widget_funcs widget_funcs[] = {
 		.ini_handler = <k_window_ini_handler,
 		.fill_theme_defaults = <k_window_fill_theme_defaults,
 		.uninitialize_theme = <k_window_uninitialize_theme,
-		.register_keypress = <k_window_register_keypress,
-		.register_keyrelease = <k_window_register_keyrelease,
 		.cleanup = <k_window_cleanup,
 	}
 };
@@ -259,12 +221,12 @@ ltk_init(void) {
 	char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
 	char *theme_path;
 	char *errstr = NULL;
-	if (ltk_config_parsefile(config_path, &handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
+	if (ltk_config_parsefile(config_path, &errstr)) {
 		if (errstr) {
 			ltk_warn("Unable to load config: %s\n", errstr);
 			ltk_free0(errstr);
 		}
-		if (ltk_config_load_default(&handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
+		if (ltk_config_load_default(&errstr)) {
 			/* FIXME: I guess errstr isn't freed here, but whatever */
 			/* FIXME: return error instead of dying */
 			ltk_fatal("Unable to load default config: %s\n", errstr);
@@ -588,30 +550,6 @@ ltk_uninitialize_theme(void) {
 	}
 }
 
-static int
-handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) {
-	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
-		if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
-			if (!widget_funcs[i].register_keypress)
-				return 1;
-			return widget_funcs[i].register_keypress(name, nlen, b);
-		}
-	}
-	return 1;
-}
-
-static int
-handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) {
-	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
-		if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
-			if (!widget_funcs[i].register_keyrelease)
-				return 1;
-			return widget_funcs[i].register_keyrelease(name, nlen, b);
-		}
-	}
-	return 1;
-}
-
 int
 ltk_call_cmd(ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) {
 	/* FIXME: support environment variable $TMPDIR */
diff --git a/src/ltk/sort_search.h b/src/ltk/sort_search.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2024 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LTK_SORT_SEARCH_H
+#define LTK_SORT_SEARCH_H
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+struct ltk_search_cmp_helper {
+	const char *text;
+	size_t len;
+};
+
+/* FIXME: documentation */
+#define GEN_SORT_SEARCH_HELPERS(name, typename, cmp_entry)                        \
+                                                                                  \
+/*                                                                                \
+ * IMPORTANT: The text passed to *_get_entry may not be nul-terminated,           \
+ * so ltk_search_cmp_helper has to be used for the bsearch comparison             \
+ * helper.                                                                        \
+ */                                                                               \
+                                                                                  \
+static int                                                                        \
+name##_search_helper(const void *keyv, const void *entryv) {                      \
+	struct ltk_search_cmp_helper *key = (struct ltk_search_cmp_helper *)keyv; \
+	typename *entry = (typename *)entryv;                                     \
+	int ret = strncmp(key->text, entry->cmp_entry, key->len);                 \
+	if (ret == 0) {                                                           \
+		if (entry->cmp_entry[key->len] == '\0')                           \
+			return 0;                                                 \
+		else                                                              \
+			return -1;                                                \
+	}                                                                         \
+	return ret;                                                               \
+}                                                                                 \
+                                                                                  \
+static int                                                                        \
+name##_sort_helper(const void *entry1v, const void *entry2v) {                    \
+	typename *entry1 = (typename *)entry1v;                                   \
+	typename *entry2 = (typename *)entry2v;                                   \
+	return strcmp(entry1->cmp_entry, entry2->cmp_entry);                      \
+}                                                                                 \
+                                                                                  \
+static void                                                                       \
+name##_sort(typename *arr, size_t arrlen) {                                       \
+	qsort(                                                                    \
+	    arr, arrlen,                                                          \
+	    sizeof(typename), &name##_sort_helper                                 \
+	);                                                                        \
+}                                                                                 \
+                                                                                  \
+static typename *                                                                 \
+name##_get_entry(typename *arr, size_t arrlen, const char *text, size_t len) {    \
+	struct ltk_search_cmp_helper tmp = {.text = text, .len = len};            \
+	return bsearch(                                                           \
+	    &tmp, arr, arrlen,                                                    \
+	    sizeof(typename), &name##_search_helper                               \
+	);                                                                        \
+}
+
+#endif /* LTK_SORT_SEARCH_H */
diff --git a/src/ltk/window.c b/src/ltk/window.c
@@ -21,12 +21,12 @@
 
 #include "ltk.h"
 #include "util.h"
-#include "keys.h"
 #include "array.h"
 #include "theme.h"
 #include "widget.h"
 #include "window.h"
 #include "memory.h"
+#include "config.h"
 #include "eventdefs.h"
 
 #define MAX_WINDOW_FONT_SIZE 20000
@@ -67,24 +67,19 @@ static struct ltk_widget_vtable vtable = {
 	.invalid_signal = LTK_WINDOW_SIGNAL_INVALID,
 };
 
-static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled);
-
-struct key_cb {
-	char *func_name;
-	int (*callback)(ltk_window *, ltk_key_event *, int handled);
-};
-
-static struct key_cb cb_map[] = {
+static int cb_focus_active(ltk_widget *self, ltk_key_event *event);
+static int cb_unfocus_active(ltk_widget *self, ltk_key_event *event);
+static int cb_move_prev(ltk_widget *self, ltk_key_event *event);
+static int cb_move_next(ltk_widget *self, ltk_key_event *event);
+static int cb_move_left(ltk_widget *self, ltk_key_event *event);
+static int cb_move_right(ltk_widget *self, ltk_key_event *event);
+static int cb_move_up(ltk_widget *self, ltk_key_event *event);
+static int cb_move_down(ltk_widget *self, ltk_key_event *event);
+static int cb_set_pressed(ltk_widget *self, ltk_key_event *event);
+static int cb_unset_pressed(ltk_widget *self, ltk_key_event *event);
+static int cb_remove_popups(ltk_widget *self, ltk_key_event *event);
+
+static ltk_keybinding_cb cb_map[] = {
 	{"focus-active", &cb_focus_active},
 	{"move-down", &cb_move_down},
 	{"move-left", &cb_move_left},
@@ -98,25 +93,26 @@ static struct key_cb cb_map[] = {
 	{"unset-pressed", &cb_unset_pressed},
 };
 
-struct keypress_cfg {
-	ltk_keypress_binding b;
-	struct key_cb cb;
-};
-
-struct keyrelease_cfg {
-	ltk_keyrelease_binding b;
-	struct key_cb cb;
-};
-
-LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
-
 static ltk_array(keypress) *keypresses = NULL;
 static ltk_array(keyrelease) *keyreleases = NULL;
 
-GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name)
+void
+ltk_window_get_keybinding_parseinfo(
+        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
+        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
+        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
+) {
+	*press_cbs_ret = cb_map;
+	*press_len_ret = LENGTH(cb_map);
+	*release_cbs_ret = cb_map;
+	*release_len_ret = LENGTH(cb_map);
+	if (!keypresses)
+		keypresses = ltk_array_create(keypress, 1);
+	if (!keyreleases)
+		keyreleases = ltk_array_create(keyrelease, 1);
+	*presses_ret = keypresses;
+	*releases_ret = keyreleases;
+}
 
 /* needed for passing keyboard events down the hierarchy */
 static ltk_widget **widget_stack = NULL;
@@ -153,44 +149,13 @@ ltk_window_get_theme(void) {
 	return &theme;
 }
 
-/* FIXME: most of this is duplicated code */
-
-int
-ltk_window_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
-	if (!keypresses)
-		keypresses = ltk_array_create(keypress, 1);
-	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
-	if (!cb)
-		return 1;
-	struct keypress_cfg cfg = {b, *cb};
-	ltk_array_append(keypress, keypresses, cfg);
-	return 0;
-}
-
-int
-ltk_window_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
-	if (!keyreleases)
-		keyreleases = ltk_array_create(keyrelease, 1);
-	struct key_cb *cb = cb_map_get_entry(func_name, func_len);
-	if (!cb)
-		return 1;
-	struct keyrelease_cfg cfg = {b, *cb};
-	ltk_array_append(keyrelease, keyreleases, cfg);
-	return 0;
-}
-
-static void
-destroy_keypress_cfg(struct keypress_cfg cfg) {
-	ltk_keypress_binding_destroy(cfg.b);
-}
-
 void
 ltk_window_cleanup(void) {
-	ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
-	ltk_array_destroy(keyrelease, keyreleases);
-	free(widget_stack);
+	ltk_keypress_bindings_destroy(keypresses);
+	ltk_keyrelease_bindings_destroy(keyreleases);
 	keypresses = NULL;
 	keyreleases = NULL;
+	ltk_free(widget_stack);
 	widget_stack = NULL;
 }
 
@@ -244,13 +209,13 @@ ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event) {
 			continue;
 		} else if (b->text) {
 			if (event->mapped && !strcmp(b->text, event->mapped))
-				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+				handled |= ltk_array_get(keypresses, i).cb.func(LTK_CAST_WIDGET(window), event);
 		} else if (b->rawtext) {
 			if (event->text && !strcmp(b->text, event->text))
-				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+				handled |= ltk_array_get(keypresses, i).cb.func(LTK_CAST_WIDGET(window), event);
 		} else if (b->sym != LTK_KEY_NONE) {
 			if (event->sym == b->sym)
-				handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+				handled |= ltk_array_get(keypresses, i).cb.func(LTK_CAST_WIDGET(window), event);
 		}
 	}
 	return 1;
@@ -280,7 +245,7 @@ ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event) {
 		if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
 			continue;
 		} else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
-			handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled);
+			handled |= ltk_array_get(keyreleases, i).cb.func(LTK_CAST_WIDGET(window), event);
 		}
 	}
 	return 1;
@@ -1226,9 +1191,9 @@ gen_widget_stack(ltk_widget *bottom) {
    widget type, but what if the program using ltk wants to catch keyboard events even if the widget
    doesn't do that by default? */
 static int
-cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
+cb_focus_active(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
 		/* FIXME: maybe also set widgets above in hierarchy? */
 		ltk_widget_state old_state = window->active_widget->state;
@@ -1240,9 +1205,9 @@ cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
 }
 
 static int
-cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
+cb_unfocus_active(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
 		ltk_widget_state old_state = window->active_widget->state;
 		window->active_widget->state &= ~LTK_FOCUSED;
@@ -1253,51 +1218,51 @@ cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
 }
 
 static int
-cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_prev(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return prev_child(window);
 }
 
 static int
-cb_move_next(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_next(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return next_child(window);
 }
 
 static int
-cb_move_left(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_left(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return left_top_child(window, 1);
 }
 
 static int
-cb_move_right(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_right(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return right_bottom_child(window, 1);
 }
 
 static int
-cb_move_up(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_up(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return left_top_child(window, 0);
 }
 
 static int
-cb_move_down(ltk_window *window, ltk_key_event *event, int handled) {
+cb_move_down(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	return right_bottom_child(window, 0);
 }
 
 static int
-cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
+cb_set_pressed(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
 		/* FIXME: only set pressed if needs keyboard? */
 		ltk_window_set_pressed_widget(window, window->active_widget, 0);
@@ -1307,9 +1272,9 @@ cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
 }
 
 static int
-cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
+cb_unset_pressed(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	if (window->pressed_widget) {
 		ltk_window_set_pressed_widget(window, NULL, 1);
 		return 1;
@@ -1318,9 +1283,9 @@ cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
 }
 
 static int
-cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) {
+cb_remove_popups(ltk_widget *self, ltk_key_event *event) {
 	(void)event;
-	(void)handled;
+	ltk_window *window = LTK_CAST_WINDOW(self);
 	if (window->popups_num > 0) {
 		ltk_window_unregister_all_popups(window);
 		return 1;
diff --git a/src/ltk/window.h b/src/ltk/window.h
@@ -61,11 +61,6 @@ typedef struct ltk_window {
 	char popups_locked;
 } ltk_window;
 
-int ltk_window_fill_theme_defaults(ltk_renderdata *data);
-int ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
-void ltk_window_uninitialize_theme(ltk_renderdata *data);
-ltk_window_theme *ltk_window_get_theme(void);
-
 /* FIXME: should be private to ltk */
 ltk_window *ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h);
 void ltk_window_destroy_intern(ltk_window *window);
@@ -90,9 +85,15 @@ void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
 void ltk_window_unregister_all_popups(ltk_window *window);
 
 /* FIXME: these should be private to ltk */
-/* FIXME: document that pointers inside binding are taken over! */
-int ltk_window_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
-int ltk_window_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
 void ltk_window_cleanup(void);
+void ltk_window_get_keybinding_parseinfo(
+	ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
+	ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
+	ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
+);
+int ltk_window_fill_theme_defaults(ltk_renderdata *data);
+int ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+void ltk_window_uninitialize_theme(ltk_renderdata *data);
+ltk_window_theme *ltk_window_get_theme(void);
 
 #endif  /* LTK_WINDOW_H */