// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <wlr/types/wlr_keyboard.h>
#include <xkbcommon/xkbcommon.h>

#include <kywc/binding.h>
#include <kywc/log.h>

#include "input_p.h"
#include "server.h"
#include "util/macros.h"
#include "util/string.h"

struct key_binding {
    struct wl_list link;

    uint32_t modifiers;
    uint32_t keysym;
    bool no_repeat;
    bool bypass_grab;

    char *keybind;
    char *desc;

    void (*action)(struct key_binding *binding, void *data);
    void *data;
};

struct keysyms_binding {
    struct wl_list bindings; /* key binding */
};

struct gesture_binding {
    struct wl_list link;

    enum gesture_type type;
    enum gesture_stage stage;
    uint8_t fingers;
    uint32_t devices;
    uint32_t directions;
    uint32_t follow_direction;
    uint32_t edges;
    double follow_threshold;
    char *desc;

    void (*action)(struct gesture_binding *binding, void *data, double dx, double dy);
    void *data;
};

static struct bindings {
    struct keysyms_binding keysyms_binding[KEY_BINDING_TYPE_NUM];
    struct wl_list gesture_bindings;
    struct wl_listener server_destroy;

    size_t keysym_bindings_block;
    size_t type_nlocks[KEY_BINDING_TYPE_NUM];
    uint32_t keysyms_binding_masks; /* binding mask */
} *bindings = NULL;

const struct key_binding_type2string {
    enum key_binding_type type;
    const char *name;
} key_binding_table[] = {
    { KEY_BINDING_TYPE_CUSTOM_DEF, "WLCOM_CUSTOM_DEF" },
    { KEY_BINDING_TYPE_WIN_MENU, "WLCOM_WIN_MENU" },
    { KEY_BINDING_TYPE_SWITCH_WORKSPACE, "WLCOM_SWITCH_WORKSPACE" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_MINIMIZE, "WLCOM_WINDOW_ACTION_MINIMIZE" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_MAXIMIZE, "WLCOM_WINDOW_ACTION_MAXIMIZE" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_CLOSE, "WLCOM_WINDOW_ACTION_CLOSE" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_MENU, "WLCOM_WINDOW_ACTION_MENU" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_TILED, "WLCOM_WINDOW_ACTION_TILED" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_OUTPUT, "WLCOM_WINDOW_ACTION_OUTPUT" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_SEND, "WLCOM_WINDOW_ACTION_SEND" },
    { KEY_BINDING_TYPE_WINDOW_ACTION_CAPTURE, "WLCOM_WINDOW_ACTION_CAPTURE" },
    { KEY_BINDING_TYPE_MAXIMIZED_VIEWS, "WLCOM_MAXIMIZED_VIEWS" },
    { KEY_BINDING_TYPE_TOGGLE_SHOW_DESKTOP, "WLCOM_TOGGLE_SHOW_DESKTOP" },
    { KEY_BINDING_TYPE_SHOW_DESKTOP, "WLCOM_SHOW_DESKTOP" },
    { KEY_BINDING_TYPE_RESTORE_DESKTOP, "WLCOM_RESTORE_DESKTOP" },
    { KEY_BINDING_TYPE_TOGGLE_SHOW_VIEWS, "WLCOM_TOGGLE_SHOW_VIEWS" },
    { KEY_BINDING_TYPE_TOGGLE_SHOW_WINDOWS, "WLCOM_TOGGLE_SHOW_WINDOWS" },
    { KEY_BINDING_TYPE_COLOR_FILTER, "WLCOM_COLOR_FILTER" },
    { KEY_BINDING_TYPE_ZOOM, "WLCOM_ZOOM" },
    { KEY_BINDING_TYPE_NUM, "WLCOM_ALL" },
};

struct key_binding *kywc_key_binding_create(const char *keybind, const char *desc)
{
    struct key_binding *binding = calloc(1, sizeof(*binding));
    if (!binding) {
        return NULL;
    }

    /* check no_repeat first */
    size_t length = 0;
    char **bind_str = string_split(keybind, ":", &length);
    binding->no_repeat = length == 2 && strcmp(bind_str[1], "no") == 0;

    size_t len = 0;
    char **split_str = string_split(length == 2 ? bind_str[0] : keybind, "+", &len);
    string_free_split(bind_str);

