From 422d94fb46ad18e52f5385e416cd00cb7b4c2fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 6 Feb 2022 19:36:44 +0100 Subject: [PATCH 1/6] wip: map key combos to custom text strings (including escapes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this, it is now possible to map key combos to custom escapes. The new bindings are defined in a new section, “text-bindings”, on the form “string=key combo”. The string can consist of printable characters, or \xNN style hex digits: [text-bindings] abcd = Control+a \x1b[A = Control+b Control+c Control+d # map ctrl+b/c/d to UP --- config.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- config.h | 11 ++++++ foot.ini | 3 ++ input.c | 5 +++ wayland.h | 2 +- 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/config.c b/config.c index 37768a8e..99083130 100644 --- a/config.c +++ b/config.c @@ -111,6 +111,7 @@ static const char *const binding_action_map[] = { [BIND_ACTION_PIPE_SELECTED] = "pipe-selected", [BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy", [BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch", + [BIND_ACTION_TEXT_BINDING] = "text-binding", /* Mouse-specific actions */ [BIND_ACTION_SELECT_BEGIN] = "select-begin", @@ -1480,7 +1481,8 @@ free_binding_aux(struct binding_aux *aux) { switch (aux->type) { case BINDING_AUX_NONE: break; - case BINDING_AUX_PIPE: free_argv(&aux->pipe); + case BINDING_AUX_PIPE: free_argv(&aux->pipe); break; + case BINDING_AUX_TEXT: free(aux->text.data); break; } } @@ -1578,8 +1580,15 @@ binding_aux_equal(const struct binding_aux *a, return false; switch (a->type) { - case BINDING_AUX_NONE: return true; - case BINDING_AUX_PIPE: return argv_compare(&a->pipe, &b->pipe) == 0; + case BINDING_AUX_NONE: + return true; + + case BINDING_AUX_PIPE: + return argv_compare(&a->pipe, &b->pipe) == 0; + + case BINDING_AUX_TEXT: + return a->text.len == b->text.len && + memcmp(a->text.data, b->text.data, a->text.len) == 0; } BUG("invalid AUX type: %d", a->type); @@ -2230,6 +2239,81 @@ parse_section_mouse_bindings(struct context *ctx) return false; } +static bool +parse_section_text_bindings(struct context *ctx) +{ + struct config *conf = ctx->conf; + const char *key = ctx->key; + + const size_t key_len = strlen(key); + + uint8_t *data = xmalloc(key_len + 1); + size_t data_len = 0; + bool esc = false; + + for (size_t i = 0; i < key_len; i++) { + if (key[i] == '\\') { + if (i + 1 >= key_len) { + ctx->value = ""; + LOG_CONTEXTUAL_ERR("trailing backslash"); + goto err; + } + + esc = true; + } + + else if (esc) { + if (key[i] != 'x') { + ctx->value = ""; + LOG_CONTEXTUAL_ERR("invalid escaped character: %c", key[i]); + goto err; + } + if (i + 2 >= key_len) { + ctx->value = ""; + LOG_CONTEXTUAL_ERR("\\x sequence too short"); + goto err; + } + + const uint8_t nib1 = hex2nibble(key[i + 1]); + const uint8_t nib2 = hex2nibble(key[i + 2]); + + if (nib1 >= HEX_DIGIT_INVALID || nib2 >= HEX_DIGIT_INVALID) { + ctx->value = ""; + LOG_CONTEXTUAL_ERR("invalid \\x sequence: \\x%c%c", + key[i + 1], key[i + 2]); + goto err; + } + + data[data_len++] = nib1 << 4 | nib2; + esc = false; + i += 2; + } + + else + data[data_len++] = key[i]; + } + + struct binding_aux aux = { + .type = BINDING_AUX_TEXT, + .text = { + .data = data, + .len = data_len, + }, + }; + + if (!value_to_key_combos(ctx, BIND_ACTION_TEXT_BINDING, &aux, + &conf->bindings.key, KEY_BINDING)) + { + goto err; + } + + return true; + +err: + free(data); + return false; +} + static bool parse_section_tweak(struct context *ctx) { @@ -2429,6 +2513,7 @@ enum section { SECTION_SEARCH_BINDINGS, SECTION_URL_BINDINGS, SECTION_MOUSE_BINDINGS, + SECTION_TEXT_BINDINGS, SECTION_TWEAK, SECTION_COUNT, }; @@ -2452,6 +2537,7 @@ static const struct { [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"}, [SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"}, [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"}, + [SECTION_TEXT_BINDINGS] = {&parse_section_text_bindings, "text-bindings"}, [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, }; @@ -3085,6 +3171,8 @@ key_binding_list_clone(struct config_key_binding_list *dst, const struct config_key_binding_list *src) { struct argv *last_master_argv = NULL; + uint8_t *last_master_text_data = NULL; + size_t last_master_text_len = 0; dst->count = src->count; dst->arr = xmalloc(src->count * sizeof(dst->arr[0])); @@ -3098,6 +3186,8 @@ key_binding_list_clone(struct config_key_binding_list *dst, switch (old->aux.type) { case BINDING_AUX_NONE: last_master_argv = NULL; + last_master_text_data = NULL; + last_master_text_len = 0; break; case BINDING_AUX_PIPE: @@ -3108,6 +3198,25 @@ key_binding_list_clone(struct config_key_binding_list *dst, xassert(last_master_argv != NULL); new->aux.pipe = *last_master_argv; } + last_master_text_data = NULL; + last_master_text_len = 0; + break; + + case BINDING_AUX_TEXT: + if (old->aux.master_copy) { + const size_t len = old->aux.text.len; + new->aux.text.len = len; + new->aux.text.data = xmalloc(len); + memcpy(new->aux.text.data, old->aux.text.data, len); + + last_master_text_len = len; + last_master_text_data = new->aux.text.data; + } else { + xassert(last_master_text_data != NULL); + new->aux.text.len = last_master_text_len; + new->aux.text.data = last_master_text_data; + } + last_master_argv = NULL; break; } } diff --git a/config.h b/config.h index cd5bbd14..cf9ca3db 100644 --- a/config.h +++ b/config.h @@ -47,6 +47,7 @@ struct argv { enum binding_aux_type { BINDING_AUX_NONE, BINDING_AUX_PIPE, + BINDING_AUX_TEXT, }; struct binding_aux { @@ -55,6 +56,11 @@ struct binding_aux { union { struct argv pipe; + + struct { + uint8_t *data; + size_t len; + } text; }; }; @@ -63,6 +69,11 @@ enum key_binding_type { MOUSE_BINDING, }; +struct config_key_binding_text { + char *text; + bool master_copy; +}; + struct config_key_binding { int action; /* One of the varios bind_action_* enums from wayland.h */ struct config_key_modifiers modifiers; diff --git a/foot.ini b/foot.ini index 072613d0..9e6aa601 100644 --- a/foot.ini +++ b/foot.ini @@ -178,4 +178,7 @@ # select-word-whitespace=Control+BTN_LEFT-2 # select-row=BTN_LEFT-3 +[text-bindings] +# \x01=Mod4+a # Map Super+a -> Ctrl+a + # vim: ft=conf diff --git a/input.c b/input.c index e7b02fb8..26af0807 100644 --- a/input.c +++ b/input.c @@ -313,6 +313,11 @@ execute_binding(struct seat *seat, struct terminal *term, return true; } + case BIND_ACTION_TEXT_BINDING: + xassert(binding->aux->type == BINDING_AUX_TEXT); + term_to_slave(term, binding->aux->text.data, binding->aux->text.len); + return true; + case BIND_ACTION_SELECT_BEGIN: selection_start( term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false); diff --git a/wayland.h b/wayland.h index 81109673..2d753e70 100644 --- a/wayland.h +++ b/wayland.h @@ -54,6 +54,7 @@ enum bind_action_normal { BIND_ACTION_PIPE_SELECTED, BIND_ACTION_SHOW_URLS_COPY, BIND_ACTION_SHOW_URLS_LAUNCH, + BIND_ACTION_TEXT_BINDING, /* Mouse specific actions - i.e. they require a mouse coordinate */ BIND_ACTION_SELECT_BEGIN, @@ -119,7 +120,6 @@ struct key_binding { }; const struct binding_aux *aux; - }; typedef tll(struct key_binding) key_binding_list_t; From 0018e570d49778f388756890ab3ecfa23a437987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 6 Feb 2022 21:46:41 +0100 Subject: [PATCH 2/6] tests: config: initial tests for text-bindings --- tests/test-config.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test-config.c b/tests/test-config.c index 04149a59..eb99780d 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -1094,6 +1094,42 @@ test_section_mouse_bindings_collisions(void) config_free(conf); } +static void +test_section_text_bindings(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "text-bindings", .path = "unittest"}; + + ctx.key = "abcd"; + ctx.value = XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT "+x"; + xassert(parse_section_text_bindings(&ctx)); + + ctx.key = "\\x07"; + xassert(parse_section_text_bindings(&ctx)); + + ctx.key = "\\x1g"; + xassert(!parse_section_text_bindings(&ctx)); + + ctx.key = "\\x1"; + xassert(!parse_section_text_bindings(&ctx)); + + ctx.key = "\\x"; + xassert(!parse_section_text_bindings(&ctx)); + + ctx.key = "\\"; + xassert(!parse_section_text_bindings(&ctx)); + + ctx.key = "\\y"; + xassert(!parse_section_text_bindings(&ctx)); + + ctx.key = "abcd"; + ctx.value = "InvalidMod+y"; + xassert(!parse_section_text_bindings(&ctx)); + + config_free(conf); +} + static void test_section_tweak(void) { @@ -1188,6 +1224,7 @@ main(int argc, const char *const *argv) test_section_url_bindings_collisions(); test_section_mouse_bindings(); test_section_mouse_bindings_collisions(); + test_section_text_bindings(); test_section_tweak(); log_deinit(); return 0; From cff097197f0e002636f982c70278dedf057256e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 8 Feb 2022 21:21:17 +0100 Subject: [PATCH 3/6] config: do key binding collision handling in overrides This ensures we detect, and handle, collisions also for key-bindings specified as overrides. --- CHANGELOG.md | 2 ++ config.c | 35 +++++++++++++++++------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab8d4b81..a385e3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,8 @@ * Exit code being 0 when a foot server with no open windows terminate due to e.g. a Wayland connection failure (https://codeberg.org/dnkl/foot/issues/943). +* Key binding collisions not detected for bindings specified as option + overrides on the command line. ### Security diff --git a/config.c b/config.c index 99083130..f50ee685 100644 --- a/config.c +++ b/config.c @@ -3053,25 +3053,12 @@ config_load(struct config *conf, const char *conf_path, goto out; } - ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal) && - config_override_apply(conf, overrides, errors_are_fatal); - - if (ret && - (!resolve_key_binding_collisions( - conf, section_info[SECTION_KEY_BINDINGS].name, - binding_action_map, &conf->bindings.key, KEY_BINDING) || - !resolve_key_binding_collisions( - conf, section_info[SECTION_SEARCH_BINDINGS].name, - search_binding_action_map, &conf->bindings.search, KEY_BINDING) || - !resolve_key_binding_collisions( - conf, section_info[SECTION_URL_BINDINGS].name, - url_binding_action_map, &conf->bindings.url, KEY_BINDING) || - !resolve_key_binding_collisions( - conf, section_info[SECTION_MOUSE_BINDINGS].name, - binding_action_map, &conf->bindings.mouse, MOUSE_BINDING))) + if (!parse_config_file(f, conf, conf_file.path, errors_are_fatal) || + !config_override_apply(conf, overrides, errors_are_fatal)) { ret = !errors_are_fatal; - } + } else + ret = true; fclose(f); @@ -3163,7 +3150,19 @@ config_override_apply(struct config *conf, config_override_t *overrides, conf->csd.border_width = max( min_csd_border_width, conf->csd.border_width_visible); - return true; + return + resolve_key_binding_collisions( + conf, section_info[SECTION_KEY_BINDINGS].name, + binding_action_map, &conf->bindings.key, KEY_BINDING) && + resolve_key_binding_collisions( + conf, section_info[SECTION_SEARCH_BINDINGS].name, + search_binding_action_map, &conf->bindings.search, KEY_BINDING) && + resolve_key_binding_collisions( + conf, section_info[SECTION_URL_BINDINGS].name, + url_binding_action_map, &conf->bindings.url, KEY_BINDING) && + resolve_key_binding_collisions( + conf, section_info[SECTION_MOUSE_BINDINGS].name, + binding_action_map, &conf->bindings.mouse, MOUSE_BINDING); } static void NOINLINE From 8379b48a9e7ab71ffb0732d67db1814d8aafbc3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 9 Feb 2022 17:56:29 +0100 Subject: [PATCH 4/6] changelog: remapping input to custom escape sequences --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a385e3ed..e85fb439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ * Mouse selections are now finalized when the window is resized (https://codeberg.org/dnkl/foot/issues/922). +* Support for re-mapping input, i.e. mapping input to custom escape + sequences (https://codeberg.org/dnkl/foot/issues/325). ### Deprecated From 4e69c1d178d49bab9a1d9237ed21a3fb22100c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 9 Feb 2022 18:10:38 +0100 Subject: [PATCH 5/6] foot.ini: move [text-bindings] section, and update example Use Super+c -> Ctrl+c as example --- foot.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/foot.ini b/foot.ini index 9e6aa601..2ae09313 100644 --- a/foot.ini +++ b/foot.ini @@ -167,6 +167,9 @@ # cancel=Control+g Control+c Control+d Escape # toggle-url-visible=t +[text-bindings] +# \x03=Mod4+c # Map Super+c -> Ctrl+c + [mouse-bindings] # selection-override-modifiers=Shift # primary-paste=BTN_MIDDLE @@ -178,7 +181,4 @@ # select-word-whitespace=Control+BTN_LEFT-2 # select-row=BTN_LEFT-3 -[text-bindings] -# \x01=Mod4+a # Map Super+a -> Ctrl+a - # vim: ft=conf From bd9041fdb560078d20a142a9ed27b56af903e351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 9 Feb 2022 18:11:14 +0100 Subject: [PATCH 6/6] doc: foot.ini: document the new [text-bindings] section --- doc/foot.ini.5.scd | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7701cc69..7763a8b3 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -855,6 +855,37 @@ Be careful; do not use single-letter keys that are also used in Default: _t_. +# SECTION: text-bindings + +This section lets you remap key combinations to custom escape +sequences. + +The format is _text=combo1...comboN_. That is, the string to emit 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 valid XKB key names. + +The text string specifies the characters, or bytes, to emit when the +associated key combination(s) are pressed. There are two ways to +specify a character: + +- Normal, printable characters are written as-is: *abcdef*. +- Bytes (e.g. ESC) are written as two-digit hexadecimal numbers, with + a *\\x* prefix: *\\x1b*. + +Example: you would like to remap _Super+k_ to the _Up_ key. + +The escape sequence for the Up key is _ESC [ A_ (without the +spaces). Thus, we need to specify this in foot.ini (*Mod4* is the XKB +name for the Super/logo key): + +*\\x1b[A = Mod4+k* + +Another example: to remap _Super+c_ to _Control+c_: + +*\\x03 = Mod4+c* + + # SECTION: mouse-bindings This section lets you override the default mouse bindings.