mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-21 01:40:16 -05:00
Merge branch 'bindings'
This commit is contained in:
commit
47fa91e02b
13 changed files with 560 additions and 87 deletions
|
|
@ -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
|
||||
|
|
|
|||
231
config.c
231
config.c
|
|
@ -12,9 +12,16 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
config.h
14
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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\_<name>_, where _BTN\_<name>_ 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
|
||||
|
|
|
|||
17
footrc
17
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=<not bound>
|
||||
# # maximize=<not bound>
|
||||
# # fullscreen=<not bound>
|
||||
|
||||
[mouse-bindings]
|
||||
# primary-paste=BTN_MIDDLE
|
||||
|
|
|
|||
227
input.c
227
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);
|
||||
}
|
||||
|
|
|
|||
8
input.h
8
input.h
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#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);
|
||||
|
|
|
|||
4
main.c
4
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()) {
|
||||
|
|
|
|||
14
search.c
14
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))
|
||||
|
|
|
|||
3
search.h
3
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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
36
wayland.h
36
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue