From b22bb3097634206956392929795fd2902f08faa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Mar 2020 12:08:46 +0100 Subject: [PATCH 01/20] wip: initial framework for dealing with key/mouse bindings in different modes This adds initial support for defining key and mouse bindings that are applied in different terminal modes/states. For example, there are two arrays dealing with key and mouse bindings in the "normal" mode. Most bindings will go here. There's also an array for "search" mode. These bindings will be used when the user has started a scrollback search. In the future, there may be a model selection mode as well. Or maybe "search" and "modal selection" will be combined into a single "keyboard" mode. We'll see. Since the keyboard bindings depend on the current XKB keymap, translation from the user specified key combination string cannot be done when loading the configuration, but must be done when we've received a keymap from the wayland server. We should explore if it's possible to load some kind of default keymap just to be able to verify the validity of the key combination strings at configuration load time, to be able to reject the configuration at startup. A couple of key bindings have been added as proof of concept. Mouse bindings aren't handled at all yet, and is likely to be re-written. For example, we can probably translate the configuration strings at configuration load time. --- config.c | 19 +++++++++++ config.h | 14 ++++++++ input.c | 97 +++++++++++++++++++++++++++++++++++++++++++++---------- wayland.c | 1 + wayland.h | 16 +++++++++ 5 files changed, 130 insertions(+), 17 deletions(-) diff --git a/config.c b/config.c index 46056567..0baa5eb5 100644 --- a/config.c +++ b/config.c @@ -619,6 +619,19 @@ config_load(struct config *conf, const char *conf_path) }, }, + .bindings = { + .key = { + [BIND_ACTION_CLIPBOARD_COPY] = strdup("Control+Shift+C"), + [BIND_ACTION_CLIPBOARD_PASTE] = strdup("Control+Shift+V"), + [BIND_ACTION_SEARCH_START] = strdup("Control+Shift+R"), + }, + .mouse = { + [BIND_ACTION_PRIMARY_PASTE] = strdup("BTN_MIDDLE"), + }, + .search = { + }, + }, + .csd = { .preferred = CONF_CSD_PREFER_SERVER, .title_height = 26, @@ -671,4 +684,10 @@ 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.mouse[i]); + free(conf.bindings.search[i]); + } } diff --git a/config.h b/config.h index a89637e1..af078b00 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]; + char *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/input.c b/input.c index efa58c9d..0e6b3c92 100644 --- a/input.c +++ b/input.c @@ -31,6 +31,35 @@ #include "terminal.h" #include "vt.h" +static void +execute_binding(struct terminal *term, enum binding_action action, + uint32_t serial) +{ + switch (action) { + 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: + LOG_ERR("unimplemented"); + assert(false); + break; + + case BIND_ACTION_SEARCH_START: + search_begin(term); + break; + + case BIND_ACTION_COUNT: + assert(false); + break; + } +} + static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) @@ -59,6 +88,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, xkb_context_unref(wayl->kbd.xkb); wayl->kbd.xkb = NULL; } + tll_free(wayl->kbd.key_bindings); wayl->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wayl->kbd.xkb_keymap = xkb_keymap_new_from_string( @@ -81,6 +111,48 @@ 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++) { + const char *combo = wayl->conf->bindings.key[i]; + + if (combo == NULL) + continue; + + xkb_mod_mask_t mod_mask = 0; + xkb_keysym_t sym = 0; + + char *copy = strdup(combo); + + for (char *p = strtok(copy, "+"), *n = strtok(NULL, "+"); + p != NULL; + p = n, n = strtok(NULL, "+")) + { + if (n != NULL) { + /* Modifier */ + xkb_mod_index_t mod = xkb_keymap_mod_get_index( + wayl->kbd.xkb_keymap, p); + + if (mod == -1) { + LOG_ERR("%s: not a valid modifier name", p); + continue; + } + + mod_mask |= 1 << mod; + LOG_DBG("MOD: %d - %s", mod, p); + } else { + /* Symbol */ + sym = xkb_keysym_from_name(p, 0); + } + } + + free(copy); + + assert(sym != 0); + + /* TODO: convert to DBG */ + LOG_INFO("binding: %s -> mods=0x%08x, sym=%d", combo, mod_mask, sym); + tll_push_back(wayl->kbd.key_bindings, ((struct key_binding){mod_mask, sym, i})); + } } static void @@ -374,6 +446,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, "effective=0x%08x, repeats=%d", sym, mods, consumed, significant, effective_mods, should_repeat); + tll_foreach(wayl->kbd.key_bindings, it) { + if (it->item.mods == effective_mods && it->item.sym == sym) { + execute_binding(term, it->item.action, serial); + goto maybe_repeat; + } + } + /* * Builtin shortcuts */ @@ -408,23 +487,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, } 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) { + if (sym == XKB_KEY_Return) { term_spawn_new(term); goto maybe_repeat; } diff --git a/wayland.c b/wayland.c index 4bf77562..c508c710 100644 --- a/wayland.c +++ b/wayland.c @@ -911,6 +911,7 @@ wayl_destroy(struct wayland *wayl) if (wayl->presentation != NULL) wp_presentation_destroy(wayl->presentation); + tll_free(wayl->kbd.key_bindings); 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..775aee0f 100644 --- a/wayland.h +++ b/wayland.h @@ -41,6 +41,20 @@ struct monitor { float inch; /* e.g. 24" */ }; +enum binding_action { + BIND_ACTION_CLIPBOARD_COPY, + BIND_ACTION_CLIPBOARD_PASTE, + BIND_ACTION_PRIMARY_PASTE, + BIND_ACTION_SEARCH_START, + BIND_ACTION_COUNT, +}; + +struct key_binding { + xkb_mod_mask_t mods; + xkb_keysym_t sym; + enum binding_action action; +}; + struct kbd { struct xkb_context *xkb; struct xkb_keymap *xkb_keymap; @@ -66,6 +80,8 @@ struct kbd { bool alt; bool ctrl; bool meta; + + tll(struct key_binding) key_bindings; }; struct wl_clipboard { From fcf483277568a7839708428cd30f53b01465cc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Mar 2020 15:17:29 +0100 Subject: [PATCH 02/20] input: convert the rest of the normal key bindings to configurable bindings New actions defined and implemented: * scrollback up/down * font size up/down/reset * spawn terminal Break out key combo parsing to a new function, parse_key_binding_for_action(). This function parses a string containing one or more space separated key combo definitions on the form (mod1+mod2+...+key), where key is a XKB key name (e.g. KP_Add). Convert all hardcoded key bindings to configuration based bindings. These still cannot actually be configured by the user, but at least lives in the conf struct. --- config.c | 6 ++ input.c | 171 ++++++++++++++++++++++++++++-------------------------- wayland.c | 2 +- wayland.h | 11 +++- 4 files changed, 107 insertions(+), 83 deletions(-) diff --git a/config.c b/config.c index 0baa5eb5..124c76b6 100644 --- a/config.c +++ b/config.c @@ -621,9 +621,15 @@ 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] = strdup("BTN_MIDDLE"), diff --git a/input.c b/input.c index 0e6b3c92..830a0293 100644 --- a/input.c +++ b/input.c @@ -36,6 +36,14 @@ execute_binding(struct terminal *term, enum binding_action action, uint32_t serial) { switch (action) { + 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; @@ -54,12 +62,87 @@ execute_binding(struct terminal *term, enum binding_action action, 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_COUNT: assert(false); break; } } +static bool +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=%zu: mods=0x%08x, sym=%d", i, 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) @@ -88,7 +171,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, xkb_context_unref(wayl->kbd.xkb); wayl->kbd.xkb = NULL; } - tll_free(wayl->kbd.key_bindings); + 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( @@ -113,45 +196,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, close(fd); for (size_t i = 0; i < BIND_ACTION_COUNT; i++) { - const char *combo = wayl->conf->bindings.key[i]; - - if (combo == NULL) - continue; - - xkb_mod_mask_t mod_mask = 0; - xkb_keysym_t sym = 0; - - char *copy = strdup(combo); - - for (char *p = strtok(copy, "+"), *n = strtok(NULL, "+"); - p != NULL; - p = n, n = strtok(NULL, "+")) - { - if (n != NULL) { - /* Modifier */ - xkb_mod_index_t mod = xkb_keymap_mod_get_index( - wayl->kbd.xkb_keymap, p); - - if (mod == -1) { - LOG_ERR("%s: not a valid modifier name", p); - continue; - } - - mod_mask |= 1 << mod; - LOG_DBG("MOD: %d - %s", mod, p); - } else { - /* Symbol */ - sym = xkb_keysym_from_name(p, 0); - } - } - - free(copy); - - assert(sym != 0); - - /* TODO: convert to DBG */ - LOG_INFO("binding: %s -> mods=0x%08x, sym=%d", combo, mod_mask, sym); - tll_push_back(wayl->kbd.key_bindings, ((struct key_binding){mod_mask, sym, i})); + const char *combos = wayl->conf->bindings.key[i]; + parse_key_binding_for_action(wayl->kbd.xkb_keymap, i, combos, &wayl->kbd.bindings.key); } } @@ -446,53 +492,16 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, "effective=0x%08x, repeats=%d", sym, mods, consumed, significant, effective_mods, should_repeat); - tll_foreach(wayl->kbd.key_bindings, it) { + /* + * User configurable bindings + */ + tll_foreach(wayl->kbd.bindings.key, it) { if (it->item.mods == effective_mods && it->item.sym == sym) { execute_binding(term, it->item.action, serial); goto maybe_repeat; } } - /* - * Builtin shortcuts - */ - - 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_Return) { - term_spawn_new(term); - goto maybe_repeat; - } - } - /* * Keys generating escape sequences */ diff --git a/wayland.c b/wayland.c index c508c710..8916baed 100644 --- a/wayland.c +++ b/wayland.c @@ -911,7 +911,7 @@ wayl_destroy(struct wayland *wayl) if (wayl->presentation != NULL) wp_presentation_destroy(wayl->presentation); - tll_free(wayl->kbd.key_bindings); + tll_free(wayl->kbd.bindings.key); 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 775aee0f..219e4b8d 100644 --- a/wayland.h +++ b/wayland.h @@ -42,10 +42,16 @@ struct monitor { }; enum binding_action { + 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_COUNT, }; @@ -54,6 +60,7 @@ struct key_binding { xkb_keysym_t sym; enum binding_action action; }; +typedef tll(struct key_binding) key_binding_list_t; struct kbd { struct xkb_context *xkb; @@ -81,7 +88,9 @@ struct kbd { bool ctrl; bool meta; - tll(struct key_binding) key_bindings; + struct { + key_binding_list_t key; + } bindings; }; struct wl_clipboard { From 0d188895c3e88f591aa47f7a5c272ad959e5a40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Mar 2020 15:28:47 +0100 Subject: [PATCH 03/20] search: add support for user configurable key bindings No default bindings defined yet, though. --- input.c | 19 ++++++++++++------- input.h | 3 +++ search.c | 14 +++++++++++++- search.h | 3 ++- wayland.h | 1 + 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/input.c b/input.c index 830a0293..e8cfb32d 100644 --- a/input.c +++ b/input.c @@ -31,9 +31,9 @@ #include "terminal.h" #include "vt.h" -static void -execute_binding(struct terminal *term, enum binding_action action, - uint32_t serial) +void +input_execute_binding(struct terminal *term, enum binding_action action, + uint32_t serial) { switch (action) { case BIND_ACTION_SCROLLBACK_UP: @@ -196,8 +196,13 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, close(fd); for (size_t i = 0; i < BIND_ACTION_COUNT; i++) { - const char *combos = wayl->conf->bindings.key[i]; - parse_key_binding_for_action(wayl->kbd.xkb_keymap, i, combos, &wayl->kbd.bindings.key); + parse_key_binding_for_action( + wayl->kbd.xkb_keymap, i, + wayl->conf->bindings.key[i], &wayl->kbd.bindings.key); + + parse_key_binding_for_action( + wayl->kbd.xkb_keymap, i, + wayl->conf->bindings.search[i], &wayl->kbd.bindings.search); } } @@ -476,7 +481,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; } @@ -497,7 +502,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, */ tll_foreach(wayl->kbd.bindings.key, it) { if (it->item.mods == effective_mods && it->item.sym == sym) { - execute_binding(term, it->item.action, serial); + input_execute_binding(term, it->item.action, serial); goto maybe_repeat; } } diff --git a/input.h b/input.h index 2483389a..5507a269 100644 --- a/input.h +++ b/input.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "wayland.h" @@ -8,3 +9,5 @@ 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); +void input_execute_binding( + struct terminal *term, enum binding_action action, uint32_t serial); 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.h b/wayland.h index 219e4b8d..bf765f04 100644 --- a/wayland.h +++ b/wayland.h @@ -90,6 +90,7 @@ struct kbd { struct { key_binding_list_t key; + key_binding_list_t search; } bindings; }; From cb21ede02072b755e419dac9d60f664823b8fb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Mar 2020 15:30:05 +0100 Subject: [PATCH 04/20] wayland: free 'search' key bindings on destroy --- wayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wayland.c b/wayland.c index 8916baed..69407964 100644 --- a/wayland.c +++ b/wayland.c @@ -912,6 +912,7 @@ wayl_destroy(struct wayland *wayl) 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) From 9e985cc3586823d23c3bd3716e0e6ba7e8b23b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Mar 2020 15:36:30 +0100 Subject: [PATCH 05/20] footrc: add default key bindings Note that we still don't parse this section. --- footrc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/footrc b/footrc index 7cac547d..60b2d516 100644 --- a/footrc +++ b/footrc @@ -42,3 +42,14 @@ # 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 From f78a8a77ed18c48cc2369f9a51c683a6b9400a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 19:47:01 +0100 Subject: [PATCH 06/20] input: parse_key_binding_for_action: repair LOG_DBG statement --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index e8cfb32d..0b8cf213 100644 --- a/input.c +++ b/input.c @@ -125,7 +125,7 @@ parse_key_binding_for_action( } } - LOG_DBG("action=%zu: mods=0x%08x, sym=%d", i, mod_mask, sym); + LOG_DBG("action=%u: mods=0x%08x, sym=%d", action, mod_mask, sym); assert(sym != 0); if (bindings != NULL) { From 0c72128405919263127f2771de8a7a8561961219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 19:47:22 +0100 Subject: [PATCH 07/20] input: parse_key_binding_for_action: export function --- input.c | 8 ++++---- input.h | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/input.c b/input.c index 0b8cf213..96de8160 100644 --- a/input.c +++ b/input.c @@ -84,8 +84,8 @@ input_execute_binding(struct terminal *term, enum binding_action action, } } -static bool -parse_key_binding_for_action( +bool +input_parse_key_binding_for_action( struct xkb_keymap *keymap, enum binding_action action, const char *combos, key_binding_list_t *bindings) { @@ -196,11 +196,11 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, close(fd); for (size_t i = 0; i < BIND_ACTION_COUNT; i++) { - parse_key_binding_for_action( + input_parse_key_binding_for_action( wayl->kbd.xkb_keymap, i, wayl->conf->bindings.key[i], &wayl->kbd.bindings.key); - parse_key_binding_for_action( + input_parse_key_binding_for_action( wayl->kbd.xkb_keymap, i, wayl->conf->bindings.search[i], &wayl->kbd.bindings.search); } diff --git a/input.h b/input.h index 5507a269..9c274964 100644 --- a/input.h +++ b/input.h @@ -9,5 +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); From 048edc58f1709707f98a87336abe4fa7a45ae5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 20:03:04 +0100 Subject: [PATCH 08/20] config: load custom key bindings This adds parsing of a new section - "key-bindings" - where the user can configure the key bindings on the form "action=combo1 .. comboN" The validity of the key combinations are checked with the default XKB keymap. --- config.c | 96 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/config.c b/config.c index 7d7fe18d..62a060c0 100644 --- a/config.c +++ b/config.c @@ -12,9 +12,15 @@ #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 +47,19 @@ static const uint32_t default_bright[] = { 0xffffff, }; +static const char *binding_action_map[] = { + [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", +}; + static char * get_shell(void) { @@ -422,6 +441,39 @@ parse_section_csd(const char *key, const char *value, struct config *conf, 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 (strcmp(key, binding_action_map[action]) != 0) + continue; + + 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, action, value, NULL); + + xkb_keymap_unref(keymap); + xkb_context_unref(ctx); + + if (!valid_combo) { + LOG_ERR("%s:%d: invalid key combination: %s", path, lineno, value); + return false; + } + + free(conf->bindings.key[action]); + conf->bindings.key[action] = strdup(value); + return true; + } + + + LOG_WARN("%s:%u: invalid key: %s", path, lineno, key); + return false; + +} + static bool parse_config_file(FILE *f, struct config *conf, const char *path) { @@ -430,6 +482,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path) SECTION_COLORS, SECTION_CURSOR, SECTION_CSD, + SECTION_KEY_BINDINGS, + SECTION_COUNT, } section = SECTION_MAIN; /* Function pointer, called for each key/value line */ @@ -437,22 +491,18 @@ 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"}, }; -#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; @@ -500,17 +550,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; } @@ -549,7 +601,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)) From c993148635ec80e64728c7b86069a4572faf448b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 20:04:25 +0100 Subject: [PATCH 09/20] main: free config if we fail to load the config --- main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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()) { From dba880a088899e00ada30145fbe0f870ee0ac2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 20:22:04 +0100 Subject: [PATCH 10/20] doc/foot.5: document key bindings --- doc/foot.5.scd | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/doc/foot.5.scd b/doc/foot.5.scd index edfecc27..2946ddc8 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -147,6 +147,53 @@ 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. + +Note that _Alt_ is usually called *Mod1*. + +*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_. + # FONT FORMAT The font is specified in FontConfig syntax. That is, a colon-separated From 214ef146a2a75f16e994ee27689ed36981d6c57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Mar 2020 20:32:15 +0100 Subject: [PATCH 11/20] doc/foot.1: keyboard shortcuts: add pointer to foot(5) --- doc/foot.1.scd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 From 1f904fc257a143dee629eec1f8ba961a3f2fa060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 09:31:25 +0100 Subject: [PATCH 12/20] quirks: weston_csd_{on,off}: don't do anything in fullscreen mode --- quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quirks.c b/quirks.c index 6db96e46..05d2dd5f 100644 --- a/quirks.c +++ b/quirks.c @@ -61,6 +61,8 @@ quirk_weston_csd_on(struct terminal *term) { if (term->window->use_csd != CSD_YES) return; + if (term->window->is_fullscreen) + return; for (int i = 0; i < ALEN(term->window->csd.surface); i++) quirk_weston_subsurface_desync_on(term->window->csd.sub_surface[i]); @@ -71,6 +73,8 @@ quirk_weston_csd_off(struct terminal *term) { if (term->window->use_csd != CSD_YES) return; + if (term->window->is_fullscreen) + return; for (int i = 0; i < ALEN(term->window->csd.surface); i++) quirk_weston_subsurface_desync_off(term->window->csd.sub_surface[i]); From 65ff582358c07c8bb69f56dc8192c86d21b620d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 09:34:09 +0100 Subject: [PATCH 13/20] bindings: add minimize/maximize/fullscreen actions These actions are by default not bound to anything. --- config.c | 6 ++++++ doc/foot.5.scd | 9 +++++++++ footrc | 3 +++ input.c | 18 ++++++++++++++++++ wayland.h | 3 +++ 5 files changed, 39 insertions(+) diff --git a/config.c b/config.c index 62a060c0..19377b8a 100644 --- a/config.c +++ b/config.c @@ -58,8 +58,14 @@ static const char *binding_action_map[] = { [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) { diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 2946ddc8..5c8becdf 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -194,6 +194,15 @@ Note that _Alt_ is usually called *Mod1*. *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_. + # FONT FORMAT The font is specified in FontConfig syntax. That is, a colon-separated diff --git a/footrc b/footrc index 60b2d516..468bc67d 100644 --- a/footrc +++ b/footrc @@ -53,3 +53,6 @@ # font-decrease=Control+minus Control+KP_Subtract # font-reset=Control+0 Control+KP_0 # spawn-terminal=Control+Shift+Return +# # minimize= +# # maximize= +# # fullscreen= diff --git a/input.c b/input.c index 96de8160..399d9d26 100644 --- a/input.c +++ b/input.c @@ -78,6 +78,24 @@ input_execute_binding(struct terminal *term, enum binding_action action, 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; diff --git a/wayland.h b/wayland.h index bf765f04..a824c21f 100644 --- a/wayland.h +++ b/wayland.h @@ -52,6 +52,9 @@ enum binding_action { 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, }; From 9fae38a4b22afe0baf54d3a62e7f4df8a606b5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 10:19:21 +0100 Subject: [PATCH 14/20] config: 'invalid key' is always an error, not a warning --- config.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/config.c b/config.c index 19377b8a..77b74227 100644 --- a/config.c +++ b/config.c @@ -258,7 +258,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; } @@ -440,7 +440,7 @@ 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; } @@ -474,8 +474,7 @@ parse_section_key_bindings(const char *key, const char *value, struct config *co return true; } - - LOG_WARN("%s:%u: invalid key: %s", path, lineno, key); + LOG_ERR("%s:%u: invalid key: %s", path, lineno, key); return false; } From 45384839f0e9627cd5b376b045e11c14277d40d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 10:20:05 +0100 Subject: [PATCH 15/20] config/input: implement mouse bindings * New config section, "mouse-bindings", where bindings are defined on the form "action=BTN_ * pointer_button() handler now scans the bindings list instead of hardcoding primary-paste to BTN_MIDDLE. * The implementation handles single- double- and triple clicks in the bindings, but there is currently no way to define anything but a single-click binding in the configuration. --- config.c | 61 +++++++++++++++++++++++++++++++++++++++++--------- config.h | 2 +- doc/foot.5.scd | 12 ++++++++++ footrc | 3 +++ input.c | 30 +++++++++++++++++-------- wayland.h | 6 +++++ 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/config.c b/config.c index 77b74227..7e1f8939 100644 --- a/config.c +++ b/config.c @@ -12,6 +12,7 @@ #include #include +#include #include #define LOG_MODULE "config" @@ -448,8 +449,9 @@ parse_section_csd(const char *key, const char *value, struct config *conf, } static bool -parse_section_key_bindings(const char *key, const char *value, struct config *conf, - const char *path, unsigned lineno) +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 (strcmp(key, binding_action_map[action]) != 0) @@ -459,7 +461,8 @@ parse_section_key_bindings(const char *key, const char *value, struct config *co 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, action, value, NULL); + bool valid_combo = input_parse_key_binding_for_action( + keymap, action, value, NULL); xkb_keymap_unref(keymap); xkb_context_unref(ctx); @@ -479,6 +482,43 @@ parse_section_key_bindings(const char *key, const char *value, struct config *co } +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 (strcmp(key, binding_action_map[action]) != 0) + continue; + + 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; + + conf->bindings.mouse[action] = (struct mouse_binding){i, 1, 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) { @@ -488,6 +528,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path) SECTION_CURSOR, SECTION_CSD, SECTION_KEY_BINDINGS, + SECTION_MOUSE_BINDINGS, SECTION_COUNT, } section = SECTION_MAIN; @@ -500,11 +541,12 @@ parse_config_file(FILE *f, struct config *conf, const char *path) 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_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"}, }; static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); @@ -699,7 +741,7 @@ config_load(struct config *conf, const char *conf_path) [BIND_ACTION_SPAWN_TERMINAL] = strdup("Control+Shift+Return"), }, .mouse = { - [BIND_ACTION_PRIMARY_PASTE] = strdup("BTN_MIDDLE"), + [BIND_ACTION_PRIMARY_PASTE] = {BTN_MIDDLE, 1, BIND_ACTION_PRIMARY_PASTE}, }, .search = { }, @@ -760,7 +802,6 @@ config_free(struct config conf) for (size_t i = 0; i < BIND_ACTION_COUNT; i++) { free(conf.bindings.key[i]); - free(conf.bindings.mouse[i]); free(conf.bindings.search[i]); } } diff --git a/config.h b/config.h index af078b00..bf205305 100644 --- a/config.h +++ b/config.h @@ -39,7 +39,7 @@ struct config { struct { /* Bindings for "normal" mode */ char *key[BIND_ACTION_COUNT]; - char *mouse[BIND_ACTION_COUNT]; + struct mouse_binding mouse[BIND_ACTION_COUNT]; /* * Special modes diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 5c8becdf..dbbcc52d 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -203,6 +203,18 @@ Note that _Alt_ is usually called *Mod1*. *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*. + +*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 468bc67d..73512474 100644 --- a/footrc +++ b/footrc @@ -56,3 +56,6 @@ # # minimize= # # maximize= # # fullscreen= + +[mouse-bindings] +# primary-paste=BTN_MIDDLE diff --git a/input.c b/input.c index 399d9d26..8fbb02cd 100644 --- a/input.c +++ b/input.c @@ -31,6 +31,8 @@ #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) @@ -54,8 +56,7 @@ input_execute_binding(struct terminal *term, enum binding_action action, break; case BIND_ACTION_PRIMARY_PASTE: - LOG_ERR("unimplemented"); - assert(false); + selection_from_primary(term); break; case BIND_ACTION_SEARCH_START: @@ -321,7 +322,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; @@ -402,7 +402,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; } @@ -1151,11 +1150,24 @@ 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); } selection_cancel(term); } diff --git a/wayland.h b/wayland.h index a824c21f..2ddfdf87 100644 --- a/wayland.h +++ b/wayland.h @@ -65,6 +65,12 @@ struct key_binding { }; 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; From 60170f4738a7e5f20b06d8e56d61b0a1d75ae25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 10:23:59 +0100 Subject: [PATCH 16/20] changelog: user configurable key- and mouse bindings --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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 From c58f9a9ef93fd3fd2be711906b8214a53665e45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 10:46:27 +0100 Subject: [PATCH 17/20] config: mouse-bindings: verify button isn't already mapped A button may only be mapped to a single action. Detect when the user tried to map the same button to multiple actions and error out. To clear a binding (for example, to free up a button from the default bindings), one can set the action it is bound to to `NONE` (e.g. `primary-paste=NONE`). --- config.c | 26 +++++++++++++++++++++++++- doc/foot.5.scd | 14 ++++++++++---- input.c | 4 ++++ wayland.h | 1 + 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index 7e1f8939..ba216348 100644 --- a/config.c +++ b/config.c @@ -49,6 +49,7 @@ static const uint32_t default_bright[] = { }; 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", @@ -454,6 +455,9 @@ parse_section_key_bindings( 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; @@ -488,9 +492,17 @@ parse_section_mouse_bindings( 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", @@ -506,7 +518,19 @@ parse_section_mouse_bindings( if (map[i] == NULL || strcmp(map[i], value) != 0) continue; - conf->bindings.mouse[action] = (struct mouse_binding){i, 1, action}; + 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; } diff --git a/doc/foot.5.scd b/doc/foot.5.scd index dbbcc52d..457d053b 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -151,15 +151,15 @@ component. This section lets you override the default key bindings. -The general format is *action*=*combo1*...*comboN*. That is, each +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 +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. -Note that _Alt_ is usually called *Mod1*. +Note that *Alt* is usually called *Mod1*. *scrollback-up* Scrolls up/back in history. Default: _Shift+Page_Up_. @@ -207,10 +207,16 @@ Note that _Alt_ is usually called *Mod1*. This section lets you override the default mouse bindings. -The general format is *action*=*BTN\_*, where *BTN\_* is +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*. + *primary-paste* Pastes from the _primary selection_. Default: _BTN_MIDDLE_. diff --git a/input.c b/input.c index 8fbb02cd..1c80dd8b 100644 --- a/input.c +++ b/input.c @@ -38,6 +38,9 @@ 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; @@ -1168,6 +1171,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } input_execute_binding(term, binding->action, serial); + break; } selection_cancel(term); } diff --git a/wayland.h b/wayland.h index 2ddfdf87..b8b91c00 100644 --- a/wayland.h +++ b/wayland.h @@ -42,6 +42,7 @@ struct monitor { }; enum binding_action { + BIND_ACTION_NONE, BIND_ACTION_SCROLLBACK_UP, BIND_ACTION_SCROLLBACK_DOWN, BIND_ACTION_CLIPBOARD_COPY, From 5cbd0527d703b2013b175d371de6c81aa7792863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 17:16:35 +0100 Subject: [PATCH 18/20] config: key-bindings: verify key combo isn't already mapped to another action --- config.c | 58 ++++++++++++++++++++++++++++++++++++++++---------- doc/foot.5.scd | 6 ++++++ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/config.c b/config.c index ba216348..bd237604 100644 --- a/config.c +++ b/config.c @@ -449,6 +449,47 @@ parse_section_csd(const char *key, const char *value, struct config *conf, 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, @@ -461,18 +502,13 @@ parse_section_key_bindings( if (strcmp(key, binding_action_map[action]) != 0) continue; - 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); + if (strcmp(value, "NONE") == 0) { + free(conf->bindings.key[action]); + conf->bindings.key[action] = NULL; + return true; + } - bool valid_combo = input_parse_key_binding_for_action( - keymap, action, value, NULL); - - xkb_keymap_unref(keymap); - xkb_context_unref(ctx); - - if (!valid_combo) { - LOG_ERR("%s:%d: invalid key combination: %s", path, lineno, value); + if (!verify_key_combo(conf, value, path, lineno)) { return false; } diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 457d053b..d01a3d6e 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -161,6 +161,12 @@ upper case. 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_. From 0e00b6fa159956354ea9598328c13c4e201c20e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 17:21:48 +0100 Subject: [PATCH 19/20] doc/foot.5: add an example that uses Shift --- doc/foot.5.scd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/foot.5.scd b/doc/foot.5.scd index d01a3d6e..8c6fb2fe 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -157,7 +157,8 @@ 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. +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*. From a7b90b514cc9ade0ded93172d9b88ad21a04a55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 12 Mar 2020 17:24:54 +0100 Subject: [PATCH 20/20] doc/foot.5: mention that *all* actions can be used in mouse bindings --- doc/foot.5.scd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/foot.5.scd b/doc/foot.5.scd index 8c6fb2fe..f504fba5 100644 --- a/doc/foot.5.scd +++ b/doc/foot.5.scd @@ -224,6 +224,8 @@ 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_.