URI: 
       Restructure configuration parsing for keybindings - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 210aa3d51d8dcc1e36c73d3404e032937271e0d6
   DIR parent 71f3528512ae0666e68994bd70dccb933bed55f8
  HTML 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(-)
       ---
   DIR 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 \
   DIR 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", &ltk_entry_get_keybinding_parseinfo},
       +        {"window", &ltk_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;
   DIR 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 */
   DIR 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;
        }
   DIR 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 */
   DIR 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 */
   DIR 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 = &ltk_button_ini_handler,
                        .fill_theme_defaults = &ltk_button_fill_theme_defaults,
                        .uninitialize_theme = &ltk_button_uninitialize_theme,
       -                .register_keypress = NULL,
       -                .register_keyrelease = NULL,
                        .cleanup = NULL,
                },
                {
       @@ -130,8 +122,6 @@ static ltk_widget_funcs widget_funcs[] = {
                        .ini_handler = &ltk_entry_ini_handler,
                        .fill_theme_defaults = &ltk_entry_fill_theme_defaults,
                        .uninitialize_theme = &ltk_entry_uninitialize_theme,
       -                .register_keypress = &ltk_entry_register_keypress,
       -                .register_keyrelease = &ltk_entry_register_keyrelease,
                        .cleanup = &ltk_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 = &ltk_label_ini_handler,
                        .fill_theme_defaults = &ltk_label_fill_theme_defaults,
                        .uninitialize_theme = &ltk_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 = &ltk_menu_ini_handler,
                        .fill_theme_defaults = &ltk_menu_fill_theme_defaults,
                        .uninitialize_theme = &ltk_menu_uninitialize_theme,
       -                .register_keypress = NULL,
       -                .register_keyrelease = NULL,
                        .cleanup = NULL,
                },
                {
       @@ -176,8 +158,6 @@ static ltk_widget_funcs widget_funcs[] = {
                        .ini_handler = &ltk_menuentry_ini_handler,
                        .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults,
                        .uninitialize_theme = &ltk_menuentry_uninitialize_theme,
       -                .register_keypress = NULL,
       -                .register_keyrelease = NULL,
                        .cleanup = NULL,
                },
                {
       @@ -185,8 +165,6 @@ static ltk_widget_funcs widget_funcs[] = {
                        .ini_handler = &ltk_submenu_ini_handler,
                        .fill_theme_defaults = &ltk_submenu_fill_theme_defaults,
                        .uninitialize_theme = &ltk_submenu_uninitialize_theme,
       -                .register_keypress = NULL,
       -                .register_keyrelease = NULL,
                        .cleanup = NULL,
                },
                {
       @@ -194,8 +172,6 @@ static ltk_widget_funcs widget_funcs[] = {
                        .ini_handler = &ltk_submenuentry_ini_handler,
                        .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults,
                        .uninitialize_theme = &ltk_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 = &ltk_scrollbar_ini_handler,
                        .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults,
                        .uninitialize_theme = &ltk_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 = &ltk_window_ini_handler,
                        .fill_theme_defaults = &ltk_window_fill_theme_defaults,
                        .uninitialize_theme = &ltk_window_uninitialize_theme,
       -                .register_keypress = &ltk_window_register_keypress,
       -                .register_keyrelease = &ltk_window_register_keyrelease,
                        .cleanup = &ltk_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 */
   DIR 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 */
   DIR 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;
   DIR 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 */