    for (size_t i = 0; i < len; i++) {
        uint32_t mod = keyboard_get_modifier_mask_by_name(split_str[i]);
        if (mod) {
            binding->modifiers |= mod;
            continue;
        }

        /* whatever keep the lower keysym */
        xkb_keysym_t sym = xkb_keysym_from_name(split_str[i], XKB_KEYSYM_CASE_INSENSITIVE);
        binding->keysym = xkb_keysym_to_lower(sym);
    }

    string_free_split(split_str);

    if (desc) {
        binding->desc = strdup(desc);
    }
    binding->keybind = strdup(keybind);
    wl_list_init(&binding->link);
    kywc_log(KYWC_DEBUG, "Keybind %s: %s", keybind, desc);

    return binding;
}

struct key_binding *kywc_key_binding_create_by_symbol(unsigned int keysym, unsigned int modifiers,
                                                      bool no_repeat, bool bypass_grab,
                                                      const char *desc)
{
    struct key_binding *binding = calloc(1, sizeof(*binding));
    if (!binding) {
        return NULL;
    }

    binding->modifiers = modifiers;
    binding->keysym = keysym;
    binding->no_repeat = no_repeat;
    binding->bypass_grab = bypass_grab;

    if (desc) {
        binding->desc = strdup(desc);
    }

    char name[64];
    if (xkb_keysym_get_name(keysym, name, sizeof(name)) > 0) {
        const char *mods = keyboard_get_modifier_names(modifiers, '+');
        binding->keybind = string_create("%s+%s", mods, name);
    }

    wl_list_init(&binding->link);

    return binding;
}

void kywc_key_binding_destroy(struct key_binding *binding)
{
    wl_list_remove(&binding->link);

    free(binding->keybind);
    free(binding->desc);
    free(binding);
}

static bool key_binding_is_valid(struct key_binding *binding, uint32_t keysym, uint32_t modifiers)
{
    for (int i = 0; i < KEY_BINDING_TYPE_NUM; ++i) {
        struct key_binding *bind;
        struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i];
        wl_list_for_each(bind, &keysyms_binding->bindings, link) {
            /* skip itself */
            if (bind == binding) {
                continue;
            }

            if (!(modifiers ^ bind->modifiers) && keysym == bind->keysym) {
                kywc_log(KYWC_WARN, "Keybind %s(%s) is already registered by %s(%s)",
                         binding->keybind, binding->desc, bind->keybind, bind->desc);
                return false;
            }
        }
    }

    return true;
}

bool kywc_key_binding_register(struct key_binding *binding, enum key_binding_type type,
                               void (*action)(struct key_binding *binding, void *data), void *data)
{
    if (kywc_key_binding_is_registered(binding)) {
        return true;
    }

    if (!key_binding_is_valid(binding, binding->keysym, binding->modifiers)) {
        return false;
    }

    struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[type];
    wl_list_insert(&keysyms_binding->bindings, &binding->link);
    /* Make sure the type is unblocking */
    if (!bindings->type_nlocks[type]) {
        bindings->keysyms_binding_masks |= 1 << type;
    }

    if (action) {
        binding->action = action;
    }
    if (data) {
        binding->data = data;
    }

    return true;
}

bool kywc_key_binding_update(struct key_binding *binding, unsigned int keysym,
                             unsigned int modifiers, const char *desc)
{
    if (kywc_key_binding_is_registered(binding) &&
        !key_binding_is_valid(binding, keysym, modifiers)) {
        return false;
    }

    binding->keysym = keysym;
    binding->modifiers = modifiers;

    if (desc) {
        free(binding->desc);
        binding->desc = strdup(desc);
    }

    return true;
}

void kywc_key_binding_unregister(struct key_binding *binding)
{
    wl_list_remove(&binding->link);
    wl_list_init(&binding->link);
}

bool kywc_key_binding_is_registered(struct key_binding *binding)
{
    return !wl_list_empty(&binding->link);
}

