search: replace hard-coded key bindings with "user configurable" ones

They aren't really user configurable. At least not yet.

However, with this, we now handle raw key codes just like the normal
key bindings. Meaning, e.g. ctrl+g, ctrl+a, ctrl+e etc now works while
searching with e.g. a russian layout.
This commit is contained in:
Daniel Eklöf 2020-03-18 15:30:14 +01:00
parent a69b818a62
commit 0419156494
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
7 changed files with 238 additions and 177 deletions

View file

@ -455,9 +455,10 @@ parse_section_csd(const char *key, const char *value, struct config *conf,
}
static bool
verify_key_combo(const struct config *conf, const char *combo, const char *path, unsigned lineno)
verify_key_combo(const struct config *conf, const char *combo, const char *path,
unsigned lineno)
{
for (enum binding_action action = 0; action < BIND_ACTION_COUNT; action++) {
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) {
if (conf->bindings.key[action] == NULL)
continue;
@ -481,8 +482,7 @@ verify_key_combo(const struct config *conf, const char *combo, const char *path,
struct xkb_keymap *keymap = xkb_keymap_new_from_names(
ctx, &(struct xkb_rule_names){0}, XKB_KEYMAP_COMPILE_NO_FLAGS);
bool valid_combo = input_parse_key_binding_for_action(
keymap, BIND_ACTION_NONE, combo, NULL);
bool valid_combo = input_parse_key_binding(keymap, combo, NULL);
xkb_keymap_unref(keymap);
xkb_context_unref(ctx);
@ -500,7 +500,7 @@ parse_section_key_bindings(
const char *key, const char *value, struct config *conf,
const char *path, unsigned lineno)
{
for (enum binding_action action = 0; action < BIND_ACTION_COUNT; action++) {
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) {
if (binding_action_map[action] == NULL)
continue;
@ -532,7 +532,7 @@ parse_section_mouse_bindings(
const char *key, const char *value, struct config *conf,
const char *path, unsigned lineno)
{
for (enum binding_action action = 0; action < BIND_ACTION_COUNT; action++) {
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) {
if (binding_action_map[action] == NULL)
continue;
@ -562,7 +562,7 @@ parse_section_mouse_bindings(
const int count = 1;
/* Make sure button isn't already mapped to another action */
for (enum binding_action j = 0; j < BIND_ACTION_COUNT; j++) {
for (enum bind_action_normal j = 0; j < BIND_ACTION_COUNT; j++) {
const struct mouse_binding *collision = &conf->bindings.mouse[j];
if (collision->button == i && collision->count == count) {
LOG_ERR("%s:%d: %s already mapped to %s", path, lineno,
@ -856,6 +856,22 @@ config_load(struct config *conf, const char *conf_path)
[BIND_ACTION_PRIMARY_PASTE] = {BTN_MIDDLE, 1, BIND_ACTION_PRIMARY_PASTE},
},
.search = {
[BIND_ACTION_SEARCH_CANCEL] = strdup("Control+g Escape"),
[BIND_ACTION_SEARCH_COMMIT] = strdup("Return"),
[BIND_ACTION_SEARCH_FIND_PREV] = strdup("Control+r"),
[BIND_ACTION_SEARCH_FIND_NEXT] = strdup("Control+s"),
[BIND_ACTION_SEARCH_EDIT_LEFT] = strdup("Left Control+b"),
[BIND_ACTION_SEARCH_EDIT_LEFT_WORD] = strdup("Control+Left Mod1+b"),
[BIND_ACTION_SEARCH_EDIT_RIGHT] = strdup("Right Control+f"),
[BIND_ACTION_SEARCH_EDIT_RIGHT_WORD] = strdup("Control+Right Mod1+f"),
[BIND_ACTION_SEARCH_EDIT_HOME] = strdup("Home Control+a"),
[BIND_ACTION_SEARCH_EDIT_END] = strdup("End Control+e"),
[BIND_ACTION_SEARCH_DELETE_PREV] = strdup("BackSpace"),
[BIND_ACTION_SEARCH_DELETE_PREV_WORD] = strdup("Mod1+BackSpace Control+BackSpace"),
[BIND_ACTION_SEARCH_DELETE_NEXT] = strdup("Delete "),
[BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = strdup("Mod1+d Control+Delete"),
[BIND_ACTION_SEARCH_EXTEND_WORD] = strdup("Control+w"),
[BIND_ACTION_SEARCH_EXTEND_WORD_WS] = strdup("Control+Shift+W"),
},
},
@ -917,8 +933,8 @@ config_free(struct config conf)
tll_free_and_free(conf.fonts, free);
free(conf.server_socket_path);
for (size_t i = 0; i < BIND_ACTION_COUNT; i++) {
for (enum bind_action_normal i = 0; i < BIND_ACTION_COUNT; i++)
free(conf.bindings.key[i]);
for (enum bind_action_search i = 0; i < BIND_ACTION_SEARCH_COUNT; i++)
free(conf.bindings.search[i]);
}
}

View file

@ -47,7 +47,7 @@ struct config {
/* While searching (not - action to *start* a search is in the
* 'key' bindings above */
char *search[BIND_ACTION_COUNT];
char *search[BIND_ACTION_SEARCH_COUNT];
} bindings;
struct {

64
input.c
View file

@ -33,9 +33,9 @@
#define ALEN(v) (sizeof(v) / sizeof(v[0]))
void
input_execute_binding(struct terminal *term, enum binding_action action,
uint32_t serial)
static void
execute_binding(struct terminal *term, enum bind_action_normal action,
uint32_t serial)
{
switch (action) {
case BIND_ACTION_NONE:
@ -109,14 +109,12 @@ input_execute_binding(struct terminal *term, enum binding_action action,
}
bool
input_parse_key_binding_for_action(
struct xkb_keymap *keymap, enum binding_action action,
const char *combos, key_binding_list_t *bindings)
input_parse_key_binding(struct xkb_keymap *keymap, const char *combos,
key_binding_list_t *bindings)
{
if (combos == NULL)
return true;
xkb_mod_mask_t mod_mask = 0;
xkb_keysym_t sym = XKB_KEY_NoSymbol;
char *copy = strdup(combos);
@ -125,6 +123,7 @@ input_parse_key_binding_for_action(
combo != NULL;
combo = strtok_r(NULL, " ", &save1))
{
xkb_mod_mask_t mod_mask = 0;
xkb_keycode_list_t key_codes = tll_init();
LOG_DBG("%s", combo);
@ -177,7 +176,7 @@ input_parse_key_binding_for_action(
}
}
LOG_DBG("action=%u: mods=0x%08x, sym=%d", action, mod_mask, sym);
LOG_DBG("mods=0x%08x, sym=%d", mod_mask, sym);
if (sym == XKB_KEY_NoSymbol) {
assert(tll_length(key_codes) == 0);
@ -191,7 +190,6 @@ input_parse_key_binding_for_action(
.mods = mod_mask,
.sym = sym,
.key_codes = key_codes,
.action = action,
};
tll_push_back(*bindings, binding);
@ -235,11 +233,11 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
}
tll_foreach(wayl->kbd.bindings.key, it)
tll_free(it->item.key_codes);
tll_free(it->item.bind.key_codes);
tll_free(wayl->kbd.bindings.key);
tll_foreach(wayl->kbd.bindings.search, it)
tll_free(it->item.key_codes);
tll_free(it->item.bind.key_codes);
tll_free(wayl->kbd.bindings.search);
wayl->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
@ -264,14 +262,32 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
munmap(map_str, size);
close(fd);
for (size_t i = 0; i < BIND_ACTION_COUNT; i++) {
input_parse_key_binding_for_action(
wayl->kbd.xkb_keymap, i,
wayl->conf->bindings.key[i], &wayl->kbd.bindings.key);
for (enum bind_action_normal i = 0; i < BIND_ACTION_COUNT; i++) {
key_binding_list_t bindings = tll_init();
input_parse_key_binding(
wayl->kbd.xkb_keymap, wayl->conf->bindings.key[i], &bindings);
input_parse_key_binding_for_action(
wayl->kbd.xkb_keymap, i,
wayl->conf->bindings.search[i], &wayl->kbd.bindings.search);
tll_foreach(bindings, it) {
tll_push_back(
wayl->kbd.bindings.key,
((struct key_binding_normal){.bind = it->item, .action = i}));
}
tll_free(bindings);
}
for (enum bind_action_search i = 0; i < BIND_ACTION_SEARCH_COUNT; i++) {
key_binding_list_t bindings = tll_init();
input_parse_key_binding(
wayl->kbd.xkb_keymap, wayl->conf->bindings.search[i], &bindings);
tll_foreach(bindings, it) {
tll_push_back(
wayl->kbd.bindings.search,
((struct key_binding_search){.bind = it->item, .action = i}));
}
tll_free(bindings);
}
}
@ -558,19 +574,19 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
* User configurable bindings
*/
tll_foreach(wayl->kbd.bindings.key, it) {
if (it->item.mods != effective_mods)
if (it->item.bind.mods != effective_mods)
continue;
/* Match symbol */
if (it->item.sym == sym) {
input_execute_binding(term, it->item.action, serial);
if (it->item.bind.sym == sym) {
execute_binding(term, it->item.action, serial);
goto maybe_repeat;
}
/* Match raw key code */
tll_foreach(it->item.key_codes, code) {
tll_foreach(it->item.bind.key_codes, code) {
if (code->item == key) {
input_execute_binding(term, it->item.action, serial);
execute_binding(term, it->item.action, serial);
goto maybe_repeat;
}
}
@ -1192,7 +1208,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
continue;
}
input_execute_binding(term, binding->action, serial);
execute_binding(term, binding->action, serial);
break;
}
selection_cancel(term);

View file

@ -10,9 +10,5 @@ extern const struct wl_pointer_listener pointer_listener;
void input_repeat(struct wayland *wayl, uint32_t key);
bool input_parse_key_binding_for_action(
struct xkb_keymap *keymap, enum binding_action action,
const char *combos, key_binding_list_t *bindings);
void input_execute_binding(
struct terminal *term, enum binding_action action, uint32_t serial);
bool input_parse_key_binding(struct xkb_keymap *keymap, const char *combos,
key_binding_list_t *bindings);

248
search.c
View file

@ -412,63 +412,29 @@ distance_prev_word(const struct terminal *term)
return term->search.cursor - cursor;
}
void
search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
xkb_mod_mask_t mods, uint32_t serial)
static bool
execute_binding(struct terminal *term, enum bind_action_search action,
uint32_t serial)
{
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods);
switch (action) {
case BIND_ACTION_SEARCH_NONE:
return false;
const xkb_mod_mask_t ctrl = 1 << term->wl->kbd.mod_ctrl;
const xkb_mod_mask_t alt = 1 << term->wl->kbd.mod_alt;
const xkb_mod_mask_t shift = 1 << term->wl->kbd.mod_shift;
//const xkb_mod_mask_t meta = 1 << term->wl->kbd.mod_meta;
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
term->wl->kbd.xkb_compose_state);
/*
* User configurable bindings
*/
tll_foreach(term->wl->kbd.bindings.search, it) {
if (it->item.mods != mods)
continue;
/* Match symbol */
if (it->item.sym == sym) {
input_execute_binding(term, it->item.action, serial);
return;
}
/* Match raw key code */
tll_foreach(it->item.key_codes, code) {
if (code->item == key) {
input_execute_binding(term, it->item.action, serial);
return;
}
}
}
/* Cancel search */
if ((mods == 0 && sym == XKB_KEY_Escape) ||
(mods == ctrl && sym == XKB_KEY_g))
{
case BIND_ACTION_SEARCH_CANCEL:
if (term->search.view_followed_offset)
term->grid->view = term->grid->offset;
else
term->grid->view = term->search.original_view;
term_damage_view(term);
search_cancel(term);
return;
}
return true;
/* "Commit" search - copy selection to primary and cancel search */
else if (mods == 0 && sym == XKB_KEY_Return) {
case BIND_ACTION_SEARCH_COMMIT:
selection_finalize(term, term->wl->input_serial);
search_cancel_keep_selection(term);
return;
}
return true;
else if (mods == ctrl && sym == XKB_KEY_r) {
case BIND_ACTION_SEARCH_FIND_PREV:
if (term->search.match_len > 0) {
int new_col = term->search.match.col - 1;
int new_row = term->search.match.row;
@ -483,9 +449,9 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
term->search.match.row = new_row;
}
}
}
return false;
else if (mods == ctrl && sym == XKB_KEY_s) {
case BIND_ACTION_SEARCH_FIND_NEXT:
if (term->search.match_len > 0) {
int new_col = term->search.match.col + 1;
int new_row = term->search.match.row;
@ -501,49 +467,43 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
term->search.direction = SEARCH_FORWARD;
}
}
}
return false;
else if ((mods == 0 && sym == XKB_KEY_Left) ||
(mods == ctrl && sym == XKB_KEY_b))
{
case BIND_ACTION_SEARCH_EDIT_LEFT:
if (term->search.cursor > 0)
term->search.cursor--;
}
return false;
else if ((mods == ctrl && sym == XKB_KEY_Left) ||
(mods == alt && sym == XKB_KEY_b))
{
case BIND_ACTION_SEARCH_EDIT_LEFT_WORD: {
size_t diff = distance_prev_word(term);
term->search.cursor -= diff;
assert(term->search.cursor >= 0);
assert(term->search.cursor <= term->search.len);
return false;
}
else if ((mods == 0 && sym == XKB_KEY_Right) ||
(mods == ctrl && sym == XKB_KEY_f))
{
case BIND_ACTION_SEARCH_EDIT_RIGHT:
if (term->search.cursor < term->search.len)
term->search.cursor++;
}
return false;
else if ((mods == ctrl && sym == XKB_KEY_Right) ||
(mods == alt && sym == XKB_KEY_f))
{
case BIND_ACTION_SEARCH_EDIT_RIGHT_WORD: {
size_t diff = distance_next_word(term);
term->search.cursor += diff;
assert(term->search.cursor >= 0);
assert(term->search.cursor <= term->search.len);
return false;
}
else if ((mods == 0 && sym == XKB_KEY_Home) ||
(mods == ctrl && sym == XKB_KEY_a))
case BIND_ACTION_SEARCH_EDIT_HOME:
term->search.cursor = 0;
return false;
else if ((mods == 0 && sym == XKB_KEY_End) ||
(mods == ctrl && sym == XKB_KEY_e))
case BIND_ACTION_SEARCH_EDIT_END:
term->search.cursor = term->search.len;
return false;
else if (mods == 0 && sym == XKB_KEY_BackSpace) {
case BIND_ACTION_SEARCH_DELETE_PREV:
if (term->search.cursor > 0) {
memmove(
&term->search.buf[term->search.cursor - 1],
@ -552,9 +512,9 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
term->search.cursor--;
term->search.buf[--term->search.len] = L'\0';
}
}
return false;
else if ((mods == alt || mods == ctrl) && sym == XKB_KEY_BackSpace) {
case BIND_ACTION_SEARCH_DELETE_PREV_WORD: {
size_t diff = distance_prev_word(term);
size_t old_cursor = term->search.cursor;
size_t new_cursor = old_cursor - diff;
@ -565,10 +525,20 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
term->search.len -= diff;
term->search.cursor = new_cursor;
return false;
}
else if ((mods == alt && sym == XKB_KEY_d) ||
(mods == ctrl && sym == XKB_KEY_Delete)) {
case BIND_ACTION_SEARCH_DELETE_NEXT:
if (term->search.cursor < term->search.len) {
memmove(
&term->search.buf[term->search.cursor],
&term->search.buf[term->search.cursor + 1],
(term->search.len - term->search.cursor - 1) * sizeof(wchar_t));
term->search.buf[--term->search.len] = L'\0';
}
return false;
case BIND_ACTION_SEARCH_DELETE_NEXT_WORD: {
size_t diff = distance_next_word(term);
size_t cursor = term->search.cursor;
@ -577,66 +547,98 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
(term->search.len - (cursor + diff)) * sizeof(wchar_t));
term->search.len -= diff;
return false;
}
else if (mods == 0 && sym == XKB_KEY_Delete) {
if (term->search.cursor < term->search.len) {
memmove(
&term->search.buf[term->search.cursor],
&term->search.buf[term->search.cursor + 1],
(term->search.len - term->search.cursor - 1) * sizeof(wchar_t));
term->search.buf[--term->search.len] = L'\0';
}
}
else if (mods == ctrl && sym == XKB_KEY_w)
case BIND_ACTION_SEARCH_EXTEND_WORD:
search_match_to_end_of_word(term, false);
return false;
else if (mods == (ctrl | shift) && sym == XKB_KEY_W)
case BIND_ACTION_SEARCH_EXTEND_WORD_WS:
search_match_to_end_of_word(term, true);
return false;
else {
uint8_t buf[64] = {0};
int count = 0;
if (compose_status == XKB_COMPOSE_COMPOSED) {
count = xkb_compose_state_get_utf8(
term->wl->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
xkb_compose_state_reset(term->wl->kbd.xkb_compose_state);
} else if (compose_status == XKB_COMPOSE_CANCELLED) {
count = 0;
} else {
count = xkb_state_key_get_utf8(
term->wl->kbd.xkb_state, key, (char *)buf, sizeof(buf));
}
const char *src = (const char *)buf;
mbstate_t ps = {0};
size_t wchars = mbsnrtowcs(NULL, &src, count, 0, &ps);
if (wchars == -1) {
LOG_ERRNO("failed to convert %.*s to wchars", count, buf);
return;
}
if (!search_ensure_size(term, term->search.len + wchars))
return;
assert(term->search.len + wchars < term->search.sz);
memmove(&term->search.buf[term->search.cursor + wchars],
&term->search.buf[term->search.cursor],
(term->search.len - term->search.cursor) * sizeof(wchar_t));
memset(&ps, 0, sizeof(ps));
mbsnrtowcs(&term->search.buf[term->search.cursor], &src, count,
wchars, &ps);
term->search.len += wchars;
term->search.cursor += wchars;
term->search.buf[term->search.len] = L'\0';
case BIND_ACTION_SEARCH_COUNT:
assert(false);
return false;
}
assert(false);
return false;
}
void
search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym,
xkb_mod_mask_t mods, uint32_t serial)
{
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods);
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
term->wl->kbd.xkb_compose_state);
/* Key bindings */
tll_foreach(term->wl->kbd.bindings.search, it) {
if (it->item.bind.mods != mods)
continue;
/* Match symbol */
if (it->item.bind.sym == sym) {
if (!execute_binding(term, it->item.action, serial))
goto update_search;
return;
}
/* Match raw key code */
tll_foreach(it->item.bind.key_codes, code) {
if (code->item == key) {
if (!execute_binding(term, it->item.action, serial))
goto update_search;
return;
}
}
}
uint8_t buf[64] = {0};
int count = 0;
if (compose_status == XKB_COMPOSE_COMPOSED) {
count = xkb_compose_state_get_utf8(
term->wl->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
xkb_compose_state_reset(term->wl->kbd.xkb_compose_state);
} else if (compose_status == XKB_COMPOSE_CANCELLED) {
count = 0;
} else {
count = xkb_state_key_get_utf8(
term->wl->kbd.xkb_state, key, (char *)buf, sizeof(buf));
}
const char *src = (const char *)buf;
mbstate_t ps = {0};
size_t wchars = mbsnrtowcs(NULL, &src, count, 0, &ps);
if (wchars == -1) {
LOG_ERRNO("failed to convert %.*s to wchars", count, buf);
return;
}
if (!search_ensure_size(term, term->search.len + wchars))
return;
assert(term->search.len + wchars < term->search.sz);
memmove(&term->search.buf[term->search.cursor + wchars],
&term->search.buf[term->search.cursor],
(term->search.len - term->search.cursor) * sizeof(wchar_t));
memset(&ps, 0, sizeof(ps));
mbsnrtowcs(&term->search.buf[term->search.cursor], &src, count,
wchars, &ps);
term->search.len += wchars;
term->search.cursor += wchars;
term->search.buf[term->search.len] = L'\0';
update_search:
LOG_DBG("search: buffer: %S", term->search.buf);
search_find_next(term);
render_refresh_search(term);

View file

@ -924,11 +924,11 @@ wayl_destroy(struct wayland *wayl)
wp_presentation_destroy(wayl->presentation);
tll_foreach(wayl->kbd.bindings.key, it)
tll_free(it->item.key_codes);
tll_free(it->item.bind.key_codes);
tll_free(wayl->kbd.bindings.key);
tll_foreach(wayl->kbd.bindings.search, it)
tll_free(it->item.key_codes);
tll_free(it->item.bind.key_codes);
tll_free(wayl->kbd.bindings.search);
if (wayl->kbd.xkb_compose_state != NULL)

View file

@ -66,7 +66,17 @@ struct monitor {
float inch; /* e.g. 24" */
};
enum binding_action {
typedef tll(xkb_keycode_t) xkb_keycode_list_t;
struct key_binding {
xkb_mod_mask_t mods;
xkb_keysym_t sym;
xkb_keycode_list_t key_codes;
};
typedef tll(struct key_binding) key_binding_list_t;
enum bind_action_normal {
BIND_ACTION_NONE,
BIND_ACTION_SCROLLBACK_UP,
BIND_ACTION_SCROLLBACK_DOWN,
@ -84,20 +94,41 @@ enum binding_action {
BIND_ACTION_COUNT,
};
typedef tll(xkb_keycode_t) xkb_keycode_list_t;
struct key_binding {
xkb_mod_mask_t mods;
xkb_keysym_t sym;
xkb_keycode_list_t key_codes;
enum binding_action action;
struct key_binding_normal {
struct key_binding bind;
enum bind_action_normal action;
};
typedef tll(struct key_binding) key_binding_list_t;
struct mouse_binding {
uint32_t button;
int count;
enum binding_action action;
enum bind_action_normal action;
};
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_COUNT,
};
struct key_binding_search {
struct key_binding bind;
enum bind_action_search action;
};
struct kbd {
@ -127,8 +158,8 @@ struct kbd {
bool meta;
struct {
key_binding_list_t key;
key_binding_list_t search;
tll(struct key_binding_normal) key;
tll(struct key_binding_search) search;
} bindings;
};