mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
675 lines
20 KiB
C
675 lines
20 KiB
C
#include "key-binding.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#define LOG_MODULE "key-binding"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "log.h"
|
|
|
|
#include "config.h"
|
|
#include "debug.h"
|
|
#include "terminal.h"
|
|
#include "util.h"
|
|
#include "wayland.h"
|
|
#include "xkbcommon-vmod.h"
|
|
#include "xmalloc.h"
|
|
|
|
struct vmod_map {
|
|
const char *name;
|
|
xkb_mod_mask_t virtual_mask;
|
|
xkb_mod_mask_t real_mask;
|
|
};
|
|
|
|
struct key_set {
|
|
struct key_binding_set public;
|
|
|
|
const struct config *conf;
|
|
const struct seat *seat;
|
|
size_t conf_ref_count;
|
|
|
|
/* Virtual to real modifier mappings */
|
|
struct vmod_map vmods[8];
|
|
};
|
|
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);
|
|
}
|
|
|
|
static void
|
|
initialize_vmod_mappings(struct key_set *set)
|
|
{
|
|
if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL)
|
|
return;
|
|
|
|
set->vmods[0].name = XKB_VMOD_NAME_ALT;
|
|
set->vmods[1].name = XKB_VMOD_NAME_HYPER;
|
|
set->vmods[2].name = XKB_VMOD_NAME_LEVEL3;
|
|
set->vmods[3].name = XKB_VMOD_NAME_LEVEL5;
|
|
set->vmods[4].name = XKB_VMOD_NAME_META;
|
|
set->vmods[5].name = XKB_VMOD_NAME_NUM;
|
|
set->vmods[6].name = XKB_VMOD_NAME_SCROLL;
|
|
set->vmods[7].name = XKB_VMOD_NAME_SUPER;
|
|
|
|
struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap);
|
|
xassert(scratch_state != NULL);
|
|
|
|
for (size_t i = 0; i < ALEN(set->vmods); i++) {
|
|
xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index(
|
|
set->seat->kbd.xkb_keymap, set->vmods[i].name);
|
|
|
|
if (virt_idx != XKB_MOD_INVALID) {
|
|
xkb_mod_mask_t vmask = 1 << virt_idx;
|
|
xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0);
|
|
set->vmods[i].real_mask = xkb_state_serialize_mods(
|
|
scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask;
|
|
set->vmods[i].virtual_mask = vmask;
|
|
|
|
LOG_DBG("%s: 0x%04x -> 0x%04x",
|
|
set->vmods[i].name,
|
|
set->vmods[i].virtual_mask,
|
|
set->vmods[i].real_mask);
|
|
} else {
|
|
set->vmods[i].virtual_mask = 0;
|
|
set->vmods[i].real_mask = 0;
|
|
|
|
LOG_DBG("%s: virtual modifier not available", set->vmods[i].name);
|
|
}
|
|
}
|
|
|
|
xkb_state_unref(scratch_state);
|
|
}
|
|
|
|
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(),
|
|
.vimode = 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);
|
|
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
|
|
|
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_conf(struct key_binding_manager *mgr,
|
|
const struct wayland *wayl, const struct config *conf)
|
|
{
|
|
tll_foreach(wayl->seats, it) {
|
|
struct seat *seat = &it->item;
|
|
|
|
struct key_set *existing =
|
|
(struct key_set *)key_binding_for(mgr, conf, seat);
|
|
|
|
if (existing != NULL) {
|
|
existing->conf_ref_count++;
|
|
continue;
|
|
}
|
|
|
|
struct key_set set = {
|
|
.public = {
|
|
.key = tll_init(),
|
|
.vimode = tll_init(),
|
|
.url = tll_init(),
|
|
.mouse = tll_init(),
|
|
},
|
|
.conf = conf,
|
|
.seat = seat,
|
|
.conf_ref_count = 1,
|
|
};
|
|
|
|
tll_push_back(mgr->binding_sets, set);
|
|
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
|
|
|
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 (conf): set=%p, seat=%p, conf=%p, ref-count=1",
|
|
(void *)&tll_back(mgr->binding_sets),
|
|
(void *)set.seat, (void *)set.conf);
|
|
}
|
|
|
|
LOG_DBG("new (conf): total number of sets: %zu",
|
|
tll_length(mgr->binding_sets));
|
|
}
|
|
|
|
struct key_binding_set * NOINLINE
|
|
key_binding_for(struct key_binding_manager *mgr, const struct config *conf,
|
|
const struct seat *seat)
|
|
{
|
|
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(struct key_binding_manager *mgr, const struct config *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 int
|
|
key_cmp(struct key_binding a, struct key_binding b)
|
|
{
|
|
xassert(a.type == b.type);
|
|
|
|
/*
|
|
* Sort bindings such that bindings with the same symbol are
|
|
* sorted with the binding having the most modifiers comes first.
|
|
*
|
|
* This fixes an issue where the "wrong" key binding are triggered
|
|
* when used with "consumed" modifiers.
|
|
*
|
|
* For example: if Control+BackSpace is bound before
|
|
* Control+Shift+BackSpace, then the latter binding is never
|
|
* triggered.
|
|
*
|
|
* Why? Because Shift is a consumed modifier. This means
|
|
* Control+BackSpace is "the same" as Control+Shift+BackSpace.
|
|
*
|
|
* By sorting bindings with more modifiers first, we work around
|
|
* the problem. But note that it is *just* a workaround, and I'm
|
|
* not confident there aren't cases where it doesn't work.
|
|
*
|
|
* See https://codeberg.org/dnkl/foot/issues/1280
|
|
*/
|
|
|
|
const int a_mod_count = __builtin_popcount(a.mods);
|
|
const int b_mod_count = __builtin_popcount(b.mods);
|
|
|
|
switch (a.type) {
|
|
case KEY_BINDING:
|
|
if (a.k.sym != b.k.sym)
|
|
return b.k.sym - a.k.sym;
|
|
return b_mod_count - a_mod_count;
|
|
|
|
case MOUSE_BINDING: {
|
|
if (a.m.button != b.m.button)
|
|
return b.m.button - a.m.button;
|
|
if (a_mod_count != b_mod_count)
|
|
return b_mod_count - a_mod_count;
|
|
return b.m.count - a.m.count;
|
|
}
|
|
}
|
|
|
|
BUG("invalid key binding type");
|
|
return 0;
|
|
}
|
|
|
|
static void NOINLINE
|
|
sort_binding_list(key_binding_list_t *list)
|
|
{
|
|
tll_sort(*list, key_cmp);
|
|
}
|
|
|
|
static xkb_mod_mask_t
|
|
mods_to_mask(const struct seat *seat,
|
|
const struct vmod_map *vmods, size_t vmod_count,
|
|
const config_modifier_list_t *mods)
|
|
{
|
|
xkb_mod_mask_t mask = 0;
|
|
tll_foreach(*mods, it) {
|
|
const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item);
|
|
|
|
if (idx == XKB_MOD_INVALID) {
|
|
LOG_ERR("%s: invalid modifier name", it->item);
|
|
continue;
|
|
}
|
|
|
|
xkb_mod_mask_t mod = 1 << idx;
|
|
|
|
/* Check if this is a virtual modifier, and if so, use the
|
|
real modifier it maps to instead */
|
|
for (size_t i = 0; i < vmod_count; i++) {
|
|
if (vmods[i].virtual_mask == mod) {
|
|
mask |= vmods[i].real_mask;
|
|
mod = 0;
|
|
|
|
LOG_DBG("%s: virtual modifier, mapped to 0x%04x",
|
|
it->item, vmods[i].real_mask);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mask |= mod;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static void NOINLINE
|
|
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 = mods_to_mask(
|
|
seat, set->vmods, ALEN(set->vmods), &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);
|
|
sort_binding_list(bindings);
|
|
}
|
|
|
|
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_vimode_bindings(struct key_set *set)
|
|
{
|
|
const struct config *conf = set->conf;
|
|
|
|
for (size_t i = 0; i < conf->bindings.vimode.count; i++) {
|
|
const struct config_key_binding *binding = &conf->bindings.vimode.arr[i];
|
|
convert_key_binding(set, binding, &set->public.vimode);
|
|
}
|
|
}
|
|
|
|
static void
|
|
convert_vimode_search_bindings(struct key_set *set)
|
|
{
|
|
const struct config *conf = set->conf;
|
|
|
|
for (size_t i = 0; i < conf->bindings.vimode_search.count; i++) {
|
|
const struct config_key_binding *binding =
|
|
&conf->bindings.vimode_search.arr[i];
|
|
convert_key_binding(set, binding, &set->public.vimode_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 = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers),
|
|
.m = {
|
|
.button = conf_binding->m.button,
|
|
.count = conf_binding->m.count,
|
|
},
|
|
};
|
|
tll_push_back(set->public.mouse, binding);
|
|
sort_binding_list(&set->public.mouse);
|
|
}
|
|
|
|
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 NOINLINE
|
|
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_vimode_bindings(set);
|
|
convert_vimode_search_bindings(set);
|
|
convert_url_bindings(set);
|
|
convert_mouse_bindings(set);
|
|
|
|
set->public.selection_overrides = mods_to_mask(
|
|
set->seat, set->vmods, ALEN(set->vmods),
|
|
&set->conf->mouse.selection_override_modifiers);
|
|
}
|
|
|
|
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) {
|
|
initialize_vmod_mappings(set);
|
|
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 NOINLINE
|
|
unload_keymap(struct key_set *set)
|
|
{
|
|
key_bindings_destroy(&set->public.key);
|
|
key_bindings_destroy(&set->public.vimode);
|
|
key_bindings_destroy(&set->public.vimode_search);
|
|
key_bindings_destroy(&set->public.url);
|
|
key_bindings_destroy(&set->public.mouse);
|
|
set->public.selection_overrides = 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|