static void handle_server_destroy(struct wl_listener *listener, void *data)
{
    wl_list_remove(&bindings->server_destroy.link);

    for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) {
        struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i];
        struct key_binding *key_binding, *key_binding_tmp;
        wl_list_for_each_safe(key_binding, key_binding_tmp, &keysyms_binding->bindings, link) {
            kywc_key_binding_destroy(key_binding);
        }
    }

    struct gesture_binding *gesture_binding, *gesture_binding_tmp;
    wl_list_for_each_safe(gesture_binding, gesture_binding_tmp, &bindings->gesture_bindings, link) {
        kywc_gesture_binding_destroy(gesture_binding);
    }

    free(bindings);
    bindings = NULL;
}

bool bindings_create(struct input_manager *input_manager)
{
    bindings = calloc(1, sizeof(*bindings));
    if (!bindings) {
        return false;
    }

    for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) {
        struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i];
        wl_list_init(&keysyms_binding->bindings);
    }
    wl_list_init(&bindings->gesture_bindings);

    bindings->server_destroy.notify = handle_server_destroy;
    server_add_destroy_listener(input_manager->server, &bindings->server_destroy);
    return true;
}

static bool match_key_binding(struct keyboard_state *keyboard_state, struct key_binding *binding)
{
    for (size_t j = 0; j < keyboard_state->npressed; j++) {
        uint32_t keysym = keyboard_state->pressed_keysyms[j];
        if (binding->keysym == xkb_keysym_to_lower(keysym)) {
            return true;
        }
    }

    return false;
}

struct key_binding *bindings_get_key_binding(struct keyboard_state *keyboard_state)
{
    if (bindings->keysym_bindings_block) {
        return NULL;
    }

    for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) {
        if (!(bindings->keysyms_binding_masks & (1 << i))) {
            continue;
        }

        struct key_binding *binding;
        struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i];
        wl_list_for_each(binding, &keysyms_binding->bindings, link) {
            if ((keyboard_state->only_one_modifier && binding->keysym) ||
                (!keyboard_state->only_one_modifier && !binding->keysym)) {
                continue;
            }

            if (keyboard_state->last_modifiers ^ binding->modifiers) {
                continue;
            }

            if (keyboard_state->npressed < 1 && binding->keysym) {
                continue;
            }

            if (!binding->keysym || match_key_binding(keyboard_state, binding)) {
                return binding;
            }
        }
    }

    return NULL;
}

bool bindings_get_key_binding_bypass_grab(struct key_binding *binding)
{
    if (!binding) {
        return false;
    }

    return binding->bypass_grab;
}

bool bindings_get_key_binding_no_repeat(struct key_binding *binding)
{
    if (!binding) {
        return false;
    }

    return binding->no_repeat;
}

bool bindings_handle_key_binding(struct key_binding *binding, bool *repeat)
{
    if (!binding) {
        *repeat = false;
        return false;
    }

    if (binding->action) {
        binding->action(binding, binding->data);
    }

    *repeat = !binding->no_repeat;

    return true;
}

void kywc_gesture_binding_destroy(struct gesture_binding *binding)
{
    wl_list_remove(&binding->link);
    free(binding->desc);
    free(binding);
}

static bool gesture_binding_is_valid(struct gesture_binding *binding, enum gesture_type type,
                                     enum gesture_stage stage, uint32_t devices,
                                     uint32_t directions, uint32_t follow_direction,
                                     uint8_t fingers, uint8_t edges)
{
    struct gesture_binding *bind;
    wl_list_for_each(bind, &bindings->gesture_bindings, link) {
        /* skip itself */
        if (bind == binding) {
            continue;
        }
        if (bind->type == type && (bind->devices & devices) && (bind->stage == stage) &&
            (GESTURE_DIRECTION_NONE == bind->directions || (bind->directions & directions)) &&
            (GESTURE_EDGE_NONE == bind->edges || (edges & bind->edges)) &&
            bind->fingers == fingers && bind->follow_direction == follow_direction) {
            return false;
        }
    }

    return true;
}

