mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-23 05:33:57 -04:00
commit
f76c9e77f1
8 changed files with 221 additions and 22 deletions
|
|
@ -48,6 +48,8 @@
|
||||||
|
|
||||||
* Mouse selections are now finalized when the window is resized
|
* Mouse selections are now finalized when the window is resized
|
||||||
(https://codeberg.org/dnkl/foot/issues/922).
|
(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
|
### Deprecated
|
||||||
|
|
@ -72,6 +74,8 @@
|
||||||
* Exit code being 0 when a foot server with no open windows terminate
|
* Exit code being 0 when a foot server with no open windows terminate
|
||||||
due to e.g. a Wayland connection failure
|
due to e.g. a Wayland connection failure
|
||||||
(https://codeberg.org/dnkl/foot/issues/943).
|
(https://codeberg.org/dnkl/foot/issues/943).
|
||||||
|
* Key binding collisions not detected for bindings specified as option
|
||||||
|
overrides on the command line.
|
||||||
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
|
||||||
150
config.c
150
config.c
|
|
@ -111,6 +111,7 @@ static const char *const binding_action_map[] = {
|
||||||
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
||||||
[BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy",
|
[BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy",
|
||||||
[BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch",
|
[BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch",
|
||||||
|
[BIND_ACTION_TEXT_BINDING] = "text-binding",
|
||||||
|
|
||||||
/* Mouse-specific actions */
|
/* Mouse-specific actions */
|
||||||
[BIND_ACTION_SELECT_BEGIN] = "select-begin",
|
[BIND_ACTION_SELECT_BEGIN] = "select-begin",
|
||||||
|
|
@ -1480,7 +1481,8 @@ free_binding_aux(struct binding_aux *aux)
|
||||||
{
|
{
|
||||||
switch (aux->type) {
|
switch (aux->type) {
|
||||||
case BINDING_AUX_NONE: break;
|
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;
|
return false;
|
||||||
|
|
||||||
switch (a->type) {
|
switch (a->type) {
|
||||||
case BINDING_AUX_NONE: return true;
|
case BINDING_AUX_NONE:
|
||||||
case BINDING_AUX_PIPE: return argv_compare(&a->pipe, &b->pipe) == 0;
|
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);
|
BUG("invalid AUX type: %d", a->type);
|
||||||
|
|
@ -2230,6 +2239,81 @@ parse_section_mouse_bindings(struct context *ctx)
|
||||||
return false;
|
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
|
static bool
|
||||||
parse_section_tweak(struct context *ctx)
|
parse_section_tweak(struct context *ctx)
|
||||||
{
|
{
|
||||||
|
|
@ -2429,6 +2513,7 @@ enum section {
|
||||||
SECTION_SEARCH_BINDINGS,
|
SECTION_SEARCH_BINDINGS,
|
||||||
SECTION_URL_BINDINGS,
|
SECTION_URL_BINDINGS,
|
||||||
SECTION_MOUSE_BINDINGS,
|
SECTION_MOUSE_BINDINGS,
|
||||||
|
SECTION_TEXT_BINDINGS,
|
||||||
SECTION_TWEAK,
|
SECTION_TWEAK,
|
||||||
SECTION_COUNT,
|
SECTION_COUNT,
|
||||||
};
|
};
|
||||||
|
|
@ -2452,6 +2537,7 @@ static const struct {
|
||||||
[SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"},
|
[SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"},
|
||||||
[SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"},
|
[SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"},
|
||||||
[SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-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"},
|
[SECTION_TWEAK] = {&parse_section_tweak, "tweak"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2967,25 +3053,12 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal) &&
|
if (!parse_config_file(f, conf, conf_file.path, errors_are_fatal) ||
|
||||||
config_override_apply(conf, overrides, 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)))
|
|
||||||
{
|
{
|
||||||
ret = !errors_are_fatal;
|
ret = !errors_are_fatal;
|
||||||
}
|
} else
|
||||||
|
ret = true;
|
||||||
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
|
|
@ -3077,7 +3150,19 @@ config_override_apply(struct config *conf, config_override_t *overrides,
|
||||||
conf->csd.border_width = max(
|
conf->csd.border_width = max(
|
||||||
min_csd_border_width, conf->csd.border_width_visible);
|
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
|
static void NOINLINE
|
||||||
|
|
@ -3085,6 +3170,8 @@ key_binding_list_clone(struct config_key_binding_list *dst,
|
||||||
const struct config_key_binding_list *src)
|
const struct config_key_binding_list *src)
|
||||||
{
|
{
|
||||||
struct argv *last_master_argv = NULL;
|
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->count = src->count;
|
||||||
dst->arr = xmalloc(src->count * sizeof(dst->arr[0]));
|
dst->arr = xmalloc(src->count * sizeof(dst->arr[0]));
|
||||||
|
|
@ -3098,6 +3185,8 @@ key_binding_list_clone(struct config_key_binding_list *dst,
|
||||||
switch (old->aux.type) {
|
switch (old->aux.type) {
|
||||||
case BINDING_AUX_NONE:
|
case BINDING_AUX_NONE:
|
||||||
last_master_argv = NULL;
|
last_master_argv = NULL;
|
||||||
|
last_master_text_data = NULL;
|
||||||
|
last_master_text_len = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BINDING_AUX_PIPE:
|
case BINDING_AUX_PIPE:
|
||||||
|
|
@ -3108,6 +3197,25 @@ key_binding_list_clone(struct config_key_binding_list *dst,
|
||||||
xassert(last_master_argv != NULL);
|
xassert(last_master_argv != NULL);
|
||||||
new->aux.pipe = *last_master_argv;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
config.h
11
config.h
|
|
@ -47,6 +47,7 @@ struct argv {
|
||||||
enum binding_aux_type {
|
enum binding_aux_type {
|
||||||
BINDING_AUX_NONE,
|
BINDING_AUX_NONE,
|
||||||
BINDING_AUX_PIPE,
|
BINDING_AUX_PIPE,
|
||||||
|
BINDING_AUX_TEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct binding_aux {
|
struct binding_aux {
|
||||||
|
|
@ -55,6 +56,11 @@ struct binding_aux {
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct argv pipe;
|
struct argv pipe;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
} text;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -63,6 +69,11 @@ enum key_binding_type {
|
||||||
MOUSE_BINDING,
|
MOUSE_BINDING,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct config_key_binding_text {
|
||||||
|
char *text;
|
||||||
|
bool master_copy;
|
||||||
|
};
|
||||||
|
|
||||||
struct config_key_binding {
|
struct config_key_binding {
|
||||||
int action; /* One of the varios bind_action_* enums from wayland.h */
|
int action; /* One of the varios bind_action_* enums from wayland.h */
|
||||||
struct config_key_modifiers modifiers;
|
struct config_key_modifiers modifiers;
|
||||||
|
|
|
||||||
|
|
@ -855,6 +855,37 @@ Be careful; do not use single-letter keys that are also used in
|
||||||
Default: _t_.
|
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
|
# SECTION: mouse-bindings
|
||||||
|
|
||||||
This section lets you override the default mouse bindings.
|
This section lets you override the default mouse bindings.
|
||||||
|
|
|
||||||
3
foot.ini
3
foot.ini
|
|
@ -167,6 +167,9 @@
|
||||||
# cancel=Control+g Control+c Control+d Escape
|
# cancel=Control+g Control+c Control+d Escape
|
||||||
# toggle-url-visible=t
|
# toggle-url-visible=t
|
||||||
|
|
||||||
|
[text-bindings]
|
||||||
|
# \x03=Mod4+c # Map Super+c -> Ctrl+c
|
||||||
|
|
||||||
[mouse-bindings]
|
[mouse-bindings]
|
||||||
# selection-override-modifiers=Shift
|
# selection-override-modifiers=Shift
|
||||||
# primary-paste=BTN_MIDDLE
|
# primary-paste=BTN_MIDDLE
|
||||||
|
|
|
||||||
5
input.c
5
input.c
|
|
@ -313,6 +313,11 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
return true;
|
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:
|
case BIND_ACTION_SELECT_BEGIN:
|
||||||
selection_start(
|
selection_start(
|
||||||
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
|
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
|
||||||
|
|
|
||||||
|
|
@ -1094,6 +1094,42 @@ test_section_mouse_bindings_collisions(void)
|
||||||
config_free(conf);
|
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
|
static void
|
||||||
test_section_tweak(void)
|
test_section_tweak(void)
|
||||||
{
|
{
|
||||||
|
|
@ -1188,6 +1224,7 @@ main(int argc, const char *const *argv)
|
||||||
test_section_url_bindings_collisions();
|
test_section_url_bindings_collisions();
|
||||||
test_section_mouse_bindings();
|
test_section_mouse_bindings();
|
||||||
test_section_mouse_bindings_collisions();
|
test_section_mouse_bindings_collisions();
|
||||||
|
test_section_text_bindings();
|
||||||
test_section_tweak();
|
test_section_tweak();
|
||||||
log_deinit();
|
log_deinit();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ enum bind_action_normal {
|
||||||
BIND_ACTION_PIPE_SELECTED,
|
BIND_ACTION_PIPE_SELECTED,
|
||||||
BIND_ACTION_SHOW_URLS_COPY,
|
BIND_ACTION_SHOW_URLS_COPY,
|
||||||
BIND_ACTION_SHOW_URLS_LAUNCH,
|
BIND_ACTION_SHOW_URLS_LAUNCH,
|
||||||
|
BIND_ACTION_TEXT_BINDING,
|
||||||
|
|
||||||
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
||||||
BIND_ACTION_SELECT_BEGIN,
|
BIND_ACTION_SELECT_BEGIN,
|
||||||
|
|
@ -119,7 +120,6 @@ struct key_binding {
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct binding_aux *aux;
|
const struct binding_aux *aux;
|
||||||
|
|
||||||
};
|
};
|
||||||
typedef tll(struct key_binding) key_binding_list_t;
|
typedef tll(struct key_binding) key_binding_list_t;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue