Merge branch 'modifiers-in-mouse-bindings' into master

This commit is contained in:
Daniel Eklöf 2020-08-14 07:36:34 +02:00
commit 9b65531d6a
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
8 changed files with 603 additions and 332 deletions

View file

@ -31,6 +31,8 @@
* **colors.selection-foreground** and **colors.selection-background** * **colors.selection-foreground** and **colors.selection-background**
options to `footrc`. options to `footrc`.
* **tweak.render-timer** option to `footrc`. * **tweak.render-timer** option to `footrc`.
* Modifier support in mouse bindings
(https://codeberg.org/dnkl/foot/issues/77).
### Deprecated ### Deprecated

609
config.c
View file

@ -652,66 +652,159 @@ parse_section_csd(const char *key, const char *value, struct config *conf,
return true; return true;
} }
static bool /* Struct that holds temporary key/mouse binding parsed data */
verify_key_combo(struct config *conf, const char *combo, const char *path, unsigned lineno) struct key_combo {
char *text; /* Raw text, e.g. "Control+Shift+V" */
struct config_key_modifiers modifiers;
union {
xkb_keysym_t sym; /* Key converted to an XKB symbol, e.g. XKB_KEY_V */
struct {
int button;
int count;
} m;
};
};
typedef tll(struct key_combo) key_combo_list_t;
static void
free_key_combo_list(key_combo_list_t *key_combos)
{ {
/* Check regular key bindings */ tll_foreach(*key_combos, it)
tll_foreach(conf->bindings.key, it) { free(it->item.text);
char *copy = xstrdup(it->item.key); tll_free(*key_combos);
}
for (char *save = NULL, *collision = strtok_r(copy, " ", &save); static bool
collision != NULL; parse_modifiers(struct config *conf, const char *text, size_t len,
collision = strtok_r(NULL, " ", &save)) struct config_key_modifiers *modifiers, const char *path, unsigned lineno)
{ {
if (strcmp(combo, collision) == 0) { bool ret = false;
bool has_pipe = it->item.pipe.cmd != NULL;
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s%s%s%s'", path, lineno, combo, *modifiers = (struct config_key_modifiers){};
binding_action_map[it->item.action], char *copy = xstrndup(text, len);
has_pipe ? " [" : "",
has_pipe ? it->item.pipe.cmd : "", for (char *tok_ctx = NULL, *key = strtok_r(copy, "+", &tok_ctx);
has_pipe ? "]" : ""); key != NULL;
free(copy); key = strtok_r(NULL, "+", &tok_ctx))
return false; {
} if (strcmp(key, XKB_MOD_NAME_SHIFT) == 0)
modifiers->shift = true;
else if (strcmp(key, XKB_MOD_NAME_CTRL) == 0)
modifiers->ctrl = true;
else if (strcmp(key, XKB_MOD_NAME_ALT) == 0)
modifiers->alt = true;
else if (strcmp(key, XKB_MOD_NAME_LOGO) == 0)
modifiers->meta = true;
else {
LOG_AND_NOTIFY_ERR("%s:%d: %s: not a valid modifier name",
path, lineno, key);
goto out;
}
}
ret = true;
out:
free(copy);
return ret;
}
static bool
parse_key_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
const char *path, unsigned lineno)
{
assert(tll_length(*key_combos) == 0);
char *copy = xstrdup(combos);
for (char *tok_ctx = NULL, *combo = strtok_r(copy, " ", &tok_ctx);
combo != NULL;
combo = strtok_r(NULL, " ", &tok_ctx))
{
struct config_key_modifiers modifiers = {};
const char *key = strrchr(combo, '+');
if (key == NULL) {
/* No modifiers */
key = combo;
} else {
if (!parse_modifiers(conf, combo, key - combo, &modifiers, path, lineno))
goto err;
key++; /* Skip past the '+' */
} }
free(copy); /* Translate key name to symbol */
} xkb_keysym_t sym = xkb_keysym_from_name(key, 0);
if (sym == XKB_KEY_NoSymbol) {
/* Check scrollback search bindings */ LOG_AND_NOTIFY_ERR("%s:%d: %s: key is not a valid XKB key name",
tll_foreach(conf->bindings.search, it) { path, lineno, key);
char *copy = xstrdup(it->item.key); goto err;
for (char *save = NULL, *collision = strtok_r(copy, " ", &save);
collision != NULL;
collision = strtok_r(NULL, " ", &save))
{
if (strcmp(combo, collision) == 0) {
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'", path, lineno, combo,
search_binding_action_map[it->item.action]);
free(copy);
return false;
}
} }
free(copy); tll_push_back(
} *key_combos,
((struct key_combo){.text = xstrdup(combo), .modifiers = modifiers, .sym = sym}));
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){}, XKB_KEYMAP_COMPILE_NO_FLAGS);
bool valid_combo = input_parse_key_binding(keymap, combo, NULL);
xkb_keymap_unref(keymap);
xkb_context_unref(ctx);
if (!valid_combo) {
LOG_AND_NOTIFY_ERR("%s:%d: invalid key combination: %s", path, lineno, combo);
return false;
} }
free(copy);
return true; return true;
err:
tll_foreach(*key_combos, it)
free(it->item.text);
tll_free(*key_combos);
free(copy);
return false;
}
static bool
has_key_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
const char *path, unsigned lineno)
{
tll_foreach(conf->bindings.key, it) {
tll_foreach(*key_combos, it2) {
const struct config_key_modifiers *mods1 = &it->item.modifiers;
const struct config_key_modifiers *mods2 = &it2->item.modifiers;
if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
it->item.sym == it2->item.sym)
{
bool has_pipe = it->item.pipe.cmd != NULL;
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s%s%s%s'",
path, lineno, it2->item.text,
binding_action_map[it->item.action],
has_pipe ? " [" : "",
has_pipe ? it->item.pipe.cmd : "",
has_pipe ? "]" : "");
return true;
}
}
}
return false;
}
static bool
has_search_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
const char *path, unsigned lineno)
{
tll_foreach(conf->bindings.search, it) {
tll_foreach(*key_combos, it2) {
const struct config_key_modifiers *mods1 = &it->item.modifiers;
const struct config_key_modifiers *mods2 = &it2->item.modifiers;
if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
it->item.sym == it2->item.sym)
{
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'",
path, lineno, it2->item.text,
search_binding_action_map[it->item.action]);
return true;
}
}
}
return false;
} }
static int static int
@ -777,12 +870,14 @@ parse_section_key_bindings(
if (strcmp(key, binding_action_map[action]) != 0) if (strcmp(key, binding_action_map[action]) != 0)
continue; continue;
/* Unset binding */
if (strcasecmp(value, "none") == 0) { if (strcasecmp(value, "none") == 0) {
tll_foreach(conf->bindings.key, it) { tll_foreach(conf->bindings.key, it) {
if (it->item.action == action) { if (it->item.action == action) {
free(it->item.key); if (it->item.pipe.master_copy) {
free(it->item.pipe.cmd); free(it->item.pipe.cmd);
free(it->item.pipe.argv); free(it->item.pipe.argv);
}
tll_remove(conf->bindings.key, it); tll_remove(conf->bindings.key, it);
} }
} }
@ -791,13 +886,17 @@ parse_section_key_bindings(
return true; return true;
} }
if (!verify_key_combo(conf, value, path, lineno)) { key_combo_list_t key_combos = tll_init();
if (!parse_key_combos(conf, value, &key_combos, path, lineno) ||
has_key_binding_collisions(conf, &key_combos, path, lineno))
{
free(pipe_argv); free(pipe_argv);
free(pipe_cmd); free(pipe_cmd);
free_key_combo_list(&key_combos);
return false; return false;
} }
bool already_added = false; /* Remove existing bindings for this action+pipe */
tll_foreach(conf->bindings.key, it) { tll_foreach(conf->bindings.key, it) {
if (it->item.action == action && if (it->item.action == action &&
((it->item.pipe.argv == NULL && pipe_argv == NULL) || ((it->item.pipe.argv == NULL && pipe_argv == NULL) ||
@ -805,29 +904,33 @@ parse_section_key_bindings(
argv_compare(it->item.pipe.argv, pipe_argv) == 0))) argv_compare(it->item.pipe.argv, pipe_argv) == 0)))
{ {
free(it->item.key); if (it->item.pipe.master_copy) {
free(it->item.pipe.cmd); free(it->item.pipe.cmd);
free(it->item.pipe.argv); free(it->item.pipe.argv);
}
it->item.key = xstrdup(value); tll_remove(conf->bindings.key, it);
it->item.pipe.cmd = pipe_cmd;
it->item.pipe.argv = pipe_argv;
already_added = true;
break;
} }
} }
if (!already_added) { /* Emit key bindings */
bool first = true;
tll_foreach(key_combos, it) {
struct config_key_binding_normal binding = { struct config_key_binding_normal binding = {
.action = action, .action = action,
.key = xstrdup(value), .modifiers = it->item.modifiers,
.sym = it->item.sym,
.pipe = { .pipe = {
.cmd = pipe_cmd, .cmd = pipe_cmd,
.argv = pipe_argv, .argv = pipe_argv,
.master_copy = first,
}, },
}; };
tll_push_back(conf->bindings.key, binding); tll_push_back(conf->bindings.key, binding);
first = false;
} }
free_key_combo_list(&key_combos);
return true; return true;
} }
@ -851,39 +954,40 @@ parse_section_search_bindings(
if (strcmp(key, search_binding_action_map[action]) != 0) if (strcmp(key, search_binding_action_map[action]) != 0)
continue; continue;
/* Unset binding */
if (strcasecmp(value, "none") == 0) { if (strcasecmp(value, "none") == 0) {
tll_foreach(conf->bindings.search, it) { tll_foreach(conf->bindings.search, it) {
if (it->item.action == action) { if (it->item.action == action)
free(it->item.key);
tll_remove(conf->bindings.search, it); tll_remove(conf->bindings.search, it);
}
} }
return true; return true;
} }
if (!verify_key_combo(conf, value, path, lineno)) { key_combo_list_t key_combos = tll_init();
if (!parse_key_combos(conf, value, &key_combos, path, lineno) ||
has_search_binding_collisions(conf, &key_combos, path, lineno))
{
free_key_combo_list(&key_combos);
return false; return false;
} }
bool already_added = false; /* Remove existing bindings for this action */
tll_foreach(conf->bindings.search, it) { tll_foreach(conf->bindings.search, it) {
if (it->item.action == action) { if (it->item.action == action)
tll_remove(conf->bindings.search, it);
free(it->item.key);
it->item.key = xstrdup(value);
already_added = true;
break;
}
} }
if (!already_added) { /* Emit key bindings */
struct config_key_binding_search binding = { tll_foreach(key_combos, it) {
struct config_key_binding_normal binding = {
.action = action, .action = action,
.key = xstrdup(value), .modifiers = it->item.modifiers,
.sym = it->item.sym,
}; };
tll_push_back(conf->bindings.search, binding); tll_push_back(conf->bindings.key, binding);
} }
free_key_combo_list(&key_combos);
return true; return true;
} }
@ -892,6 +996,104 @@ parse_section_search_bindings(
} }
static bool
parse_mouse_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
const char *path, unsigned lineno)
{
assert(tll_length(*key_combos) == 0);
char *copy = xstrdup(combos);
for (char *tok_ctx = NULL, *combo = strtok_r(copy, " ", &tok_ctx);
combo != NULL;
combo = strtok_r(NULL, " ", &tok_ctx))
{
struct config_key_modifiers modifiers = {};
const char *key = strrchr(combo, '+');
if (key == NULL) {
/* No modifiers */
key = combo;
} else {
if (!parse_modifiers(conf, combo, key - combo, &modifiers, path, lineno))
goto err;
key++; /* Skip past the '+' */
}
static const struct {
const char *name;
int code;
} 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},
};
int button = 0;
for (size_t i = 0; i < ALEN(map); i++) {
if (strcmp(key, map[i].name) == 0) {
button = map[i].code;
break;
}
}
if (button == 0) {
LOG_AND_NOTIFY_ERR("%s:%d: %s: invalid mouse button name", path, lineno, key);
goto err;
}
struct key_combo new = {
.text = xstrdup(combo),
.modifiers = modifiers,
.m = {
.button = button,
.count = 1,
},
};
tll_push_back(*key_combos, new);
}
free(copy);
return true;
err:
tll_foreach(*key_combos, it)
free(it->item.text);
tll_free(*key_combos);
free(copy);
return false;
}
static bool
has_mouse_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
const char *path, unsigned lineno)
{
tll_foreach(conf->bindings.mouse, it) {
tll_foreach(*key_combos, it2) {
const struct config_key_modifiers *mods1 = &it->item.modifiers;
const struct config_key_modifiers *mods2 = &it2->item.modifiers;
if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
it->item.button == it2->item.m.button &&
it->item.count == it2->item.m.count)
{
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'",
path, lineno, it2->item.text,
binding_action_map[it->item.action]);
return true;
}
}
}
return false;
}
static bool static bool
parse_section_mouse_bindings( parse_section_mouse_bindings(
const char *key, const char *value, struct config *conf, const char *key, const char *value, struct config *conf,
@ -904,66 +1106,43 @@ parse_section_mouse_bindings(
if (strcmp(key, binding_action_map[action]) != 0) if (strcmp(key, binding_action_map[action]) != 0)
continue; continue;
if (strcmp(value, "NONE") == 0) { /* Unset binding */
if (strcasecmp(value, "none") == 0) {
tll_foreach(conf->bindings.mouse, it) { tll_foreach(conf->bindings.mouse, it) {
if (it->item.action == action) { if (it->item.action == action)
tll_remove(conf->bindings.mouse, it); tll_remove(conf->bindings.mouse, it);
break;
}
} }
return true; return true;
} }
const char *map[] = { key_combo_list_t key_combos = tll_init();
[BTN_LEFT] = "BTN_LEFT", if (!parse_mouse_combos(conf, value, &key_combos, path, lineno) ||
[BTN_RIGHT] = "BTN_RIGHT", has_mouse_binding_collisions(conf, &key_combos, path, lineno))
[BTN_MIDDLE] = "BTN_MIDDLE", {
[BTN_SIDE] = "BTN_SIDE", free_key_combo_list(&key_combos);
[BTN_EXTRA] = "BTN_EXTRA", return false;
[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 */
tll_foreach(conf->bindings.mouse, it) {
if (it->item.button == i && it->item.count == count) {
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to %s", path, lineno,
value, binding_action_map[it->item.action]);
return false;
}
}
bool already_added = false;
tll_foreach(conf->bindings.mouse, it) {
if (it->item.action == action) {
it->item.button = i;
it->item.count = count;
already_added = true;
break;
}
}
if (!already_added) {
struct mouse_binding binding = {
.action = action,
.button = i,
.count = count,
};
tll_push_back(conf->bindings.mouse, binding);
}
return true;
} }
LOG_AND_NOTIFY_ERR("%s:%d: invalid mouse button: %s", path, lineno, value); /* Remove existing bindings for this action */
return false; tll_foreach(conf->bindings.mouse, it) {
if (it->item.action == action) {
tll_remove(conf->bindings.mouse, it);
}
}
/* Emit mouse bindings */
tll_foreach(key_combos, it) {
struct config_mouse_binding binding = {
.action = action,
.modifiers = it->item.modifiers,
.button = it->item.m.button,
.count = it->item.m.count,
};
tll_push_back(conf->bindings.mouse, binding);
}
free_key_combo_list(&key_combos);
return true;
} }
LOG_AND_NOTIFY_ERR("%s:%u: [mouse-bindings]: %s: invalid key", path, lineno, key); LOG_AND_NOTIFY_ERR("%s:%u: [mouse-bindings]: %s: invalid key", path, lineno, key);
@ -1232,6 +1411,98 @@ get_server_socket_path(void)
return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display); return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display);
} }
static void
add_default_key_bindings(struct config *conf)
{
#define add_binding(action, mods, sym) \
do { \
tll_push_back( \
conf->bindings.key, \
((struct config_key_binding_normal){action, mods, sym})); \
} while (0)
const struct config_key_modifiers shift = {.shift = true};
const struct config_key_modifiers ctrl = {.ctrl = true};
const struct config_key_modifiers ctrl_shift = {.ctrl = true, .shift = true};
add_binding(BIND_ACTION_SCROLLBACK_UP, shift, XKB_KEY_Page_Up);
add_binding(BIND_ACTION_SCROLLBACK_DOWN, shift, XKB_KEY_Page_Down);
add_binding(BIND_ACTION_CLIPBOARD_COPY, ctrl_shift, XKB_KEY_C);
add_binding(BIND_ACTION_CLIPBOARD_PASTE, ctrl_shift, XKB_KEY_V);
add_binding(BIND_ACTION_SEARCH_START, ctrl_shift, XKB_KEY_R);
add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_plus);
add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_equal);
add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_KP_Add);
add_binding(BIND_ACTION_FONT_SIZE_DOWN, ctrl, XKB_KEY_minus);
add_binding(BIND_ACTION_FONT_SIZE_DOWN, ctrl, XKB_KEY_KP_Subtract);
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_0);
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_KP_0);
add_binding(BIND_ACTION_SPAWN_TERMINAL, ctrl_shift, XKB_KEY_N);
#undef add_binding
}
static void
add_default_search_bindings(struct config *conf)
{
#define add_binding(action, mods, sym) \
do { \
tll_push_back( \
conf->bindings.search, \
((struct config_key_binding_search){action, mods, sym})); \
} while (0)
const struct config_key_modifiers none = {};
const struct config_key_modifiers alt = {.alt = true};
const struct config_key_modifiers ctrl = {.ctrl = true};
const struct config_key_modifiers ctrl_shift = {.ctrl = true, .shift = true};
add_binding(BIND_ACTION_SEARCH_CANCEL, ctrl, XKB_KEY_g);
add_binding(BIND_ACTION_SEARCH_CANCEL, none, XKB_KEY_Escape);
add_binding(BIND_ACTION_SEARCH_COMMIT, none, XKB_KEY_Return);
add_binding(BIND_ACTION_SEARCH_FIND_PREV, ctrl, XKB_KEY_r);
add_binding(BIND_ACTION_SEARCH_FIND_NEXT, ctrl, XKB_KEY_s);
add_binding(BIND_ACTION_SEARCH_EDIT_LEFT, none, XKB_KEY_Left);
add_binding(BIND_ACTION_SEARCH_EDIT_LEFT, ctrl, XKB_KEY_b);
add_binding(BIND_ACTION_SEARCH_EDIT_LEFT_WORD, ctrl, XKB_KEY_Left);
add_binding(BIND_ACTION_SEARCH_EDIT_LEFT_WORD, alt, XKB_KEY_b);
add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT, none, XKB_KEY_Right);
add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT, ctrl, XKB_KEY_f);
add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, ctrl, XKB_KEY_Right);
add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, alt, XKB_KEY_f);
add_binding(BIND_ACTION_SEARCH_EDIT_HOME, none, XKB_KEY_Home);
add_binding(BIND_ACTION_SEARCH_EDIT_HOME, ctrl, XKB_KEY_a);
add_binding(BIND_ACTION_SEARCH_EDIT_END, none, XKB_KEY_End);
add_binding(BIND_ACTION_SEARCH_EDIT_END, ctrl, XKB_KEY_e);
add_binding(BIND_ACTION_SEARCH_DELETE_PREV, none, XKB_KEY_BackSpace);
add_binding(BIND_ACTION_SEARCH_DELETE_PREV_WORD, ctrl, XKB_KEY_BackSpace);
add_binding(BIND_ACTION_SEARCH_DELETE_PREV_WORD, alt, XKB_KEY_BackSpace);
add_binding(BIND_ACTION_SEARCH_DELETE_NEXT, none, XKB_KEY_Delete);
add_binding(BIND_ACTION_SEARCH_DELETE_NEXT_WORD, ctrl, XKB_KEY_Delete);
add_binding(BIND_ACTION_SEARCH_DELETE_NEXT_WORD, alt, XKB_KEY_d);
add_binding(BIND_ACTION_SEARCH_EXTEND_WORD, ctrl, XKB_KEY_w);
add_binding(BIND_ACTION_SEARCH_EXTEND_WORD_WS, ctrl_shift, XKB_KEY_W);
#undef add_binding
}
static void
add_default_mouse_bindings(struct config *conf)
{
#define add_binding(action, mods, btn, count) \
do { \
tll_push_back( \
conf->bindings.mouse, \
((struct config_mouse_binding){action, mods, btn, count})); \
} while (0)
const struct config_key_modifiers none = {};
add_binding(BIND_ACTION_PRIMARY_PASTE, none, BTN_MIDDLE, 1);
#undef add_binding
}
bool bool
config_load(struct config *conf, const char *conf_path, bool errors_are_fatal) config_load(struct config *conf, const char *conf_path, bool errors_are_fatal)
{ {
@ -1320,62 +1591,9 @@ config_load(struct config *conf, const char *conf_path, bool errors_are_fatal)
.notifications = tll_init(), .notifications = tll_init(),
}; };
struct config_key_binding_normal scrollback_up = {BIND_ACTION_SCROLLBACK_UP, xstrdup("Shift+Page_Up")}; add_default_key_bindings(conf);
struct config_key_binding_normal scrollback_down = {BIND_ACTION_SCROLLBACK_DOWN, xstrdup("Shift+Page_Down")}; add_default_search_bindings(conf);
struct config_key_binding_normal clipboard_copy = {BIND_ACTION_CLIPBOARD_COPY, xstrdup("Control+Shift+C")}; add_default_mouse_bindings(conf);
struct config_key_binding_normal clipboard_paste = {BIND_ACTION_CLIPBOARD_PASTE, xstrdup("Control+Shift+V")};
struct config_key_binding_normal search_start = {BIND_ACTION_SEARCH_START, xstrdup("Control+Shift+R")};
struct config_key_binding_normal font_size_up = {BIND_ACTION_FONT_SIZE_UP, xstrdup("Control+plus Control+equal Control+KP_Add")};
struct config_key_binding_normal font_size_down = {BIND_ACTION_FONT_SIZE_DOWN, xstrdup("Control+minus Control+KP_Subtract")};
struct config_key_binding_normal font_size_reset = {BIND_ACTION_FONT_SIZE_RESET, xstrdup("Control+0 Control+KP_0")};
struct config_key_binding_normal spawn_terminal = {BIND_ACTION_SPAWN_TERMINAL, xstrdup("Control+Shift+N")};
tll_push_back(conf->bindings.key, scrollback_up);
tll_push_back(conf->bindings.key, scrollback_down);
tll_push_back(conf->bindings.key, clipboard_copy);
tll_push_back(conf->bindings.key, clipboard_paste);
tll_push_back(conf->bindings.key, search_start);
tll_push_back(conf->bindings.key, font_size_up);
tll_push_back(conf->bindings.key, font_size_down);
tll_push_back(conf->bindings.key, font_size_reset);
tll_push_back(conf->bindings.key, spawn_terminal);
struct mouse_binding primary_paste = {BIND_ACTION_PRIMARY_PASTE, BTN_MIDDLE, 1};
tll_push_back(conf->bindings.mouse, primary_paste);
struct config_key_binding_search search_cancel = {BIND_ACTION_SEARCH_CANCEL, xstrdup("Control+g Escape")};
struct config_key_binding_search search_commit = {BIND_ACTION_SEARCH_COMMIT, xstrdup("Return")};
struct config_key_binding_search search_find_prev = {BIND_ACTION_SEARCH_FIND_PREV, xstrdup("Control+r")};
struct config_key_binding_search search_find_next = {BIND_ACTION_SEARCH_FIND_NEXT, xstrdup("Control+s")};
struct config_key_binding_search search_edit_left = {BIND_ACTION_SEARCH_EDIT_LEFT, xstrdup("Left Control+b")};
struct config_key_binding_search search_edit_left_word = {BIND_ACTION_SEARCH_EDIT_LEFT_WORD, xstrdup("Control+Left Mod1+b")};
struct config_key_binding_search search_edit_right = {BIND_ACTION_SEARCH_EDIT_RIGHT, xstrdup("Right Control+f")};
struct config_key_binding_search search_edit_right_word = {BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, xstrdup("Control+Right Mod1+f")};
struct config_key_binding_search search_edit_home = {BIND_ACTION_SEARCH_EDIT_HOME, xstrdup("Home Control+a")};
struct config_key_binding_search search_edit_end = {BIND_ACTION_SEARCH_EDIT_END, xstrdup("End Control+e")};
struct config_key_binding_search search_del_prev = {BIND_ACTION_SEARCH_DELETE_PREV, xstrdup("BackSpace")};
struct config_key_binding_search search_del_prev_word = {BIND_ACTION_SEARCH_DELETE_PREV_WORD, xstrdup("Mod1+BackSpace Control+BackSpace")};
struct config_key_binding_search search_del_next = {BIND_ACTION_SEARCH_DELETE_NEXT, xstrdup("Delete")};
struct config_key_binding_search search_del_next_word = {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, xstrdup("Mod1+d Control+Delete")};
struct config_key_binding_search search_ext_word = {BIND_ACTION_SEARCH_EXTEND_WORD, xstrdup("Control+w")};
struct config_key_binding_search search_ext_word_ws = {BIND_ACTION_SEARCH_EXTEND_WORD_WS, xstrdup("Control+Shift+W")};
tll_push_back(conf->bindings.search, search_cancel);
tll_push_back(conf->bindings.search, search_commit);
tll_push_back(conf->bindings.search, search_find_prev);
tll_push_back(conf->bindings.search, search_find_next);
tll_push_back(conf->bindings.search, search_edit_left);
tll_push_back(conf->bindings.search, search_edit_left_word);
tll_push_back(conf->bindings.search, search_edit_right);
tll_push_back(conf->bindings.search, search_edit_right_word);
tll_push_back(conf->bindings.search, search_edit_home);
tll_push_back(conf->bindings.search, search_edit_end);
tll_push_back(conf->bindings.search, search_del_prev);
tll_push_back(conf->bindings.search, search_del_prev_word);
tll_push_back(conf->bindings.search, search_del_next);
tll_push_back(conf->bindings.search, search_del_next_word);
tll_push_back(conf->bindings.search, search_ext_word);
tll_push_back(conf->bindings.search, search_ext_word_ws);
char *default_path = NULL; char *default_path = NULL;
if (conf_path == NULL) { if (conf_path == NULL) {
@ -1428,12 +1646,11 @@ config_free(struct config conf)
free(conf.server_socket_path); free(conf.server_socket_path);
tll_foreach(conf.bindings.key, it) { tll_foreach(conf.bindings.key, it) {
free(it->item.key); if (it->item.pipe.master_copy) {
free(it->item.pipe.cmd); free(it->item.pipe.cmd);
free(it->item.pipe.argv); free(it->item.pipe.argv);
}
} }
tll_foreach(conf.bindings.search, it)
free(it->item.key);
tll_free(conf.bindings.key); tll_free(conf.bindings.key);
tll_free(conf.bindings.mouse); tll_free(conf.bindings.mouse);

View file

@ -15,18 +15,35 @@ struct config_font {
int px_size; int px_size;
}; };
struct config_key_modifiers {
bool shift;
bool alt;
bool ctrl;
bool meta;
};
struct config_key_binding_normal { struct config_key_binding_normal {
enum bind_action_normal action; enum bind_action_normal action;
char *key; struct config_key_modifiers modifiers;
xkb_keysym_t sym;
struct { struct {
char *cmd; char *cmd;
char **argv; char **argv;
bool master_copy;
} pipe; } pipe;
}; };
struct config_key_binding_search { struct config_key_binding_search {
enum bind_action_search action; enum bind_action_search action;
char *key; struct config_key_modifiers modifiers;
xkb_keysym_t sym;
};
struct config_mouse_binding {
enum bind_action_normal action;
struct config_key_modifiers modifiers;
int button;
int count;
}; };
struct config { struct config {
@ -91,7 +108,7 @@ struct config {
struct { struct {
/* Bindings for "normal" mode */ /* Bindings for "normal" mode */
tll(struct config_key_binding_normal) key; tll(struct config_key_binding_normal) key;
tll(struct mouse_binding) mouse; tll(struct config_mouse_binding) mouse;
/* /*
* Special modes * Special modes

View file

@ -362,9 +362,11 @@ scrollback search mode. The syntax is exactly the same as the regular
This section lets you override the default mouse bindings. This section lets you override the default mouse bindings.
The general format is _action=BTN\_<name>_, where _BTN\_<name>_ is The general format is _action=combo1...comboN_. That is, each action
the name of the event code (e.g. *BTN\_LEFT*, *BTN\_RIGHT*). You can may have one or more key combinations, space separated. Each
find the event names using *libinput debug-events*. combination is on the form _mod1+mod2+BTN\_<name>_. The names of the
modifiers and the key *must* be valid XKB key names. You can find the
button names using *libinput debug-events*.
A button can only be mapped to *one* action. Lets say you want to bind 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 *BTN\_MIDDLE* to *fullscreen*. Since *BTN\_MIDDLE* is the default

266
input.c
View file

@ -260,97 +260,110 @@ execute_binding(struct seat *seat, struct terminal *term,
} }
} }
bool static xkb_mod_mask_t
input_parse_key_binding(struct xkb_keymap *keymap, const char *combos, conf_modifiers_to_mask(const struct seat *seat,
key_binding_list_t *bindings) const struct config_key_modifiers *modifiers)
{ {
if (combos == NULL) xkb_mod_mask_t mods = 0;
return true; mods |= modifiers->shift << seat->kbd.mod_shift;
mods |= modifiers->ctrl << seat->kbd.mod_ctrl;
mods |= modifiers->alt << seat->kbd.mod_alt;
mods |= modifiers->meta << seat->kbd.mod_meta;
return mods;
}
xkb_keysym_t sym = XKB_KEY_NoSymbol; static xkb_keycode_list_t
key_codes_for_xkb_sym(struct xkb_keymap *keymap, xkb_keysym_t sym)
{
xkb_keycode_list_t key_codes = tll_init();
char *copy = xstrdup(combos); /*
* Find all key codes that map to the lower case
* version of the symbol.
*
* This allows us to match bindings in other layouts
* too.
*/
xkb_keysym_t lower_sym = xkb_keysym_to_lower(sym);
struct xkb_state *state = xkb_state_new(keymap);
for (char *save1 = NULL, *combo = strtok_r(copy, " ", &save1); for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap);
combo != NULL; code <= xkb_keymap_max_keycode(keymap);
combo = strtok_r(NULL, " ", &save1)) code++)
{ {
xkb_mod_mask_t mod_mask = 0; if (xkb_state_key_get_one_sym(state, code) == lower_sym)
xkb_keycode_list_t key_codes = tll_init(); tll_push_back(key_codes, code);
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);
if (sym == XKB_KEY_NoSymbol) {
LOG_ERR("%s: key binding is not a valid XKB symbol name",
key);
break;
}
/*
* Find all key codes that map to the lower case
* version of the symbol.
*
* This allows us to match bindings in other layouts
* too.
*/
xkb_keysym_t lower_sym = xkb_keysym_to_lower(sym);
struct xkb_state *state = xkb_state_new(keymap);
for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap);
code <= xkb_keymap_max_keycode(keymap);
code++)
{
if (xkb_state_key_get_one_sym(state, code) == lower_sym)
tll_push_back(key_codes, code);
}
xkb_state_unref(state);
}
}
LOG_DBG("mods=0x%08x, sym=%d", mod_mask, sym);
if (sym == XKB_KEY_NoSymbol) {
assert(tll_length(key_codes) == 0);
tll_free(key_codes);
continue;
}
assert(sym != 0);
if (bindings != NULL) {
const struct key_binding binding = {
.mods = mod_mask,
.sym = sym,
.key_codes = key_codes,
};
tll_push_back(*bindings, binding);
} else
tll_free(key_codes);
} }
free(copy); xkb_state_unref(state);
return true; return key_codes;
}
static void
convert_key_binding(struct seat *seat,
const struct config_key_binding_normal *conf_binding)
{
struct key_binding_normal binding = {
.action = conf_binding->action,
.bind = {
.mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers),
.sym = conf_binding->sym,
.key_codes = key_codes_for_xkb_sym(
seat->kbd.xkb_keymap, conf_binding->sym),
},
.pipe_argv = conf_binding->pipe.argv,
};
tll_push_back(seat->kbd.bindings.key, binding);
}
static void
convert_key_bindings(const struct config *conf, struct seat *seat)
{
tll_foreach(conf->bindings.key, it)
convert_key_binding(seat, &it->item);
}
static void
convert_search_binding(struct seat *seat,
const struct config_key_binding_search *conf_binding)
{
struct key_binding_search binding = {
.action = conf_binding->action,
.bind = {
.mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers),
.sym = conf_binding->sym,
.key_codes = key_codes_for_xkb_sym(
seat->kbd.xkb_keymap, conf_binding->sym),
},
};
tll_push_back(seat->kbd.bindings.search, binding);
}
static void
convert_search_bindings(const struct config *conf, struct seat *seat)
{
tll_foreach(conf->bindings.search, it)
convert_search_binding(seat, &it->item);
}
static void
convert_mouse_binding(struct seat *seat,
const struct config_mouse_binding *conf_binding)
{
struct mouse_binding binding = {
.action = conf_binding->action,
.mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers),
.button = conf_binding->button,
.count = conf_binding->count,
};
tll_push_back(seat->mouse.bindings, binding);
}
static void
convert_mouse_bindings(const struct config *conf, struct seat *seat)
{
tll_foreach(conf->bindings.mouse, it)
convert_mouse_binding(seat, &it->item);
} }
static void static void
@ -402,12 +415,13 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
tll_free(it->item.bind.key_codes); tll_free(it->item.bind.key_codes);
tll_free(seat->kbd.bindings.search); tll_free(seat->kbd.bindings.search);
tll_free(seat->mouse.bindings);
seat->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); seat->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
seat->kbd.xkb_keymap = xkb_keymap_new_from_buffer( seat->kbd.xkb_keymap = xkb_keymap_new_from_buffer(
seat->kbd.xkb, map_str, size, XKB_KEYMAP_FORMAT_TEXT_V1, seat->kbd.xkb, map_str, size, XKB_KEYMAP_FORMAT_TEXT_V1,
XKB_KEYMAP_COMPILE_NO_FLAGS); XKB_KEYMAP_COMPILE_NO_FLAGS);
/* TODO: initialize in enter? */
seat->kbd.xkb_state = xkb_state_new(seat->kbd.xkb_keymap); seat->kbd.xkb_state = xkb_state_new(seat->kbd.xkb_keymap);
seat->kbd.mod_shift = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, "Shift"); seat->kbd.mod_shift = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, "Shift");
@ -424,35 +438,9 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
munmap(map_str, size); munmap(map_str, size);
close(fd); close(fd);
tll_foreach(wayl->conf->bindings.key, it) { convert_key_bindings(wayl->conf, seat);
key_binding_list_t bindings = tll_init(); convert_search_bindings(wayl->conf, seat);
input_parse_key_binding( convert_mouse_bindings(wayl->conf, seat);
seat->kbd.xkb_keymap, it->item.key, &bindings);
tll_foreach(bindings, it2) {
tll_push_back(
seat->kbd.bindings.key,
((struct key_binding_normal){
.bind = it2->item,
.action = it->item.action,
.pipe_argv = it->item.pipe.argv}));
}
tll_free(bindings);
}
tll_foreach(wayl->conf->bindings.search, it) {
key_binding_list_t bindings = tll_init();
input_parse_key_binding(
seat->kbd.xkb_keymap, it->item.key, &bindings);
tll_foreach(bindings, it2) {
tll_push_back(
seat->kbd.bindings.search,
((struct key_binding_search){
.bind = it2->item, .action = it->item.action}));
}
tll_free(bindings);
}
} }
static void static void
@ -1119,7 +1107,12 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
} }
/* Reset mouse state */ /* Reset mouse state */
memset(&seat->mouse, 0, sizeof(seat->mouse)); seat->mouse.x = seat->mouse.y = 0;
seat->mouse.col = seat->mouse.row = 0;
seat->mouse.button = seat->mouse.last_button = seat->mouse.count = 0;
memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time));
seat->mouse.axis_aggregated = 0.0;
seat->mouse.have_discrete = false;
seat->mouse_focus = NULL; seat->mouse_focus = NULL;
if (old_moused == NULL) { if (old_moused == NULL) {
@ -1444,9 +1437,12 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0; bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0;
xkb_mod_mask_t mods = xkb_state_serialize_mods(
seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED);
switch (state) { switch (state) {
case WL_POINTER_BUTTON_STATE_PRESSED: { case WL_POINTER_BUTTON_STATE_PRESSED: {
if (button == BTN_LEFT && seat->mouse.count <= 3) { if (button == BTN_LEFT && seat->mouse.count <= 3 && mods == 0) {
selection_cancel(term); selection_cancel(term);
if (selection_enabled(term, seat) && cursor_is_on_grid) { if (selection_enabled(term, seat) && cursor_is_on_grid) {
@ -1470,15 +1466,16 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
} }
} }
else if (button == BTN_RIGHT && seat->mouse.count == 1) { else if (button == BTN_RIGHT && seat->mouse.count == 1 && mods == 0) {
if (selection_enabled(term, seat) && cursor_is_on_grid) { if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_extend( selection_extend(
seat, term, seat->mouse.col, seat->mouse.row, serial); seat, term, seat->mouse.col, seat->mouse.row, serial);
} }
} }
else { else if (seat->wl_keyboard != NULL) {
tll_foreach(wayl->conf->bindings.mouse, it) { /* Seat has keyboard - use mouse bindings *with* modifiers */
tll_foreach(seat->mouse.bindings, it) {
const struct mouse_binding *binding = &it->item; const struct mouse_binding *binding = &it->item;
if (binding->button != button) { if (binding->button != button) {
@ -1486,6 +1483,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
continue; continue;
} }
if (binding->mods != mods) {
/* Modifier mismatch */
continue;
}
if (binding->count != seat->mouse.count) { if (binding->count != seat->mouse.count) {
/* Not correct click count */ /* Not correct click count */
continue; continue;
@ -1496,6 +1498,32 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
} }
} }
else {
/* Seat does NOT have a keyboard - use mouse bindings *without* modifiers */
tll_foreach(seat->wayl->conf->bindings.mouse, it) {
const struct config_mouse_binding *binding = &it->item;
if (binding->button != button) {
/* Wrong button */
continue;
}
if (binding->count != seat->mouse.count) {
/* Incorrect click count */
continue;
}
const struct config_key_modifiers no_mods = {};
if (memcmp(&binding->modifiers, &no_mods, sizeof(no_mods)) != 0) {
/* Binding has modifiers */
continue;
}
execute_binding(seat, term, binding->action, NULL, serial);
break;
}
}
if (!term_mouse_grabbed(term, seat) && cursor_is_on_grid) { if (!term_mouse_grabbed(term, seat) && cursor_is_on_grid) {
term_mouse_down( term_mouse_down(
term, button, seat->mouse.row, seat->mouse.col, term, button, seat->mouse.row, seat->mouse.col,

View file

@ -9,6 +9,3 @@ extern const struct wl_keyboard_listener keyboard_listener;
extern const struct wl_pointer_listener pointer_listener; extern const struct wl_pointer_listener pointer_listener;
void input_repeat(struct seat *seat, uint32_t key); void input_repeat(struct seat *seat, uint32_t key);
bool input_parse_key_binding(struct xkb_keymap *keymap, const char *combos,
key_binding_list_t *bindings);

View file

@ -125,6 +125,8 @@ seat_destroy(struct seat *seat)
tll_free(it->item.bind.key_codes); tll_free(it->item.bind.key_codes);
tll_free(seat->kbd.bindings.search); tll_free(seat->kbd.bindings.search);
tll_free(seat->mouse.bindings);
if (seat->kbd.xkb_compose_state != NULL) if (seat->kbd.xkb_compose_state != NULL)
xkb_compose_state_unref(seat->kbd.xkb_compose_state); xkb_compose_state_unref(seat->kbd.xkb_compose_state);
if (seat->kbd.xkb_compose_table != NULL) if (seat->kbd.xkb_compose_table != NULL)
@ -762,9 +764,6 @@ handle_global(void *data, struct wl_registry *registry,
struct wl_seat *wl_seat = wl_registry_bind( struct wl_seat *wl_seat = wl_registry_bind(
wayl->registry, name, &wl_seat_interface, required); wayl->registry, name, &wl_seat_interface, required);
/* Clipboard */
/* Primary selection */
tll_push_back(wayl->seats, ((struct seat){ tll_push_back(wayl->seats, ((struct seat){
.wayl = wayl, .wayl = wayl,
.wl_seat = wl_seat, .wl_seat = wl_seat,
@ -913,22 +912,27 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
if (seat->kbd_focus != NULL) { if (seat->kbd_focus != NULL) {
LOG_WARN("compositor destroyed seat '%s' " LOG_WARN("compositor destroyed seat '%s' "
"without sending keyboard/pointer leave events", "without sending a keyboard leave event",
seat->name); seat->name);
struct terminal *term = seat->kbd_focus;
if (seat->wl_keyboard != NULL) if (seat->wl_keyboard != NULL)
keyboard_listener.leave( keyboard_listener.leave(
seat, seat->wl_keyboard, -1, term->window->surface); seat, seat->wl_keyboard, -1, seat->kbd_focus->window->surface);
}
if (seat->mouse_focus != NULL) {
LOG_WARN("compositor destroyed seat '%s' "
"without sending a pointer leave event",
seat->name);
if (seat->wl_pointer != NULL) if (seat->wl_pointer != NULL)
pointer_listener.leave( pointer_listener.leave(
seat, seat->wl_pointer, -1, term->window->surface); seat, seat->wl_pointer, -1, seat->mouse_focus->window->surface);
} }
seat_destroy(seat); seat_destroy(seat);
tll_remove(wayl->seats, it); tll_remove(wayl->seats, it);
return;
} }
LOG_WARN("unknown global removed: 0x%08x", name); LOG_WARN("unknown global removed: 0x%08x", name);

View file

@ -53,9 +53,11 @@ struct key_binding_normal {
struct mouse_binding { struct mouse_binding {
enum bind_action_normal action; enum bind_action_normal action;
xkb_mod_mask_t mods;
uint32_t button; uint32_t button;
int count; int count;
}; };
typedef tll(struct mouse_binding) mouse_binding_list_t;
enum bind_action_search { enum bind_action_search {
BIND_ACTION_SEARCH_NONE, BIND_ACTION_SEARCH_NONE,
@ -173,6 +175,8 @@ struct seat {
/* We used a discrete axis event in the current pointer frame */ /* We used a discrete axis event in the current pointer frame */
double axis_aggregated; double axis_aggregated;
bool have_discrete; bool have_discrete;
mouse_binding_list_t bindings;
} mouse; } mouse;
/* Clipboard */ /* Clipboard */