static uint32_t gesture_string_parse_directions(const char *str)
{
    uint32_t directions = GESTURE_DIRECTION_NONE;

    size_t len = 0;
    char **split_str = string_split(str, "+", &len);

    for (size_t i = 0; i < len; i++) {
        if (strcmp(split_str[i], "none") == 0) {
            directions |= GESTURE_DIRECTION_NONE;
        } else if (strcmp(split_str[i], "up") == 0) {
            directions |= GESTURE_DIRECTION_UP;
        } else if (strcmp(split_str[i], "down") == 0) {
            directions |= GESTURE_DIRECTION_DOWN;
        } else if (strcmp(split_str[i], "left") == 0) {
            directions |= GESTURE_DIRECTION_LEFT;
        } else if (strcmp(split_str[i], "right") == 0) {
            directions |= GESTURE_DIRECTION_RIGHT;
        } else if (strcmp(split_str[i], "inward") == 0) {
            directions |= GESTURE_DIRECTION_INWARD;
        } else if (strcmp(split_str[i], "outward") == 0) {
            directions |= GESTURE_DIRECTION_OUTWARD;
        } else if (strcmp(split_str[i], "clockwise") == 0) {
            directions |= GESTURE_DIRECTION_CLOCKWISE;
        } else if (strcmp(split_str[i], "counterclockwise") == 0) {
            directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
        } else {
            kywc_log(KYWC_WARN, "Expected directions, got %s", str);
        }
    }

    string_free_split(split_str);

    return directions;
}

static uint32_t gesture_string_parse_edges(const char *str)
{
    uint32_t edges = GESTURE_EDGE_NONE;

    size_t len = 0;
    char **split_str = string_split(str, "+", &len);

    for (size_t i = 0; i < len; i++) {
        if (strcmp(split_str[i], "none") == 0) {
            edges |= GESTURE_EDGE_NONE;
        } else if (strcmp(split_str[i], "top") == 0) {
            edges |= GESTURE_EDGE_TOP;
        } else if (strcmp(split_str[i], "bottom") == 0) {
            edges |= GESTURE_EDGE_BOTTOM;
        } else if (strcmp(split_str[i], "left") == 0) {
            edges |= GESTURE_EDGE_LEFT;
        } else if (strcmp(split_str[i], "right") == 0) {
            edges |= GESTURE_EDGE_RIGHT;
        } else {
            kywc_log(KYWC_WARN, "Expected edges, got %s", str);
        }
    }

    string_free_split(split_str);

    return edges;
}

struct gesture_binding *kywc_gesture_binding_create_by_string(const char *gestures,
                                                              const char *desc)
{
    enum gesture_type type;
    uint8_t fingers;
    uint32_t devices;
    uint32_t directions;
    uint32_t edges = GESTURE_EDGE_NONE;
    enum gesture_stage stage = GESTURE_STAGE_TRIGGER;
    double follow_threshold = 0.0;
    uint32_t follow_direction = GESTURE_DIRECTION_NONE;

    size_t len = 0;
    char **split_str = string_split(gestures, ":", &len);
    if (len != 4 && len != 5 && len != 6 && len != 7 && len != 8) {
        kywc_log(KYWC_ERROR,
                 "Expected <gesture>:<device>:<fingers>:<directions>"
                 "[:edges][:stage][:follow_threshold][:follow_direction],"
                 " got %s",
                 gestures);
        goto err;
    }

    // type
    if (strcmp(split_str[0], "tap") == 0) {
#if HAVE_LIBINPUT_RAW_TAP
        type = GESTURE_TYPE_TAP;
#else
        type = GESTURE_TYPE_HOLD;
#endif
    } else if (strcmp(split_str[0], "hold") == 0) {
        type = GESTURE_TYPE_HOLD;
    } else if (strcmp(split_str[0], "pinch") == 0) {
        type = GESTURE_TYPE_PINCH;
    } else if (strcmp(split_str[0], "swipe") == 0) {
        type = GESTURE_TYPE_SWIPE;
    } else {
        kywc_log(KYWC_ERROR, "Expected hold|pinch|swipe, got %s", gestures);
        goto err;
    }

    // device
    if (strcmp(split_str[1], "any") == 0) {
        devices = GESTURE_DEVICE_TOUCHPAD | GESTURE_DEVICE_TOUCHSCREEN;
    } else if (strcmp(split_str[1], "touch") == 0) {
        devices = GESTURE_DEVICE_TOUCHSCREEN;
    } else if (strcmp(split_str[1], "touchpad") == 0) {
        devices = GESTURE_DEVICE_TOUCHPAD;
    } else {
        kywc_log(KYWC_ERROR, "Expected any|touch|touchpad, got %s", gestures);
        goto err;
    }

    /* fingers: 1 - 9 */
    /* directions: up down left right inward outward clockwise counterclockwise */
    if ('1' <= split_str[2][0] && split_str[2][0] <= '9') {
        fingers = atoi(split_str[2]);
        directions = gesture_string_parse_directions(split_str[3]);
    } else {
        kywc_log(KYWC_ERROR, "Expected 1 - 9, got %s", gestures);
        goto err;
    }

    /* edge: top bottom left right */
    /* stage: before trigger after stop */
    switch (len) {
    case 8:
        follow_direction = gesture_string_parse_directions(split_str[7]);
        // fallthrough
    case 7:
        follow_threshold = atof(split_str[6]);
        // fallthrough
    case 6:
        if (strcmp(split_str[5], "before") == 0) {
            stage = GESTURE_STAGE_BEFORE;
        } else if (strcmp(split_str[5], "trigger") == 0) {
            stage = GESTURE_STAGE_TRIGGER;
        } else if (strcmp(split_str[5], "after") == 0) {
            stage = GESTURE_STAGE_AFTER;
        } else if (strcmp(split_str[5], "stop") == 0) {
            stage = GESTURE_STAGE_STOP;
        } else {
            kywc_log(KYWC_ERROR, "Expected before|trigger|after|stop, got %s", gestures);
            goto err;
        }
        // fallthrough
    case 5:
        edges = gesture_string_parse_edges(split_str[4]);
        break;
    }

    string_free_split(split_str);

    kywc_log(KYWC_DEBUG, "Gesture binding: %s", gestures);
    return kywc_gesture_binding_create(type, stage, devices, directions, edges, fingers,
                                       follow_direction, follow_threshold, desc);

err:
    string_free_split(split_str);
    return NULL;
}

