diff --git a/config.c b/config.c index f9ddf5e4..10b899e0 100644 --- a/config.c +++ b/config.c @@ -23,10 +23,10 @@ #include "char32.h" #include "debug.h" #include "input.h" +#include "key-binding.h" #include "macros.h" #include "tokenize.h" #include "util.h" -#include "wayland.h" #include "xmalloc.h" #include "xsnprintf.h" @@ -3307,3 +3307,19 @@ check_if_font_is_monospaced(const char *pattern, fcft_destroy(f); return is_monospaced; } + +xkb_mod_mask_t +conf_modifiers_to_mask(const struct seat *seat, + const struct config_key_modifiers *modifiers) +{ + xkb_mod_mask_t mods = 0; + if (seat->kbd.mod_shift != XKB_MOD_INVALID) + mods |= modifiers->shift << seat->kbd.mod_shift; + if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) + mods |= modifiers->ctrl << seat->kbd.mod_ctrl; + if (seat->kbd.mod_alt != XKB_MOD_INVALID) + mods |= modifiers->alt << seat->kbd.mod_alt; + if (seat->kbd.mod_super != XKB_MOD_INVALID) + mods |= modifiers->super << seat->kbd.mod_super; + return mods; +} diff --git a/config.h b/config.h index 38ff758f..2061415e 100644 --- a/config.h +++ b/config.h @@ -336,5 +336,10 @@ struct config *config_clone(const struct config *old); bool config_font_parse(const char *pattern, struct config_font *font); void config_font_list_destroy(struct config_font_list *font_list); +struct seat; +xkb_mod_mask_t +conf_modifiers_to_mask( + const struct seat *seat, const struct config_key_modifiers *modifiers); + bool check_if_font_is_monospaced( const char *pattern, user_notifications_t *notifications); diff --git a/input.c b/input.c index 403ee965..04d03bbe 100644 --- a/input.c +++ b/input.c @@ -380,237 +380,6 @@ execute_binding(struct seat *seat, struct terminal *term, return false; } -static xkb_mod_mask_t -conf_modifiers_to_mask(const struct seat *seat, - const struct config_key_modifiers *modifiers) -{ - xkb_mod_mask_t mods = 0; - if (seat->kbd.mod_shift != XKB_MOD_INVALID) - mods |= modifiers->shift << seat->kbd.mod_shift; - if (seat->kbd.mod_ctrl != XKB_MOD_INVALID) - mods |= modifiers->ctrl << seat->kbd.mod_ctrl; - if (seat->kbd.mod_alt != XKB_MOD_INVALID) - mods |= modifiers->alt << seat->kbd.mod_alt; - if (seat->kbd.mod_super != XKB_MOD_INVALID) - mods |= modifiers->super << seat->kbd.mod_super; - return mods; -} - -static xkb_keycode_list_t -key_codes_for_xkb_sym(struct xkb_keymap *keymap, xkb_keysym_t sym) -{ - xkb_keycode_list_t key_codes = tll_init(); - - /* - * Find all key codes that map to this symbol. - * - * This allows us to match bindings in other layouts - * too. - */ - struct xkb_state *state = xkb_state_new(keymap); - - for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap); - code <= xkb_keymap_max_keycode(keymap); - code++) - { - if (xkb_state_key_get_one_sym(state, code) == sym) - tll_push_back(key_codes, code); - } - - xkb_state_unref(state); - return key_codes; -} - -static xkb_keysym_t -maybe_repair_key_combo(const struct seat *seat, - xkb_keysym_t sym, xkb_mod_mask_t mods) -{ - /* - * Detect combos containing a shifted symbol and the corresponding - * modifier, and replace the shifted symbol with its unshifted - * variant. - * - * For example, the combo is “Control+Shift+U”. In this case, - * Shift is the modifier used to “shift” ‘u’ to ‘U’, after which - * ‘Shift’ will have been “consumed”. Since we filter out consumed - * modifiers when matching key combos, this key combo will never - * trigger (we will never be able to match the ‘Shift’ modifier). - * - * There are two correct variants of the above key combo: - * - “Control+U” (upper case ‘U’) - * - “Control+Shift+u” (lower case ‘u’) - * - * What we do here is, for each key *code*, check if there are any - * (shifted) levels where it produces ‘sym’. If there are, check - * *which* sets of modifiers are needed to produce it, and compare - * with ‘mods’. - * - * If there is at least one common modifier, it means ‘sym’ is a - * “shifted” symbol, with the corresponding shifting modifier - * explicitly included in the key combo. I.e. the key combo will - * never trigger. - * - * We then proceed and “repair” the key combo by replacing ‘sym’ - * with the corresponding unshifted symbol. - * - * To reduce the noise, we ignore all key codes where the shifted - * symbol is the same as the unshifted symbol. - */ - - for (xkb_keycode_t code = xkb_keymap_min_keycode(seat->kbd.xkb_keymap); - code <= xkb_keymap_max_keycode(seat->kbd.xkb_keymap); - code++) - { - xkb_layout_index_t layout_idx = - xkb_state_key_get_layout(seat->kbd.xkb_state, code); - - /* Get all unshifted symbols for this key */ - const xkb_keysym_t *base_syms = NULL; - size_t base_count = xkb_keymap_key_get_syms_by_level( - seat->kbd.xkb_keymap, code, layout_idx, 0, &base_syms); - - if (base_count == 0 || sym == base_syms[0]) { - /* No unshifted symbols, or unshifted symbol is same as ‘sym’ */ - continue; - } - - /* Name of the unshifted symbol, for logging */ - char base_name[100]; - xkb_keysym_get_name(base_syms[0], base_name, sizeof(base_name)); - - /* Iterate all shift levels */ - for (xkb_level_index_t level_idx = 1; - level_idx < xkb_keymap_num_levels_for_key( - seat->kbd.xkb_keymap, code, layout_idx); - level_idx++) { - - /* Get all symbols for current shift level */ - const xkb_keysym_t *shifted_syms = NULL; - size_t shifted_count = xkb_keymap_key_get_syms_by_level( - seat->kbd.xkb_keymap, code, - layout_idx, level_idx, &shifted_syms); - - for (size_t i = 0; i < shifted_count; i++) { - if (shifted_syms[i] != sym) - continue; - - /* Get modifier sets that produces the current shift level */ - xkb_mod_mask_t mod_masks[16]; - size_t mod_mask_count = xkb_keymap_key_get_mods_for_level( - seat->kbd.xkb_keymap, code, layout_idx, level_idx, - mod_masks, ALEN(mod_masks)); - - /* Check if key combo’s modifier set intersects */ - for (size_t j = 0; j < mod_mask_count; j++) { - if ((mod_masks[j] & mods) != mod_masks[j]) - continue; - - char combo[64] = {0}; - - for (int k = 0; k < sizeof(xkb_mod_mask_t) * 8; k++) { - if (!(mods & (1u << k))) - continue; - - const char *mod_name = xkb_keymap_mod_get_name( - seat->kbd.xkb_keymap, k); - strcat(combo, mod_name); - strcat(combo, "+"); - } - - size_t len = strlen(combo); - xkb_keysym_get_name( - sym, &combo[len], sizeof(combo) - len); - - LOG_WARN( - "%s: combo with both explicit modifier and shifted symbol " - "(level=%d, mod-mask=0x%08x), " - "replacing with %s", - combo, level_idx, mod_masks[j], base_name); - - /* Replace with unshifted symbol */ - return base_syms[0]; - } - } - } - } - - return sym; -} - -static void -convert_key_binding(const struct seat *seat, - const struct config_key_binding *conf_binding, - key_binding_list_t *bindings) -{ - xkb_mod_mask_t mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers); - xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods); - - struct key_binding binding = { - .type = KEY_BINDING, - .action = conf_binding->action, - .aux = &conf_binding->aux, - .mods = mods, - .k = { - .sym = sym, - .key_codes = key_codes_for_xkb_sym(seat->kbd.xkb_keymap, sym), - }, - }; - tll_push_back(*bindings, binding); -} - -static void -convert_key_bindings(const struct config *conf, struct seat *seat) -{ - for (size_t i = 0; i < conf->bindings.key.count; i++) { - const struct config_key_binding *binding = &conf->bindings.key.arr[i]; - convert_key_binding(seat, binding, &seat->kbd.bindings.key); - } -} - -static void -convert_search_bindings(const struct config *conf, struct seat *seat) -{ - for (size_t i = 0; i < conf->bindings.search.count; i++) { - const struct config_key_binding *binding = &conf->bindings.search.arr[i]; - convert_key_binding(seat, binding, &seat->kbd.bindings.search); - } -} - -static void -convert_url_bindings(const struct config *conf, struct seat *seat) -{ - for (size_t i = 0; i < conf->bindings.url.count; i++) { - const struct config_key_binding *binding = &conf->bindings.url.arr[i]; - convert_key_binding(seat, binding, &seat->kbd.bindings.url); - } -} - -static void -convert_mouse_binding(struct seat *seat, - const struct config_key_binding *conf_binding) -{ - struct key_binding binding = { - .type = MOUSE_BINDING, - .action = conf_binding->action, - .aux = &conf_binding->aux, - .mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers), - .m = { - .button = conf_binding->m.button, - .count = conf_binding->m.count, - }, - }; - tll_push_back(seat->mouse.bindings, binding); -} - -static void -convert_mouse_bindings(const struct config *conf, struct seat *seat) -{ - for (size_t i = 0; i < conf->bindings.mouse.count; i++) { - const struct config_key_binding *binding = &conf->bindings.mouse.arr[i]; - convert_mouse_binding(seat, binding); - } -} - static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) @@ -646,7 +415,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.xkb = NULL; } - wayl_bindings_reset(seat); + key_binding_unload_keymap(wayl->key_binding_manager, seat); /* Verify keymap is in a format we understand */ switch ((enum wl_keyboard_keymap_format)format) { @@ -728,10 +497,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, munmap(map_str, size); close(fd); - convert_key_bindings(wayl->conf, seat); - convert_search_bindings(wayl->conf, seat); - convert_url_bindings(wayl->conf, seat); - convert_mouse_bindings(wayl->conf, seat); + key_binding_load_keymap(wayl->key_binding_manager, seat); } static void @@ -1558,13 +1324,17 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, size_t raw_count = xkb_keymap_key_get_syms_by_level( seat->kbd.xkb_keymap, key, layout_idx, 0, &raw_syms); + const struct key_binding_set *bindings = key_binding_for( + seat->wayl->key_binding_manager, term, seat); + xassert(bindings != NULL); + if (pressed) { if (term->is_searching) { if (should_repeat) start_repeater(seat, key); search_input( - seat, term, key, sym, mods, consumed, locked, + seat, term, bindings, key, sym, mods, consumed, locked, raw_syms, raw_count, serial); return; } @@ -1574,7 +1344,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, start_repeater(seat, key); urls_input( - seat, term, key, sym, mods, consumed, locked, + seat, term, bindings, key, sym, mods, consumed, locked, raw_syms, raw_count, serial); return; } @@ -1602,7 +1372,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * User configurable bindings */ if (pressed) { - tll_foreach(seat->kbd.bindings.key, it) { + tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; /* Match translated symbol */ @@ -2475,8 +2245,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, mods &= ~seat->kbd.selection_override_modmask; const struct key_binding *match = NULL; + const struct key_binding_set *bindings = key_binding_for( + wayl->key_binding_manager, term, seat); + xassert(bindings != NULL); - tll_foreach(seat->mouse.bindings, it) { + tll_foreach(bindings->mouse, it) { const struct key_binding *binding = &it->item; if (binding->m.button != button) { diff --git a/key-binding.c b/key-binding.c new file mode 100644 index 00000000..81a9f6a2 --- /dev/null +++ b/key-binding.c @@ -0,0 +1,514 @@ +#include "key-binding.h" + +#include + +#define LOG_MODULE "key-binding" +#define LOG_ENABLE_DBG 1 +#include "log.h" + +#include "config.h" +#include "debug.h" +#include "terminal.h" +#include "util.h" +#include "wayland.h" +#include "xmalloc.h" + +struct key_set { + struct key_binding_set public; + + const struct config *conf; + const struct seat *seat; + size_t conf_ref_count; +}; +typedef tll(struct key_set) bind_set_list_t; + +struct key_binding_manager { + struct key_set *last_used_set; + bind_set_list_t binding_sets; +}; + +static void load_keymap(struct key_set *set); +static void unload_keymap(struct key_set *set); + +struct key_binding_manager * +key_binding_manager_new(void) +{ + struct key_binding_manager *mgr = xcalloc(1, sizeof(*mgr)); + return mgr; +} + +void +key_binding_manager_destroy(struct key_binding_manager *mgr) +{ + xassert(tll_length(mgr->binding_sets) == 0); + free(mgr); +} + +void +key_binding_new_for_seat(struct key_binding_manager *mgr, + const struct seat *seat) +{ +#if defined(_DEBUG) + tll_foreach(mgr->binding_sets, it) + xassert(it->item.seat != seat); +#endif + + tll_foreach(seat->wayl->terms, it) { + struct key_set set = { + .public = { + .key = tll_init(), + .search = tll_init(), + .url = tll_init(), + .mouse = tll_init(), + }, + .conf = it->item->conf, + .seat = seat, + .conf_ref_count = 1, + }; + + tll_push_back(mgr->binding_sets, set); + + LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1", + (void *)&tll_back(mgr->binding_sets), + (void *)set.seat, (void *)set.conf); + + load_keymap(&tll_back(mgr->binding_sets)); + } + + LOG_DBG("new (seat): total number of sets: %zu", + tll_length(mgr->binding_sets)); +} + +void +key_binding_new_for_term(struct key_binding_manager *mgr, + const struct terminal *term) +{ + const struct config *conf = term->conf; + const struct wayland *wayl = term->wl; + + tll_foreach(wayl->seats, it) { + struct seat *seat = &it->item; + + struct key_set *existing = + (struct key_set *)key_binding_for(mgr, term, seat); + + if (existing != NULL) { + existing->conf_ref_count++; + continue; + } + + struct key_set set = { + .public = { + .key = tll_init(), + .search = tll_init(), + .url = tll_init(), + .mouse = tll_init(), + }, + .conf = conf, + .seat = seat, + .conf_ref_count = 1, + }; + + tll_push_back(mgr->binding_sets, set); + + load_keymap(&tll_back(mgr->binding_sets)); + + /* Chances are high this set will be requested next */ + mgr->last_used_set = &tll_back(mgr->binding_sets); + + LOG_DBG("new (term): set=%p, seat=%p, conf=%p, ref-count=1", + (void *)&tll_back(mgr->binding_sets), + (void *)set.seat, (void *)set.conf); + } + + LOG_DBG("new (term): total number of sets: %zu", + tll_length(mgr->binding_sets)); +} + +struct key_binding_set * +key_binding_for(struct key_binding_manager *mgr, const struct terminal *term, + const struct seat *seat) +{ + const struct config *conf = term->conf; + + struct key_set *last_used = mgr->last_used_set; + if (last_used != NULL && + last_used->conf == conf && + last_used->seat == seat) + { + // LOG_DBG("lookup: last used"); + return &last_used->public; + } + + tll_foreach(mgr->binding_sets, it) { + struct key_set *set = &it->item; + + if (set->conf != conf) + continue; + if (set->seat != seat) + continue; + +#if 0 + LOG_DBG("lookup: set=%p, seat=%p, conf=%p, ref-count=%zu", + (void *)set, (void *)seat, (void *)conf, set->conf_ref_count); +#endif + mgr->last_used_set = set; + return &set->public; + } + + return NULL; +} + +static void +key_binding_set_destroy(struct key_binding_manager *mgr, + struct key_set *set) +{ + unload_keymap(set); + if (mgr->last_used_set == set) + mgr->last_used_set = NULL; + + /* Note: caller must remove from binding_sets */ +} + +void +key_binding_remove_seat(struct key_binding_manager *mgr, + const struct seat *seat) +{ + tll_foreach(mgr->binding_sets, it) { + struct key_set *set = &it->item; + + if (set->seat != seat) + continue; + + key_binding_set_destroy(mgr, set); + tll_remove(mgr->binding_sets, it); + + LOG_DBG("remove seat: set=%p, seat=%p, total number of sets: %zu", + (void *)set, (void *)seat, tll_length(mgr->binding_sets)); + } + + LOG_DBG("remove seat: total number of sets: %zu", + tll_length(mgr->binding_sets)); +} + +void +key_binding_unref_term(struct key_binding_manager *mgr, + const struct terminal *term) +{ + const struct config *conf = term->conf; + + tll_foreach(mgr->binding_sets, it) { + struct key_set *set = &it->item; + + if (set->conf != conf) + continue; + + xassert(set->conf_ref_count > 0); + if (--set->conf_ref_count == 0) { + LOG_DBG("unref conf: set=%p, seat=%p, conf=%p", + (void *)set, (void *)set->seat, (void *)conf); + + key_binding_set_destroy(mgr, set); + tll_remove(mgr->binding_sets, it); + } + } + + LOG_DBG("unref conf: total number of sets: %zu", + tll_length(mgr->binding_sets)); +} + +static xkb_keycode_list_t +key_codes_for_xkb_sym(struct xkb_keymap *keymap, xkb_keysym_t sym) +{ + xkb_keycode_list_t key_codes = tll_init(); + + /* + * Find all key codes that map to this symbol. + * + * This allows us to match bindings in other layouts + * too. + */ + struct xkb_state *state = xkb_state_new(keymap); + + for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap); + code <= xkb_keymap_max_keycode(keymap); + code++) + { + if (xkb_state_key_get_one_sym(state, code) == sym) + tll_push_back(key_codes, code); + } + + xkb_state_unref(state); + return key_codes; +} + +static xkb_keysym_t +maybe_repair_key_combo(const struct seat *seat, + xkb_keysym_t sym, xkb_mod_mask_t mods) +{ + /* + * Detect combos containing a shifted symbol and the corresponding + * modifier, and replace the shifted symbol with its unshifted + * variant. + * + * For example, the combo is “Control+Shift+U”. In this case, + * Shift is the modifier used to “shift” ‘u’ to ‘U’, after which + * ‘Shift’ will have been “consumed”. Since we filter out consumed + * modifiers when matching key combos, this key combo will never + * trigger (we will never be able to match the ‘Shift’ modifier). + * + * There are two correct variants of the above key combo: + * - “Control+U” (upper case ‘U’) + * - “Control+Shift+u” (lower case ‘u’) + * + * What we do here is, for each key *code*, check if there are any + * (shifted) levels where it produces ‘sym’. If there are, check + * *which* sets of modifiers are needed to produce it, and compare + * with ‘mods’. + * + * If there is at least one common modifier, it means ‘sym’ is a + * “shifted” symbol, with the corresponding shifting modifier + * explicitly included in the key combo. I.e. the key combo will + * never trigger. + * + * We then proceed and “repair” the key combo by replacing ‘sym’ + * with the corresponding unshifted symbol. + * + * To reduce the noise, we ignore all key codes where the shifted + * symbol is the same as the unshifted symbol. + */ + + for (xkb_keycode_t code = xkb_keymap_min_keycode(seat->kbd.xkb_keymap); + code <= xkb_keymap_max_keycode(seat->kbd.xkb_keymap); + code++) + { + xkb_layout_index_t layout_idx = + xkb_state_key_get_layout(seat->kbd.xkb_state, code); + + /* Get all unshifted symbols for this key */ + const xkb_keysym_t *base_syms = NULL; + size_t base_count = xkb_keymap_key_get_syms_by_level( + seat->kbd.xkb_keymap, code, layout_idx, 0, &base_syms); + + if (base_count == 0 || sym == base_syms[0]) { + /* No unshifted symbols, or unshifted symbol is same as ‘sym’ */ + continue; + } + + /* Name of the unshifted symbol, for logging */ + char base_name[100]; + xkb_keysym_get_name(base_syms[0], base_name, sizeof(base_name)); + + /* Iterate all shift levels */ + for (xkb_level_index_t level_idx = 1; + level_idx < xkb_keymap_num_levels_for_key( + seat->kbd.xkb_keymap, code, layout_idx); + level_idx++) { + + /* Get all symbols for current shift level */ + const xkb_keysym_t *shifted_syms = NULL; + size_t shifted_count = xkb_keymap_key_get_syms_by_level( + seat->kbd.xkb_keymap, code, + layout_idx, level_idx, &shifted_syms); + + for (size_t i = 0; i < shifted_count; i++) { + if (shifted_syms[i] != sym) + continue; + + /* Get modifier sets that produces the current shift level */ + xkb_mod_mask_t mod_masks[16]; + size_t mod_mask_count = xkb_keymap_key_get_mods_for_level( + seat->kbd.xkb_keymap, code, layout_idx, level_idx, + mod_masks, ALEN(mod_masks)); + + /* Check if key combo’s modifier set intersects */ + for (size_t j = 0; j < mod_mask_count; j++) { + if ((mod_masks[j] & mods) != mod_masks[j]) + continue; + + char combo[64] = {0}; + + for (int k = 0; k < sizeof(xkb_mod_mask_t) * 8; k++) { + if (!(mods & (1u << k))) + continue; + + const char *mod_name = xkb_keymap_mod_get_name( + seat->kbd.xkb_keymap, k); + strcat(combo, mod_name); + strcat(combo, "+"); + } + + size_t len = strlen(combo); + xkb_keysym_get_name( + sym, &combo[len], sizeof(combo) - len); + + LOG_WARN( + "%s: combo with both explicit modifier and shifted symbol " + "(level=%d, mod-mask=0x%08x), " + "replacing with %s", + combo, level_idx, mod_masks[j], base_name); + + /* Replace with unshifted symbol */ + return base_syms[0]; + } + } + } + } + + return sym; +} + +static void +convert_key_binding(struct key_set *set, + const struct config_key_binding *conf_binding, + key_binding_list_t *bindings) +{ + const struct seat *seat = set->seat; + + xkb_mod_mask_t mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers); + xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods); + + struct key_binding binding = { + .type = KEY_BINDING, + .action = conf_binding->action, + .aux = &conf_binding->aux, + .mods = mods, + .k = { + .sym = sym, + .key_codes = key_codes_for_xkb_sym(seat->kbd.xkb_keymap, sym), + }, + }; + tll_push_back(*bindings, binding); +} + +static void +convert_key_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.key.count; i++) { + const struct config_key_binding *binding = &conf->bindings.key.arr[i]; + convert_key_binding(set, binding, &set->public.key); + } +} + +static void +convert_search_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.search.count; i++) { + const struct config_key_binding *binding = &conf->bindings.search.arr[i]; + convert_key_binding(set, binding, &set->public.search); + } +} + +static void +convert_url_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.url.count; i++) { + const struct config_key_binding *binding = &conf->bindings.url.arr[i]; + convert_key_binding(set, binding, &set->public.url); + } +} + +static void +convert_mouse_binding(struct key_set *set, + const struct config_key_binding *conf_binding) +{ + struct key_binding binding = { + .type = MOUSE_BINDING, + .action = conf_binding->action, + .aux = &conf_binding->aux, + .mods = conf_modifiers_to_mask(set->seat, &conf_binding->modifiers), + .m = { + .button = conf_binding->m.button, + .count = conf_binding->m.count, + }, + }; + tll_push_back(set->public.mouse, binding); +} + +static void +convert_mouse_bindings(struct key_set *set) +{ + const struct config *conf = set->conf; + + for (size_t i = 0; i < conf->bindings.mouse.count; i++) { + const struct config_key_binding *binding = + &conf->bindings.mouse.arr[i]; + convert_mouse_binding(set, binding); + } +} + +static void +load_keymap(struct key_set *set) +{ + LOG_DBG("load keymap: set=%p, seat=%p, conf=%p", + (void *)set, (void *)set->seat, (void *)set->conf); + + if (set->seat->kbd.xkb_state == NULL || + set->seat->kbd.xkb_keymap == NULL) + { + LOG_DBG("no XKB keymap"); + return; + } + + convert_key_bindings(set); + convert_search_bindings(set); + convert_url_bindings(set); + convert_mouse_bindings(set); +} + +void +key_binding_load_keymap(struct key_binding_manager *mgr, + const struct seat *seat) +{ + tll_foreach(mgr->binding_sets, it) { + struct key_set *set = &it->item; + + if (set->seat == seat) + load_keymap(set); + } +} + +static void NOINLINE +key_bindings_destroy(key_binding_list_t *bindings) +{ + tll_foreach(*bindings, it) { + struct key_binding *bind = &it->item; + switch (bind->type) { + case KEY_BINDING: tll_free(it->item.k.key_codes); break; + case MOUSE_BINDING: break; + } + + tll_remove(*bindings, it); + } +} + +static void +unload_keymap(struct key_set *set) +{ + key_bindings_destroy(&set->public.key); + key_bindings_destroy(&set->public.search); + key_bindings_destroy(&set->public.url); + key_bindings_destroy(&set->public.mouse); +} + +void +key_binding_unload_keymap(struct key_binding_manager *mgr, + const struct seat *seat) +{ + tll_foreach(mgr->binding_sets, it) { + struct key_set *set = &it->item; + if (set->seat != seat) + continue; + + LOG_DBG("unload keymap: set=%p, seat=%p, conf=%p", + (void *)set, (void *)seat, (void *)set->conf); + + unload_keymap(set); + } +} diff --git a/key-binding.h b/key-binding.h new file mode 100644 index 00000000..a8301fd4 --- /dev/null +++ b/key-binding.h @@ -0,0 +1,143 @@ +#pragma once + +#include + +#include +#include + +#include "config.h" + +enum bind_action_normal { + BIND_ACTION_NONE, + BIND_ACTION_NOOP, + BIND_ACTION_SCROLLBACK_UP_PAGE, + BIND_ACTION_SCROLLBACK_UP_HALF_PAGE, + BIND_ACTION_SCROLLBACK_UP_LINE, + BIND_ACTION_SCROLLBACK_DOWN_PAGE, + BIND_ACTION_SCROLLBACK_DOWN_HALF_PAGE, + BIND_ACTION_SCROLLBACK_DOWN_LINE, + BIND_ACTION_SCROLLBACK_HOME, + BIND_ACTION_SCROLLBACK_END, + BIND_ACTION_CLIPBOARD_COPY, + BIND_ACTION_CLIPBOARD_PASTE, + BIND_ACTION_PRIMARY_PASTE, + BIND_ACTION_SEARCH_START, + BIND_ACTION_FONT_SIZE_UP, + BIND_ACTION_FONT_SIZE_DOWN, + BIND_ACTION_FONT_SIZE_RESET, + BIND_ACTION_SPAWN_TERMINAL, + BIND_ACTION_MINIMIZE, + BIND_ACTION_MAXIMIZE, + BIND_ACTION_FULLSCREEN, + BIND_ACTION_PIPE_SCROLLBACK, + BIND_ACTION_PIPE_VIEW, + BIND_ACTION_PIPE_SELECTED, + BIND_ACTION_SHOW_URLS_COPY, + BIND_ACTION_SHOW_URLS_LAUNCH, + BIND_ACTION_SHOW_URLS_PERSISTENT, + BIND_ACTION_TEXT_BINDING, + + /* Mouse specific actions - i.e. they require a mouse coordinate */ + BIND_ACTION_SELECT_BEGIN, + BIND_ACTION_SELECT_BEGIN_BLOCK, + BIND_ACTION_SELECT_EXTEND, + BIND_ACTION_SELECT_EXTEND_CHAR_WISE, + BIND_ACTION_SELECT_WORD, + BIND_ACTION_SELECT_WORD_WS, + BIND_ACTION_SELECT_ROW, + + BIND_ACTION_KEY_COUNT = BIND_ACTION_TEXT_BINDING + 1, + BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, +}; + +enum bind_action_search { + BIND_ACTION_SEARCH_NONE, + BIND_ACTION_SEARCH_CANCEL, + BIND_ACTION_SEARCH_COMMIT, + BIND_ACTION_SEARCH_FIND_PREV, + BIND_ACTION_SEARCH_FIND_NEXT, + BIND_ACTION_SEARCH_EDIT_LEFT, + BIND_ACTION_SEARCH_EDIT_LEFT_WORD, + BIND_ACTION_SEARCH_EDIT_RIGHT, + BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, + BIND_ACTION_SEARCH_EDIT_HOME, + BIND_ACTION_SEARCH_EDIT_END, + BIND_ACTION_SEARCH_DELETE_PREV, + BIND_ACTION_SEARCH_DELETE_PREV_WORD, + BIND_ACTION_SEARCH_DELETE_NEXT, + BIND_ACTION_SEARCH_DELETE_NEXT_WORD, + BIND_ACTION_SEARCH_EXTEND_WORD, + BIND_ACTION_SEARCH_EXTEND_WORD_WS, + BIND_ACTION_SEARCH_CLIPBOARD_PASTE, + BIND_ACTION_SEARCH_PRIMARY_PASTE, + BIND_ACTION_SEARCH_COUNT, +}; + +enum bind_action_url { + BIND_ACTION_URL_NONE, + BIND_ACTION_URL_CANCEL, + BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL, + BIND_ACTION_URL_COUNT, +}; + +typedef tll(xkb_keycode_t) xkb_keycode_list_t; + +struct key_binding { + enum key_binding_type type; + + int action; /* enum bind_action_* */ + xkb_mod_mask_t mods; + + union { + struct { + xkb_keysym_t sym; + xkb_keycode_list_t key_codes; + } k; + + struct { + uint32_t button; + int count; + } m; + }; + + const struct binding_aux *aux; +}; +typedef tll(struct key_binding) key_binding_list_t; + +struct terminal; +struct seat; + +struct key_binding_set { + key_binding_list_t key; + key_binding_list_t search; + key_binding_list_t url; + key_binding_list_t mouse; +}; + +struct key_binding_manager; + +struct key_binding_manager *key_binding_manager_new(void); +void key_binding_manager_destroy(struct key_binding_manager *mgr); + +void key_binding_new_for_seat( + struct key_binding_manager *mgr, const struct seat *seat); + +void key_binding_new_for_term( + struct key_binding_manager *mgr, const struct terminal *term); + +/* Returns the set of key bindings associated with this seat/term pair */ +struct key_binding_set *key_binding_for( + struct key_binding_manager *mgr, const struct terminal *term, + const struct seat *seat); + +/* Remove all key bindings tied to the specified seat */ +void key_binding_remove_seat( + struct key_binding_manager *mgr, const struct seat *seat); + +void key_binding_unref_term( + struct key_binding_manager *mgr, const struct terminal *term); + +void key_binding_load_keymap( + struct key_binding_manager *mgr, const struct seat *seat); +void key_binding_unload_keymap( + struct key_binding_manager *mgr, const struct seat *seat); diff --git a/main.c b/main.c index 58399a93..0acb7ea6 100644 --- a/main.c +++ b/main.c @@ -21,8 +21,9 @@ #include "log.h" #include "config.h" -#include "foot-features.h" #include "fdm.h" +#include "foot-features.h" +#include "key-binding.h" #include "macros.h" #include "reaper.h" #include "render.h" @@ -569,6 +570,7 @@ main(int argc, char *const *argv) struct fdm *fdm = NULL; struct reaper *reaper = NULL; + struct key_binding_manager *key_binding_manager = NULL; struct wayland *wayl = NULL; struct renderer *renderer = NULL; struct terminal *term = NULL; @@ -600,7 +602,10 @@ main(int argc, char *const *argv) if ((reaper = reaper_init(fdm)) == NULL) goto out; - if ((wayl = wayl_init(&conf, fdm)) == NULL) + if ((key_binding_manager = key_binding_manager_new()) == NULL) + goto out; + + if ((wayl = wayl_init(&conf, fdm, key_binding_manager)) == NULL) goto out; if ((renderer = render_init(fdm, wayl)) == NULL) @@ -658,6 +663,7 @@ out: shm_fini(); render_destroy(renderer); wayl_destroy(wayl); + key_binding_manager_destroy(key_binding_manager); reaper_destroy(reaper); fdm_signal_del(fdm, SIGTERM); fdm_signal_del(fdm, SIGINT); diff --git a/meson.build b/meson.build index 735419f9..c552d2a2 100644 --- a/meson.build +++ b/meson.build @@ -210,6 +210,7 @@ executable( 'foot-features.h', 'ime.c', 'ime.h', 'input.c', 'input.h', + 'key-binding.c', 'key-binding.h', 'main.c', 'notify.c', 'notify.h', 'quirks.c', 'quirks.h', diff --git a/search.c b/search.c index 2c2941ff..2a7903d7 100644 --- a/search.c +++ b/search.c @@ -13,6 +13,7 @@ #include "extract.h" #include "grid.h" #include "input.h" +#include "key-binding.h" #include "misc.h" #include "render.h" #include "selection.h" @@ -899,7 +900,8 @@ execute_binding(struct seat *seat, struct terminal *term, } void -search_input(struct seat *seat, struct terminal *term, uint32_t key, +search_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, xkb_mod_mask_t locked, const xkb_keysym_t *raw_syms, size_t raw_count, @@ -919,7 +921,7 @@ search_input(struct seat *seat, struct terminal *term, uint32_t key, bool redraw = false; /* Key bindings */ - tll_foreach(seat->kbd.bindings.search, it) { + tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; /* Match translated symbol */ diff --git a/search.h b/search.h index 24828512..d5d4162b 100644 --- a/search.h +++ b/search.h @@ -1,12 +1,15 @@ #pragma once #include + +#include "key-binding.h" #include "terminal.h" void search_begin(struct terminal *term); void search_cancel(struct terminal *term); void search_input( - struct seat *seat, struct terminal *term, uint32_t key, + struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, xkb_mod_mask_t locked, const xkb_keysym_t *raw_syms, size_t raw_count, diff --git a/terminal.c b/terminal.c index 8a8a5451..2bb35ef5 100644 --- a/terminal.c +++ b/terminal.c @@ -1262,6 +1262,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); + key_binding_new_for_term(wayl->key_binding_manager, term); + /* Initialize the Wayland window backend */ if ((term->window = wayl_win_init(term, token)) == NULL) goto err; @@ -1577,6 +1579,8 @@ term_destroy(struct terminal *term) if (term == NULL) return 0; + key_binding_unref_term(term->wl->key_binding_manager, term); + tll_foreach(term->wl->terms, it) { if (it->item == term) { tll_remove(term->wl->terms, it); diff --git a/terminal.h b/terminal.h index 042af6dc..6c566e6e 100644 --- a/terminal.h +++ b/terminal.h @@ -14,10 +14,11 @@ #include #include -#include "config.h" #include "composed.h" +#include "config.h" #include "debug.h" #include "fdm.h" +#include "key-binding.h" #include "macros.h" #include "reaper.h" #include "shm.h" diff --git a/url-mode.c b/url-mode.c index 5b144e9f..8a46c6bf 100644 --- a/url-mode.c +++ b/url-mode.c @@ -13,6 +13,7 @@ #include "log.h" #include "char32.h" #include "grid.h" +#include "key-binding.h" #include "render.h" #include "selection.h" #include "spawn.h" @@ -118,7 +119,8 @@ activate_url(struct seat *seat, struct terminal *term, const struct url *url) } void -urls_input(struct seat *seat, struct terminal *term, uint32_t key, +urls_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, xkb_mod_mask_t locked, const xkb_keysym_t *raw_syms, size_t raw_count, @@ -130,7 +132,7 @@ urls_input(struct seat *seat, struct terminal *term, uint32_t key, consumed & seat->kbd.bind_significant & ~locked; /* Key bindings */ - tll_foreach(seat->kbd.bindings.url, it) { + tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; /* Match translated symbol */ diff --git a/url-mode.h b/url-mode.h index 0f1451da..abfcb57b 100644 --- a/url-mode.h +++ b/url-mode.h @@ -5,6 +5,7 @@ #include #include "config.h" +#include "key-binding.h" #include "terminal.h" static inline bool urls_mode_is_active(const struct terminal *term) @@ -19,7 +20,8 @@ void urls_assign_key_combos(const struct config *conf, url_list_t *urls); void urls_render(struct terminal *term); void urls_reset(struct terminal *term); -void urls_input(struct seat *seat, struct terminal *term, uint32_t key, +void urls_input(struct seat *seat, struct terminal *term, + const struct key_binding_set *bindings, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed, xkb_mod_mask_t locked, const xkb_keysym_t *raw_syms, size_t raw_count, diff --git a/wayland.c b/wayland.c index 7ffbe4d5..2eeb1d72 100644 --- a/wayland.c +++ b/wayland.c @@ -156,27 +156,9 @@ seat_add_text_input(struct seat *seat) } static void -key_bindings_destroy(key_binding_list_t *bindings) +seat_add_key_bindings(struct seat *seat) { - tll_foreach(*bindings, it) { - struct key_binding *bind = &it->item; - switch (bind->type) { - case KEY_BINDING: tll_free(it->item.k.key_codes); break; - case MOUSE_BINDING: break; - } - - tll_remove(*bindings, it); - } -} - -void -wayl_bindings_reset(struct seat *seat) -{ - - key_bindings_destroy(&seat->kbd.bindings.key); - key_bindings_destroy(&seat->kbd.bindings.search); - key_bindings_destroy(&seat->kbd.bindings.url); - key_bindings_destroy(&seat->mouse.bindings); + key_binding_new_for_seat(seat->wayl->key_binding_manager, seat); } static void @@ -186,7 +168,7 @@ seat_destroy(struct seat *seat) return; tll_free(seat->mouse.buttons); - wayl_bindings_reset(seat); + key_binding_remove_seat(seat->wayl->key_binding_manager, seat); if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_unref(seat->kbd.xkb_compose_state); @@ -952,6 +934,7 @@ handle_global(void *data, struct wl_registry *registry, seat_add_data_device(seat); seat_add_primary_selection(seat); seat_add_text_input(seat); + seat_add_key_bindings(seat); wl_seat_add_listener(wl_seat, &seat_listener, seat); } @@ -1207,7 +1190,8 @@ fdm_wayl(struct fdm *fdm, int fd, int events, void *data) } struct wayland * -wayl_init(const struct config *conf, struct fdm *fdm) +wayl_init(const struct config *conf, struct fdm *fdm, + struct key_binding_manager *key_binding_manager) { struct wayland *wayl = calloc(1, sizeof(*wayl)); if (unlikely(wayl == NULL)) { @@ -1217,6 +1201,7 @@ wayl_init(const struct config *conf, struct fdm *fdm) wayl->conf = conf; wayl->fdm = fdm; + wayl->key_binding_manager = key_binding_manager; wayl->fd = -1; if (!fdm_hook_add(fdm, &fdm_hook, wayl, FDM_HOOK_PRIORITY_LOW)) { diff --git a/wayland.h b/wayland.h index ff4f2d77..90087706 100644 --- a/wayland.h +++ b/wayland.h @@ -6,7 +6,6 @@ #include #include -#include /* Wayland protocols */ #include @@ -29,103 +28,6 @@ /* Forward declarations */ struct terminal; -enum bind_action_normal { - BIND_ACTION_NONE, - BIND_ACTION_NOOP, - BIND_ACTION_SCROLLBACK_UP_PAGE, - BIND_ACTION_SCROLLBACK_UP_HALF_PAGE, - BIND_ACTION_SCROLLBACK_UP_LINE, - BIND_ACTION_SCROLLBACK_DOWN_PAGE, - BIND_ACTION_SCROLLBACK_DOWN_HALF_PAGE, - BIND_ACTION_SCROLLBACK_DOWN_LINE, - BIND_ACTION_SCROLLBACK_HOME, - BIND_ACTION_SCROLLBACK_END, - BIND_ACTION_CLIPBOARD_COPY, - BIND_ACTION_CLIPBOARD_PASTE, - BIND_ACTION_PRIMARY_PASTE, - BIND_ACTION_SEARCH_START, - BIND_ACTION_FONT_SIZE_UP, - BIND_ACTION_FONT_SIZE_DOWN, - BIND_ACTION_FONT_SIZE_RESET, - BIND_ACTION_SPAWN_TERMINAL, - BIND_ACTION_MINIMIZE, - BIND_ACTION_MAXIMIZE, - BIND_ACTION_FULLSCREEN, - BIND_ACTION_PIPE_SCROLLBACK, - BIND_ACTION_PIPE_VIEW, - BIND_ACTION_PIPE_SELECTED, - BIND_ACTION_SHOW_URLS_COPY, - BIND_ACTION_SHOW_URLS_LAUNCH, - BIND_ACTION_SHOW_URLS_PERSISTENT, - BIND_ACTION_TEXT_BINDING, - - /* Mouse specific actions - i.e. they require a mouse coordinate */ - BIND_ACTION_SELECT_BEGIN, - BIND_ACTION_SELECT_BEGIN_BLOCK, - BIND_ACTION_SELECT_EXTEND, - BIND_ACTION_SELECT_EXTEND_CHAR_WISE, - BIND_ACTION_SELECT_WORD, - BIND_ACTION_SELECT_WORD_WS, - BIND_ACTION_SELECT_ROW, - - BIND_ACTION_KEY_COUNT = BIND_ACTION_TEXT_BINDING + 1, - BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, -}; - -enum bind_action_search { - BIND_ACTION_SEARCH_NONE, - BIND_ACTION_SEARCH_CANCEL, - BIND_ACTION_SEARCH_COMMIT, - BIND_ACTION_SEARCH_FIND_PREV, - BIND_ACTION_SEARCH_FIND_NEXT, - BIND_ACTION_SEARCH_EDIT_LEFT, - BIND_ACTION_SEARCH_EDIT_LEFT_WORD, - BIND_ACTION_SEARCH_EDIT_RIGHT, - BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, - BIND_ACTION_SEARCH_EDIT_HOME, - BIND_ACTION_SEARCH_EDIT_END, - BIND_ACTION_SEARCH_DELETE_PREV, - BIND_ACTION_SEARCH_DELETE_PREV_WORD, - BIND_ACTION_SEARCH_DELETE_NEXT, - BIND_ACTION_SEARCH_DELETE_NEXT_WORD, - BIND_ACTION_SEARCH_EXTEND_WORD, - BIND_ACTION_SEARCH_EXTEND_WORD_WS, - BIND_ACTION_SEARCH_CLIPBOARD_PASTE, - BIND_ACTION_SEARCH_PRIMARY_PASTE, - BIND_ACTION_SEARCH_COUNT, -}; - -enum bind_action_url { - BIND_ACTION_URL_NONE, - BIND_ACTION_URL_CANCEL, - BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL, - BIND_ACTION_URL_COUNT, -}; - -typedef tll(xkb_keycode_t) xkb_keycode_list_t; - -struct key_binding { - enum key_binding_type type; - - int action; /* enum bind_action_* */ - xkb_mod_mask_t mods; - - union { - struct { - xkb_keysym_t sym; - xkb_keycode_list_t key_codes; - } k; - - struct { - uint32_t button; - int count; - } m; - }; - - const struct binding_aux *aux; -}; -typedef tll(struct key_binding) key_binding_list_t; - /* Mime-types we support when dealing with data offers (e.g. copy-paste, or DnD) */ enum data_offer_mime_type { DATA_OFFER_MIME_UNSET, @@ -219,12 +121,6 @@ struct seat { bool alt; bool ctrl; bool super; - - struct { - key_binding_list_t key; - key_binding_list_t search; - key_binding_list_t url; - } bindings; } kbd; /* Pointer state */ @@ -260,8 +156,6 @@ struct seat { /* We used a discrete axis event in the current pointer frame */ double aggregated[2]; bool have_discrete; - - key_binding_list_t bindings; } mouse; /* Clipboard */ @@ -465,6 +359,7 @@ struct terminal; struct wayland { const struct config *conf; struct fdm *fdm; + struct key_binding_manager *key_binding_manager; int fd; @@ -500,7 +395,8 @@ struct wayland { tll(struct terminal *) terms; }; -struct wayland *wayl_init(const struct config *conf, struct fdm *fdm); +struct wayland *wayl_init(const struct config *conf, struct fdm *fdm, + struct key_binding_manager *key_binding_manager); void wayl_destroy(struct wayland *wayl); bool wayl_reload_xcursor_theme(struct seat *seat, int new_scale); @@ -523,5 +419,3 @@ bool wayl_win_subsurface_new_with_custom_parent( struct wl_window *win, struct wl_surface *parent, struct wl_surf_subsurf *surf, bool allow_pointer_input); void wayl_win_subsurface_destroy(struct wl_surf_subsurf *surf); - -void wayl_bindings_reset(struct seat *seat);