diff --git a/CHANGELOG.md b/CHANGELOG.md index 642dc0c8..b2f64473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased ### Added +* User configurable key- and mouse bindings. See `man 5 foot` and the + example `footrc` (https://codeberg.org/dnkl/foot/issues/1) ### Changed * Changed icon name in `foot.desktop` and `foot-server.desktop` from diff --git a/config.c b/config.c index 912ac75a..dacd2b28 100644 --- a/config.c +++ b/config.c @@ -12,9 +12,16 @@ #include #include +#include +#include + #define LOG_MODULE "config" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "input.h" +#include "wayland.h" + +#define ALEN(v) (sizeof(v) / sizeof(v[0])) static const uint32_t default_foreground = 0xdcdccc; static const uint32_t default_background = 0x111111; @@ -41,6 +48,26 @@ static const uint32_t default_bright[] = { 0xffffff, }; +static const char *binding_action_map[] = { + [BIND_ACTION_NONE] = NULL, + [BIND_ACTION_SCROLLBACK_UP] = "scrollback-up", + [BIND_ACTION_SCROLLBACK_DOWN] = "scrollback-down", + [BIND_ACTION_CLIPBOARD_COPY] = "clipboard-copy", + [BIND_ACTION_CLIPBOARD_PASTE] = "clipboard-paste", + [BIND_ACTION_PRIMARY_PASTE] = "primary-paste", + [BIND_ACTION_SEARCH_START] = "search-start", + [BIND_ACTION_FONT_SIZE_UP] = "font-increase", + [BIND_ACTION_FONT_SIZE_DOWN] = "font-decrease", + [BIND_ACTION_FONT_SIZE_RESET] = "font-reset", + [BIND_ACTION_SPAWN_TERMINAL] = "spawn-terminal", + [BIND_ACTION_MINIMIZE] = "minimize", + [BIND_ACTION_MAXIMIZE] = "maximize", + [BIND_ACTION_FULLSCREEN] = "fullscreen", +}; + +static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT, + "binding action map size mismatch"); + static char * get_shell(void) { @@ -238,7 +265,7 @@ parse_section_main(const char *key, const char *value, struct config *conf, } else { - LOG_WARN("%s:%u: invalid key: %s", path, lineno, key); + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); return false; } @@ -420,13 +447,143 @@ parse_section_csd(const char *key, const char *value, struct config *conf, } else { - LOG_WARN("%s:%u: invalid key: %s", path, lineno, key); + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); return false; } return true; } +static bool +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++) { + if (conf->bindings.key[action] == NULL) + continue; + + char *copy = strdup(conf->bindings.key[action]); + + for (char *save = NULL, *collision = strtok_r(copy, " ", &save); + collision != NULL; + collision = strtok_r(NULL, " ", &save)) + { + if (strcmp(combo, collision) == 0) { + LOG_ERR("%s:%d: %s already mapped to %s", path, lineno, combo, binding_action_map[action]); + free(copy); + return false; + } + } + + free(copy); + } + + struct xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + 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); + + xkb_keymap_unref(keymap); + xkb_context_unref(ctx); + + if (!valid_combo) { + LOG_ERR("%s:%d: invalid key combination: %s", path, lineno, combo); + return false; + } + + return true; +} + +static bool +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++) { + if (binding_action_map[action] == NULL) + continue; + + if (strcmp(key, binding_action_map[action]) != 0) + continue; + + if (strcmp(value, "NONE") == 0) { + free(conf->bindings.key[action]); + conf->bindings.key[action] = NULL; + return true; + } + + if (!verify_key_combo(conf, value, path, lineno)) { + return false; + } + + free(conf->bindings.key[action]); + conf->bindings.key[action] = strdup(value); + return true; + } + + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); + return false; + +} + +static bool +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++) { + if (binding_action_map[action] == NULL) + continue; + + if (strcmp(key, binding_action_map[action]) != 0) + continue; + + if (strcmp(value, "NONE") == 0) { + conf->bindings.mouse[action] = (struct mouse_binding){0, 0, BIND_ACTION_NONE}; + return true; + } + + const char *map[] = { + [BTN_LEFT] = "BTN_LEFT", + [BTN_RIGHT] = "BTN_RIGHT", + [BTN_MIDDLE] = "BTN_MIDDLE", + [BTN_SIDE] = "BTN_SIDE", + [BTN_EXTRA] = "BTN_EXTRA", + [BTN_FORWARD] = "BTN_FORWARD", + [BTN_BACK] = "BTN_BACK", + [BTN_TASK] = "BTN_TASK", + }; + + for (size_t i = 0; i < ALEN(map); i++) { + if (map[i] == NULL || strcmp(map[i], value) != 0) + continue; + + 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++) { + 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, + value, binding_action_map[collision->action]); + return false; + } + } + + conf->bindings.mouse[action] = (struct mouse_binding){i, count, action}; + return true; + } + + LOG_ERR("%s:%d: invalid mouse button: %s", path, lineno, value); + return false; + + } + + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); + return false; +} + static bool parse_config_file(FILE *f, struct config *conf, const char *path) { @@ -435,6 +592,9 @@ parse_config_file(FILE *f, struct config *conf, const char *path) SECTION_COLORS, SECTION_CURSOR, SECTION_CSD, + SECTION_KEY_BINDINGS, + SECTION_MOUSE_BINDINGS, + SECTION_COUNT, } section = SECTION_MAIN; /* Function pointer, called for each key/value line */ @@ -442,22 +602,19 @@ parse_config_file(FILE *f, struct config *conf, const char *path) const char *key, const char *value, struct config *conf, const char *path, unsigned lineno); - /* Maps sections to line parser functions */ - static const parser_fun_t section_parser_map[] = { - [SECTION_MAIN] = &parse_section_main, - [SECTION_COLORS] = &parse_section_colors, - [SECTION_CURSOR] = &parse_section_cursor, - [SECTION_CSD] = &parse_section_csd, + static const struct { + parser_fun_t fun; + const char *name; + } section_info[] = { + [SECTION_MAIN] = {&parse_section_main, "main"}, + [SECTION_COLORS] = {&parse_section_colors, "colors"}, + [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, + [SECTION_CSD] = {&parse_section_csd, "csd"}, + [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"}, + [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, }; -#if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG - static const char *const section_names[] = { - [SECTION_MAIN] = "main", - [SECTION_COLORS] = "colors", - [SECTION_CURSOR] = "cursor", - [SECTION_CSD] = "csd", - }; -#endif + static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); unsigned lineno = 0; @@ -505,17 +662,19 @@ parse_config_file(FILE *f, struct config *conf, const char *path) *end = '\0'; - if (strcmp(&line[1], "colors") == 0) - section = SECTION_COLORS; - else if (strcmp(&line[1], "cursor") == 0) - section = SECTION_CURSOR; - else if (strcmp(&line[1], "csd") == 0) - section = SECTION_CSD; - else { + section = SECTION_COUNT; + for (enum section i = 0; i < SECTION_COUNT; i++) { + if (strcmp(&line[1], section_info[i].name) == 0) { + section = i; + } + } + + if (section == SECTION_COUNT) { LOG_ERR("%s:%d: invalid section name: %s", path, lineno, &line[1]); goto err; } + /* Process next line */ continue; } @@ -554,7 +713,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) LOG_DBG("section=%s, key='%s', value='%s'", section_names[section], key, value); - parser_fun_t section_parser = section_parser_map[section]; + parser_fun_t section_parser = section_info[section].fun; assert(section_parser != NULL); if (!section_parser(key, value, conf, path, lineno)) @@ -634,6 +793,25 @@ config_load(struct config *conf, const char *conf_path) }, }, + .bindings = { + .key = { + [BIND_ACTION_SCROLLBACK_UP] = strdup("Shift+Page_Up"), + [BIND_ACTION_SCROLLBACK_DOWN] = strdup("Shift+Page_Down"), + [BIND_ACTION_CLIPBOARD_COPY] = strdup("Control+Shift+C"), + [BIND_ACTION_CLIPBOARD_PASTE] = strdup("Control+Shift+V"), + [BIND_ACTION_SEARCH_START] = strdup("Control+Shift+R"), + [BIND_ACTION_FONT_SIZE_UP] = strdup("Control+plus Control+equal Control+KP_Add"), + [BIND_ACTION_FONT_SIZE_DOWN] = strdup("Control+minus Control+KP_Subtract"), + [BIND_ACTION_FONT_SIZE_RESET] = strdup("Control+0 Control+KP_0"), + [BIND_ACTION_SPAWN_TERMINAL] = strdup("Control+Shift+Return"), + }, + .mouse = { + [BIND_ACTION_PRIMARY_PASTE] = {BTN_MIDDLE, 1, BIND_ACTION_PRIMARY_PASTE}, + }, + .search = { + }, + }, + .csd = { .preferred = CONF_CSD_PREFER_SERVER, .title_height = 26, @@ -686,4 +864,9 @@ config_free(struct config conf) free(conf.shell); tll_free_and_free(conf.fonts, free); free(conf.server_socket_path); + + for (size_t i = 0; i < BIND_ACTION_COUNT; i++) { + free(conf.bindings.key[i]); + free(conf.bindings.search[i]); + } } diff --git a/config.h b/config.h index a89637e1..bf205305 100644 --- a/config.h +++ b/config.h @@ -36,6 +36,20 @@ struct config { } color; } cursor; + struct { + /* Bindings for "normal" mode */ + char *key[BIND_ACTION_COUNT]; + struct mouse_binding mouse[BIND_ACTION_COUNT]; + + /* + * Special modes + */ + + /* While searching (not - action to *start* a search is in the + * 'key' bindings above */ + char *search[BIND_ACTION_COUNT]; + } bindings; + struct { enum { CONF_CSD_PREFER_SERVER, CONF_CSD_PREFER_CLIENT } preferred; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 5b3c07cc..cb060bb4 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -98,11 +98,13 @@ arguments, use *--*: # KEYBOARD SHORTCUTS -The following keyboard shortcuts are available. Note that they cannot -be changed. +The following keyboard shortcuts are available. ## NORMAL MODE +Note that these are just the defaults; they can be changed in the +*footrc*, see *foot*(5). + *shift*+*page up*/*page down* Scroll up/down in history diff --git a/doc/foot.5.scd b/doc/foot.5.scd index edfecc27..f504fba5 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -147,6 +147,89 @@ component. *button-close-color* Close button's AARRGGBB color. Default: _ffff3030_. +# SECTION: key-bindings + +This section lets you override the default key bindings. + +The general format is _action_=_combo1_..._comboN_. That is, each +action may have one or more key combinations, space separated. Each +combination is on the form _mod1_+_mod2_+_key_. The names of the +modifiers and the key *must* be a valid XKB key name. + +Note that if *Shift* is one of the modifiers, the _key_ *must* be in +upper case. For example, *Control*+*Shift*+*v* will never trigger - +*Control*+*Shift*+*V* is the correct way to write it. + +Note that *Alt* is usually called *Mod1*. + +A key combination can only be mapped to *one* action. Lets say you +want to bind *Control*+*Shift*+*R* to *fullscreen*. Since this is the +default shortcut for *search-start*, you first need to unmap the +default binding. This can be done by setting _action_=*NONE*; +e.g. *search-start*=*NONE*. + +*scrollback-up* + Scrolls up/back in history. Default: _Shift+Page_Up_. + +*scrollback-down* + Scroll down/forward in history. Default: _Shift+Page_Down_. + +*clipboard-copy* + Copies the current selection into the _clipboard_. Default: _Control+Shift+C_. + +*clipboard-paste* + Pastes from the _clipboard_. Default: _Control+Shift+V_. + +*primary-paste* + Pastes from the _primary selection_. Default: not bound (to a + _key_, see *mouse-bindings*). + +*search-start* + Starts a scrollback/history search. Default: _Control+Shift+R_. + +*font-increase* + Increases the font size by 0.5pt. Default: _Control+plus + Control+equal Control+KP\_Add_. + +*font-decrease* + Decreases the font size by 0.5pt. Default: _Control+minus + Control+KP\_Subtract_. + +*font-reset* + Resets the font size to the default. Default: _Control+0 Control+KP\_0_. + +*spawn-terminal* + Spawns a new terminal. Default: _Control+Shift+Return_. + +*minimize* + Minimizes the window. Default: _not bound_. + +*maximize* + Toggle the maximized state. Default: _not bound_. + +*fullscreen* + Toggles the fullscreen state. Default: _not bound_. + +# SECTION: mouse-bindings + +This section lets you override the default mouse bindings. + +The general format is _action_=_BTN\__, where _BTN\__ is +the name of the event code (e.g. *BTN\_LEFT*, *BTN\_RIGHT*). You can +find the event names using *libinput debug-events*. + +A button can only be mapped to *one* action. Lets say you want to bind +*BTN\_MIDDLE* to *fullscreen*. Since *BTN\_MIDDLE* is the default +binding for *primary-paste*, you first need to unmap the default +binding. This can be done by setting _action_=*NONE*; +e.g. *primary-paste*=*NONE*. + +All actions listed under *key-bindings* can be user here as well. + +*primary-paste* + Pastes from the _primary selection_. Default: _BTN_MIDDLE_. + + # FONT FORMAT The font is specified in FontConfig syntax. That is, a colon-separated diff --git a/footrc b/footrc index 7cac547d..73512474 100644 --- a/footrc +++ b/footrc @@ -42,3 +42,20 @@ # button-minimize-color=ff0000ff # button-maximize-color=ff00ff00 # button-close-color=ffff0000 + +[key-bindings] +# scrollback-up=Shift+Page_Up +# scrollback-down=Shift+Page_Down +# clipboard-copy=Control+Shift+C +# clipboard-paste=Control+Shift+V +# search-start=Control+Shift+R +# font-increase=Control+plus Control+equal Control+KP_Add +# font-decrease=Control+minus Control+KP_Subtract +# font-reset=Control+0 Control+KP_0 +# spawn-terminal=Control+Shift+Return +# # minimize= +# # maximize= +# # fullscreen= + +[mouse-bindings] +# primary-paste=BTN_MIDDLE diff --git a/input.c b/input.c index efa58c9d..1c80dd8b 100644 --- a/input.c +++ b/input.c @@ -31,6 +31,140 @@ #include "terminal.h" #include "vt.h" +#define ALEN(v) (sizeof(v) / sizeof(v[0])) + +void +input_execute_binding(struct terminal *term, enum binding_action action, + uint32_t serial) +{ + switch (action) { + case BIND_ACTION_NONE: + break; + + case BIND_ACTION_SCROLLBACK_UP: + cmd_scrollback_up(term, term->rows); + break; + + case BIND_ACTION_SCROLLBACK_DOWN: + cmd_scrollback_down(term, term->rows); + break; + + case BIND_ACTION_CLIPBOARD_COPY: + selection_to_clipboard(term, serial); + break; + + case BIND_ACTION_CLIPBOARD_PASTE: + selection_from_clipboard(term, serial); + term_reset_view(term); + break; + + case BIND_ACTION_PRIMARY_PASTE: + selection_from_primary(term); + break; + + case BIND_ACTION_SEARCH_START: + search_begin(term); + break; + + case BIND_ACTION_FONT_SIZE_UP: + term_font_size_increase(term); + break; + + case BIND_ACTION_FONT_SIZE_DOWN: + term_font_size_decrease(term); + break; + + case BIND_ACTION_FONT_SIZE_RESET: + term_font_size_reset(term); + break; + + case BIND_ACTION_SPAWN_TERMINAL: + term_spawn_new(term); + break; + + case BIND_ACTION_MINIMIZE: + xdg_toplevel_set_minimized(term->window->xdg_toplevel); + break; + + case BIND_ACTION_MAXIMIZE: + if (term->window->is_maximized) + xdg_toplevel_unset_maximized(term->window->xdg_toplevel); + else + xdg_toplevel_set_maximized(term->window->xdg_toplevel); + break; + + case BIND_ACTION_FULLSCREEN: + if (term->window->is_fullscreen) + xdg_toplevel_unset_fullscreen(term->window->xdg_toplevel); + else + xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL); + break; + + case BIND_ACTION_COUNT: + assert(false); + break; + } +} + +bool +input_parse_key_binding_for_action( + struct xkb_keymap *keymap, enum binding_action action, + const char *combos, key_binding_list_t *bindings) +{ + if (combos == NULL) + return true; + + xkb_mod_mask_t mod_mask = 0; + xkb_keysym_t sym = 0; + + char *copy = strdup(combos); + + for (char *save1 = NULL, *combo = strtok_r(copy, " ", &save1); + combo != NULL; + combo = strtok_r(NULL, " ", &save1)) + { + LOG_DBG("%s", combo); + for (char *save2 = NULL, *key = strtok_r(combo, "+", &save2), + *next_key = strtok_r(NULL, "+", &save2); + key != NULL; + key = next_key, next_key = strtok_r(NULL, "+", &save2)) + { + if (next_key != NULL) { + /* Modifier */ + xkb_mod_index_t mod = xkb_keymap_mod_get_index(keymap, key); + + if (mod == -1) { + LOG_ERR("%s: not a valid modifier name", key); + free(copy); + return false; + } + + mod_mask |= 1 << mod; + LOG_DBG("MOD: %d - %s", mod, key); + } else { + /* Symbol */ + sym = xkb_keysym_from_name(key, 0); + } + } + + LOG_DBG("action=%u: mods=0x%08x, sym=%d", action, mod_mask, sym); + + assert(sym != 0); + if (bindings != NULL) { + const struct key_binding binding = { + .mods = mod_mask, + .sym = sym, + .action = action, + }; + + tll_push_back(*bindings, binding); + } + } + + free(copy); + return true; +} + static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) @@ -59,6 +193,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, xkb_context_unref(wayl->kbd.xkb); wayl->kbd.xkb = NULL; } + tll_free(wayl->kbd.bindings.key); wayl->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wayl->kbd.xkb_keymap = xkb_keymap_new_from_string( @@ -81,6 +216,16 @@ 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); + + input_parse_key_binding_for_action( + wayl->kbd.xkb_keymap, i, + wayl->conf->bindings.search[i], &wayl->kbd.bindings.search); + } } static void @@ -180,7 +325,6 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, static const struct key_data * keymap_data_for_sym(xkb_keysym_t sym, size_t *count) { -#define ALEN(a) (sizeof(a) / sizeof(a[0])) switch (sym) { case XKB_KEY_Escape: *count = ALEN(key_escape); return key_escape; case XKB_KEY_Return: *count = ALEN(key_return); return key_return; @@ -261,7 +405,6 @@ keymap_data_for_sym(xkb_keysym_t sym, size_t *count) case XKB_KEY_KP_9: *count = ALEN(key_kp_9); return key_kp_9; } - #undef ALEN return NULL; } @@ -358,7 +501,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, if (term->is_searching) { if (should_repeat) start_repeater(wayl, key - 8); - search_input(term, key, sym, effective_mods); + search_input(term, key, sym, effective_mods, serial); return; } @@ -375,57 +518,11 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, sym, mods, consumed, significant, effective_mods, should_repeat); /* - * Builtin shortcuts + * User configurable bindings */ - - if (effective_mods == shift) { - if (sym == XKB_KEY_Page_Up) { - cmd_scrollback_up(term, term->rows); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_Page_Down) { - cmd_scrollback_down(term, term->rows); - goto maybe_repeat; - } - } - - else if (effective_mods == ctrl) { - if (sym == XKB_KEY_equal || sym == XKB_KEY_plus || sym == XKB_KEY_KP_Add) { - term_font_size_increase(term); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_minus || sym == XKB_KEY_KP_Subtract) { - term_font_size_decrease(term); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_0 || sym == XKB_KEY_KP_Equal || sym == XKB_KEY_KP_0) { - term_font_size_reset(term); - goto maybe_repeat; - } - } - - else if (effective_mods == (shift | ctrl)) { - if (sym == XKB_KEY_C) { - selection_to_clipboard(term, serial); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_V) { - selection_from_clipboard(term, serial); - term_reset_view(term); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_R) { - search_begin(term); - goto maybe_repeat; - } - - else if (sym == XKB_KEY_Return) { - term_spawn_new(term); + tll_foreach(wayl->kbd.bindings.key, it) { + if (it->item.mods == effective_mods && it->item.sym == sym) { + input_execute_binding(term, it->item.action, serial); goto maybe_repeat; } } @@ -1056,11 +1153,25 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, selection_mark_row(term, wayl->mouse.row, serial); break; } - } else { - if (wayl->mouse.count == 1 && button == BTN_MIDDLE && - selection_enabled(term)) - { - selection_from_primary(term); + } + + else { + for (size_t i = 0; i < ALEN(wayl->conf->bindings.mouse); i++) { + const struct mouse_binding *binding = + &wayl->conf->bindings.mouse[i]; + + if (binding->button != button) { + /* Wrong button */ + continue; + } + + if (binding->count != wayl->mouse.count) { + /* Not correct click count */ + continue; + } + + input_execute_binding(term, binding->action, serial); + break; } selection_cancel(term); } diff --git a/input.h b/input.h index 2483389a..9c274964 100644 --- a/input.h +++ b/input.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "wayland.h" @@ -8,3 +9,10 @@ extern const struct wl_keyboard_listener keyboard_listener; 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); diff --git a/main.c b/main.c index 0e22d0d8..fbc318a6 100644 --- a/main.c +++ b/main.c @@ -273,8 +273,10 @@ main(int argc, char *const *argv) argv += optind; struct config conf = {NULL}; - if (!config_load(&conf, conf_path)) + if (!config_load(&conf, conf_path)) { + config_free(conf); return ret; + } setlocale(LC_ALL, ""); if (!locale_is_utf8()) { diff --git a/search.c b/search.c index 2c58f3f2..98ad64eb 100644 --- a/search.c +++ b/search.c @@ -11,6 +11,7 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "grid.h" +#include "input.h" #include "misc.h" #include "render.h" #include "selection.h" @@ -412,7 +413,8 @@ distance_prev_word(const struct terminal *term) } void -search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods) +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); @@ -424,6 +426,16 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask 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 && it->item.sym == sym) { + input_execute_binding(term, it->item.action, serial); + return; + } + } + /* Cancel search */ if ((mods == 0 && sym == XKB_KEY_Escape) || (mods == ctrl && sym == XKB_KEY_g)) diff --git a/search.h b/search.h index 4ea59c1f..69930c07 100644 --- a/search.h +++ b/search.h @@ -5,4 +5,5 @@ void search_begin(struct terminal *term); void search_cancel(struct terminal *term); -void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods); +void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods, + uint32_t serial); diff --git a/wayland.c b/wayland.c index 322188ae..1e351caf 100644 --- a/wayland.c +++ b/wayland.c @@ -911,6 +911,8 @@ wayl_destroy(struct wayland *wayl) if (wayl->presentation != NULL) wp_presentation_destroy(wayl->presentation); + tll_free(wayl->kbd.bindings.key); + tll_free(wayl->kbd.bindings.search); if (wayl->kbd.xkb_compose_state != NULL) xkb_compose_state_unref(wayl->kbd.xkb_compose_state); if (wayl->kbd.xkb_compose_table != NULL) diff --git a/wayland.h b/wayland.h index ed53591d..b8b91c00 100644 --- a/wayland.h +++ b/wayland.h @@ -41,6 +41,37 @@ struct monitor { float inch; /* e.g. 24" */ }; +enum binding_action { + BIND_ACTION_NONE, + BIND_ACTION_SCROLLBACK_UP, + BIND_ACTION_SCROLLBACK_DOWN, + 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_COUNT, +}; + +struct key_binding { + xkb_mod_mask_t mods; + xkb_keysym_t sym; + enum binding_action action; +}; +typedef tll(struct key_binding) key_binding_list_t; + +struct mouse_binding { + uint32_t button; + int count; + enum binding_action action; +}; + struct kbd { struct xkb_context *xkb; struct xkb_keymap *xkb_keymap; @@ -66,6 +97,11 @@ struct kbd { bool alt; bool ctrl; bool meta; + + struct { + key_binding_list_t key; + key_binding_list_t search; + } bindings; }; struct wl_clipboard {