static bool gesture_checked(enum gesture_type type, uint32_t devices, uint32_t directions,
                            uint32_t edges, uint8_t fingers)
{
    /* gesture type、devices and fingers cannot be 0 */
    if (type == GESTURE_TYPE_NONE || devices == GESTURE_DEVICE_NONE || fingers == 0) {
        return false;
    }

    /* pinch gesture fingers require no less than 2 */
    if (type == GESTURE_TYPE_PINCH && fingers < 2) {
        return false;
    }

    /* pinch or touchpad all gestures edges need to be 0 */
    if ((type == GESTURE_TYPE_PINCH || devices & GESTURE_DEVICE_TOUCHPAD) &&
        edges != GESTURE_EDGE_NONE) {
        return false;
    }

    /* the number of fingers for touchpad gestures need to be greater than 2 */
    if (type == GESTURE_TYPE_TAP && devices & GESTURE_DEVICE_TOUCHPAD && fingers < 3) {
        return false;
    }

    /* the number of fingers for touchpad swipe gestures need to be greater than 1 */
    if (type == GESTURE_TYPE_SWIPE && devices & GESTURE_DEVICE_TOUCHPAD && fingers == 1) {
        return false;
    }

    /* the edge of the swipe gesture for one finger of the touchscreen cannot be 0 */
    if (type == GESTURE_TYPE_SWIPE && edges == GESTURE_EDGE_NONE && fingers == 1) {
        return false;
    }

    /* swipe gesture from edge, the start edges and directions need to be set properly */
    if (edges != GESTURE_EDGE_NONE &&
        ((directions & GESTURE_DIRECTION_LEFT && !(edges & GESTURE_EDGE_RIGHT)) ||
         (directions & GESTURE_DIRECTION_RIGHT && !(edges & GESTURE_EDGE_LEFT)) ||
         (directions & GESTURE_DIRECTION_UP && !(edges & GESTURE_EDGE_BOTTOM)) ||
         (directions & GESTURE_DIRECTION_DOWN && !(edges & GESTURE_EDGE_TOP)))) {
        return false;
    }

    return true;
}

