diff --git a/config.c b/config.c index 0ed8b1a9..2fa3dcfa 100644 --- a/config.c +++ b/config.c @@ -657,7 +657,7 @@ verify_key_combo(struct config *conf, const char *combo, const char *path, unsig { /* Check regular key bindings */ tll_foreach(conf->bindings.key, it) { - char *copy = xstrdup(it->item.key); + char *copy = xstrdup(it->item.combos); for (char *save = NULL, *collision = strtok_r(copy, " ", &save); collision != NULL; @@ -680,7 +680,7 @@ verify_key_combo(struct config *conf, const char *combo, const char *path, unsig /* Check scrollback search bindings */ tll_foreach(conf->bindings.search, it) { - char *copy = xstrdup(it->item.key); + char *copy = xstrdup(it->item.combos); for (char *save = NULL, *collision = strtok_r(copy, " ", &save); collision != NULL; @@ -714,6 +714,46 @@ verify_key_combo(struct config *conf, const char *combo, const char *path, unsig return true; } +static bool +verify_mouse_combo(struct config *conf, + const char *combo, enum bind_action_normal action, + const char *path, unsigned lineno) +{ + tll_foreach(conf->bindings.mouse, it) { + char *copy = xstrdup(it->item.combos); + 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, + binding_action_map[it->item.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){}, XKB_KEYMAP_COMPILE_NO_FLAGS); + + bool valid_combo = input_parse_mouse_binding(keymap, combo, action, NULL); + + xkb_keymap_unref(keymap); + xkb_context_unref(ctx); + + if (!valid_combo) { + LOG_AND_NOTIFY_ERR("%s:%d: invalid mouse binding: %s", + path, lineno, combo); + return false; + } + + return true; +} + static int argv_compare(char *const *argv1, char *const *argv2) { @@ -780,7 +820,7 @@ parse_section_key_bindings( if (strcasecmp(value, "none") == 0) { tll_foreach(conf->bindings.key, it) { if (it->item.action == action) { - free(it->item.key); + free(it->item.combos); free(it->item.pipe.cmd); free(it->item.pipe.argv); tll_remove(conf->bindings.key, it); @@ -805,11 +845,11 @@ parse_section_key_bindings( argv_compare(it->item.pipe.argv, pipe_argv) == 0))) { - free(it->item.key); + free(it->item.combos); free(it->item.pipe.cmd); free(it->item.pipe.argv); - it->item.key = xstrdup(value); + it->item.combos = xstrdup(value); it->item.pipe.cmd = pipe_cmd; it->item.pipe.argv = pipe_argv; already_added = true; @@ -820,7 +860,7 @@ parse_section_key_bindings( if (!already_added) { struct config_key_binding_normal binding = { .action = action, - .key = xstrdup(value), + .combos = xstrdup(value), .pipe = { .cmd = pipe_cmd, .argv = pipe_argv, @@ -854,24 +894,21 @@ parse_section_search_bindings( if (strcasecmp(value, "none") == 0) { tll_foreach(conf->bindings.search, it) { if (it->item.action == action) { - free(it->item.key); + free(it->item.combos); tll_remove(conf->bindings.search, it); } } return true; } - if (!verify_key_combo(conf, value, path, lineno)) { + if (!verify_key_combo(conf, value, path, lineno)) return false; - } bool already_added = false; tll_foreach(conf->bindings.search, it) { if (it->item.action == action) { - - free(it->item.key); - - it->item.key = xstrdup(value); + free(it->item.combos); + it->item.combos = xstrdup(value); already_added = true; break; } @@ -880,7 +917,7 @@ parse_section_search_bindings( if (!already_added) { struct config_key_binding_search binding = { .action = action, - .key = xstrdup(value), + .combos = xstrdup(value), }; tll_push_back(conf->bindings.search, binding); } @@ -907,6 +944,7 @@ parse_section_mouse_bindings( if (strcmp(value, "NONE") == 0) { tll_foreach(conf->bindings.mouse, it) { if (it->item.action == action) { + free(it->item.combos); tll_remove(conf->bindings.mouse, it); break; } @@ -914,6 +952,29 @@ parse_section_mouse_bindings( return true; } + if (!verify_mouse_combo(conf, value, action, path, lineno)) + return false; + + bool already_added = false; + tll_foreach(conf->bindings.mouse, it) { + if (it->item.action == action) { + free(it->item.combos); + it->item.combos = xstrdup(value); + already_added = true; + break; + } + } + + if (!already_added) { + struct config_mouse_binding binding = { + .action = action, + .combos = xstrdup(value), + }; + tll_push_back(conf->bindings.mouse, binding); + } + + return true; +#if 0 const char *map[] = { [BTN_LEFT] = "BTN_LEFT", [BTN_RIGHT] = "BTN_RIGHT", @@ -951,8 +1012,9 @@ parse_section_mouse_bindings( } if (!already_added) { - struct mouse_binding binding = { + struct config_mouse_binding binding = { .action = action, + .mods = xstrdup(""), .button = i, .count = count, }; @@ -963,7 +1025,7 @@ parse_section_mouse_bindings( LOG_AND_NOTIFY_ERR("%s:%d: invalid mouse button: %s", path, lineno, value); return false; - +#endif } LOG_AND_NOTIFY_ERR("%s:%u: [mouse-bindings]: %s: invalid key", path, lineno, key); @@ -1340,7 +1402,7 @@ config_load(struct config *conf, const char *conf_path, bool errors_are_fatal) 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}; + struct config_mouse_binding primary_paste = {BIND_ACTION_PRIMARY_PASTE, xstrdup("BTN_MIDDLE")}; tll_push_back(conf->bindings.mouse, primary_paste); struct config_key_binding_search search_cancel = {BIND_ACTION_SEARCH_CANCEL, xstrdup("Control+g Escape")}; @@ -1428,12 +1490,14 @@ config_free(struct config conf) free(conf.server_socket_path); tll_foreach(conf.bindings.key, it) { - free(it->item.key); + free(it->item.combos); free(it->item.pipe.cmd); free(it->item.pipe.argv); } + tll_foreach(conf.bindings.mouse, it) + free(it->item.combos); tll_foreach(conf.bindings.search, it) - free(it->item.key); + free(it->item.combos); tll_free(conf.bindings.key); tll_free(conf.bindings.mouse); diff --git a/config.h b/config.h index 7963aee7..e8a06483 100644 --- a/config.h +++ b/config.h @@ -17,16 +17,21 @@ struct config_font { struct config_key_binding_normal { enum bind_action_normal action; - char *key; + char *combos; struct { char *cmd; char **argv; } pipe; }; +struct config_mouse_binding { + enum bind_action_normal action; + char *combos; +}; + struct config_key_binding_search { enum bind_action_search action; - char *key; + char *combos; }; struct config { @@ -91,7 +96,7 @@ struct config { struct { /* Bindings for "normal" mode */ tll(struct config_key_binding_normal) key; - tll(struct mouse_binding) mouse; + tll(struct config_mouse_binding) mouse; /* * Special modes diff --git a/input.c b/input.c index 55090ff7..afd9155a 100644 --- a/input.c +++ b/input.c @@ -303,7 +303,8 @@ input_parse_key_binding(struct xkb_keymap *keymap, const char *combos, if (sym == XKB_KEY_NoSymbol) { LOG_ERR("%s: key binding is not a valid XKB symbol name", key); - break; + free(copy); + return false; } /* @@ -353,6 +354,90 @@ input_parse_key_binding(struct xkb_keymap *keymap, const char *combos, return true; } +bool +input_parse_mouse_binding(struct xkb_keymap *keymap, const char *combos, + enum bind_action_normal action, + mouse_binding_list_t *bindings) +{ + if (combos == NULL) + return true; + + char *copy = xstrdup(combos); + for (char *save1 = NULL, *combo = strtok_r(copy, " ", &save1); + combo != NULL; + combo = strtok_r(NULL, " ", &save1)) + { + xkb_mod_mask_t mod_mask = 0; + int button = 0; + + 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; + } + + else { + /* Button */ + 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}, + }; + + 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_ERR("%s: invalid mouse button name", key); + free(copy); + return false; + } + } + } + + if (button == 0) + continue; + + if (bindings != NULL) { + const struct mouse_binding binding = { + .action = action, + .mods = mod_mask, + .button = button, + .count = 1, + }; + + 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) @@ -402,6 +487,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, tll_free(it->item.bind.key_codes); tll_free(seat->kbd.bindings.search); + tll_free(seat->mouse.bindings); + seat->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); seat->kbd.xkb_keymap = xkb_keymap_new_from_buffer( seat->kbd.xkb, map_str, size, XKB_KEYMAP_FORMAT_TEXT_V1, @@ -427,7 +514,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, tll_foreach(wayl->conf->bindings.key, it) { key_binding_list_t bindings = tll_init(); input_parse_key_binding( - seat->kbd.xkb_keymap, it->item.key, &bindings); + seat->kbd.xkb_keymap, it->item.combos, &bindings); tll_foreach(bindings, it2) { tll_push_back( @@ -443,7 +530,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, 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); + seat->kbd.xkb_keymap, it->item.combos, &bindings); tll_foreach(bindings, it2) { tll_push_back( @@ -453,6 +540,12 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, } tll_free(bindings); } + + tll_foreach(wayl->conf->bindings.mouse, it) { + input_parse_mouse_binding( + seat->kbd.xkb_keymap, it->item.combos, it->item.action, + &seat->mouse.bindings); + } } static void @@ -1440,9 +1533,12 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 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) { 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); if (selection_enabled(term, seat) && cursor_is_on_grid) { @@ -1466,7 +1562,7 @@ 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) { selection_extend( seat, term, seat->mouse.col, seat->mouse.row, serial); @@ -1474,7 +1570,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } else { - tll_foreach(wayl->conf->bindings.mouse, it) { + tll_foreach(seat->mouse.bindings, it) { const struct mouse_binding *binding = &it->item; if (binding->button != button) { @@ -1482,6 +1578,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, continue; } + if (binding->mods != mods) { + /* Modifier mismatch */ + continue; + } + if (binding->count != seat->mouse.count) { /* Not correct click count */ continue; diff --git a/input.h b/input.h index c1c14f7c..37ad8452 100644 --- a/input.h +++ b/input.h @@ -12,3 +12,6 @@ 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); +bool input_parse_mouse_binding(struct xkb_keymap *keymap, const char *combos, + enum bind_action_normal action, + mouse_binding_list_t *bindings); diff --git a/wayland.c b/wayland.c index a10ed2b3..30271dd2 100644 --- a/wayland.c +++ b/wayland.c @@ -125,6 +125,8 @@ seat_destroy(struct seat *seat) tll_free(it->item.bind.key_codes); tll_free(seat->kbd.bindings.search); + tll_free(seat->mouse.bindings); + if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_unref(seat->kbd.xkb_compose_state); 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( wayl->registry, name, &wl_seat_interface, required); - /* Clipboard */ - /* Primary selection */ - tll_push_back(wayl->seats, ((struct seat){ .wayl = wayl, .wl_seat = wl_seat, diff --git a/wayland.h b/wayland.h index dc07fb7a..b08c25fd 100644 --- a/wayland.h +++ b/wayland.h @@ -53,9 +53,11 @@ struct key_binding_normal { struct mouse_binding { enum bind_action_normal action; + xkb_mod_mask_t mods; uint32_t button; int count; }; +typedef tll(struct mouse_binding) mouse_binding_list_t; enum bind_action_search { BIND_ACTION_SEARCH_NONE, @@ -173,6 +175,8 @@ struct seat { /* We used a discrete axis event in the current pointer frame */ double axis_aggregated; bool have_discrete; + + mouse_binding_list_t bindings; } mouse; /* Clipboard */