struct gesture_binding *kywc_gesture_binding_create(enum gesture_type type,
                                                    enum gesture_stage stage, uint32_t devices,
                                                    uint32_t directions, uint32_t edges,
                                                    uint8_t fingers, uint32_t follow_direction,
                                                    double follow_threshold, const char *desc)
{
    if (!gesture_checked(type, devices, directions, edges, fingers)) {
        kywc_log(KYWC_ERROR, "Gesture checks are illega");
        return NULL;
    }

    struct gesture_binding *binding = calloc(1, sizeof(*binding));
    if (!binding) {
        return NULL;
    }

    binding->type = type;
    binding->stage = stage;
    binding->devices = devices;
    binding->directions = directions;
    binding->fingers = fingers;
    binding->edges = edges;
    binding->follow_threshold = follow_threshold > 0.0 ? follow_threshold : 0.0;
    binding->follow_direction = follow_direction;
    if (desc) {
        binding->desc = strdup(desc);
    }
    wl_list_init(&binding->link);

    return binding;
}

bool kywc_gesture_binding_register(struct gesture_binding *binding,
                                   void (*action)(struct gesture_binding *binding, void *data,
                                                  double dx, double dy),
                                   void *data)
{
    if (!wl_list_empty(&binding->link)) {
        return true;
    }
    if (!gesture_binding_is_valid(binding, binding->type, binding->stage, binding->devices,
                                  binding->directions, binding->follow_direction, binding->fingers,
                                  binding->edges)) {
        return false;
    }

    wl_list_insert(&bindings->gesture_bindings, &binding->link);
    binding->action = action;
    binding->data = data;

    return true;
}

bool bindings_handle_gesture_binding(struct gesture_state *gesture_state)
{
    struct gesture_binding *binding;
    wl_list_for_each(binding, &bindings->gesture_bindings, link) {
        if (gesture_state->type != binding->type) {
            continue;
        }
        if (gesture_state->fingers != binding->fingers) {
            continue;
        }
        if (gesture_state->stage != binding->stage) {
            continue;
        }
        /* do not trigger follow if interval < follow_threshold */
        if (gesture_state->stage == GESTURE_STAGE_BEFORE ||
            gesture_state->stage == GESTURE_STAGE_AFTER) {
            if ((gesture_state->follow_direction != binding->follow_direction) ||
                (gesture_state->follow_dx > -binding->follow_threshold &&
                 gesture_state->follow_dx < binding->follow_threshold &&
                 gesture_state->follow_dy > -binding->follow_threshold &&
                 gesture_state->follow_dy < binding->follow_threshold)) {
                continue;
            }
        }

        if ((gesture_state->device & binding->devices) &&
            (binding->directions == GESTURE_DIRECTION_NONE ||
             binding->stage == GESTURE_STAGE_BEFORE ||
             gesture_state->directions & binding->directions) &&
            (binding->edges == GESTURE_EDGE_NONE || gesture_state->edge & binding->edges)) {
            kywc_log(KYWC_DEBUG, "Start gesture binding: %s", binding->desc);
            if (binding->action) {
                binding->action(binding, binding->data, gesture_state->dx, gesture_state->dy);
            }
            return true;
        }
    }
    return false;
}

void kywc_key_binding_for_each(binding_iterator_func_t iterator)
{
    for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) {
        struct key_binding *binding;
        struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i];
        wl_list_for_each(binding, &keysyms_binding->bindings, link) {
            if (iterator(binding, binding->keybind, binding->desc, binding->modifiers,
                         binding->keysym)) {
                return;
            }
        }
    }
}

void kywc_key_binding_block_all(bool block)
{
    if (block) {
        bindings->keysym_bindings_block++;
    } else {
        assert(bindings->keysym_bindings_block > 0);
        bindings->keysym_bindings_block--;
    }
}

void kywc_key_binding_block_type(enum key_binding_type type, bool block)
{
    if (block) {
        bindings->keysyms_binding_masks &= ~(1 << type);
        bindings->type_nlocks[type]++;
    } else {
        assert(bindings->type_nlocks[type] > 0);
        bindings->type_nlocks[type]--;
        if (!bindings->type_nlocks[type]) {
            bindings->keysyms_binding_masks |= (1 << type);
        }
    }
}

enum key_binding_type kywc_key_binding_type_by_name(const char *name, bool *found)
{
    for (size_t i = 0; i < ARRAY_SIZE(key_binding_table); ++i) {
        if (strcmp(name, key_binding_table[i].name)) {
            continue;
        }

        if (found) {
            *found = true;
        }
        return key_binding_table[i].type;
    }

    if (found) {
        *found = false;
    }
    return KEY_BINDING_TYPE_CUSTOM_DEF;
}
