Merge branch 'configure-select-override-mods'

This commit is contained in:
Daniel Eklöf 2021-12-04 18:27:03 +01:00
commit ecfff42300
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
9 changed files with 213 additions and 41 deletions

View file

@ -37,6 +37,12 @@
## Unreleased
### Added
* `[mouse-bindings].selection-override-modifiers` option, specifying
which modifiers to hold to override mouse grabs by client
applications and force selection instead.
### Changed
### Deprecated
### Removed

196
config.c
View file

@ -1333,6 +1333,10 @@ parse_section_cursor(struct context *ctx)
return true;
}
static bool
parse_modifiers(struct context *ctx, const char *text, size_t len,
struct config_key_modifiers *modifiers);
static bool
parse_section_mouse(struct context *ctx)
{
@ -1474,6 +1478,11 @@ parse_modifiers(struct context *ctx, const char *text, size_t len,
bool ret = false;
*modifiers = (struct config_key_modifiers){0};
/* Handle "none" separately because e.g. none+shift is nonsense */
if (strncmp(text, "none", len) == 0)
return true;
char *copy = xstrndup(text, len);
for (char *tok_ctx = NULL, *key = strtok_r(copy, "+", &tok_ctx);
@ -1947,6 +1956,31 @@ parse_section_url_bindings(struct context *ctx)
&ctx->conf->bindings.url);
}
static const struct {
const char *name;
int code;
} button_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},
};
static const char*
mouse_event_code_get_name(int code)
{
for (size_t i = 0; i < ALEN(button_map); i++) {
if (code == button_map[i].code)
return button_map[i].name;
}
return NULL;
}
static bool
value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
{
@ -1971,10 +2005,6 @@ value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
*key = '\0';
if (!parse_modifiers(ctx, combo, key - combo, &modifiers))
goto err;
if (modifiers.shift) {
LOG_CONTEXTUAL_ERR("Shift cannot be used in mouse bindings");
goto err;
}
key++; /* Skip past the '+' */
}
@ -1999,24 +2029,10 @@ value_to_mouse_combos(struct context *ctx, struct key_combo_list *key_combos)
}
}
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;
for (size_t i = 0; i < ALEN(button_map); i++) {
if (strcmp(key, button_map[i].name) == 0) {
button = button_map[i].code;
break;
}
}
@ -2054,6 +2070,98 @@ err:
return false;
}
static bool
modifiers_equal(const struct config_key_modifiers *mods1,
const struct config_key_modifiers *mods2)
{
bool shift = mods1->shift == mods2->shift;
bool alt = mods1->alt == mods2->alt;
bool ctrl = mods1->ctrl == mods2->ctrl;
bool meta = mods1->meta == mods2->meta;
return shift && alt && ctrl && meta;
}
static bool
modifiers_disjoint(const struct config_key_modifiers *mods1,
const struct config_key_modifiers *mods2)
{
bool shift = mods1->shift && mods2->shift;
bool alt = mods1->alt && mods2->alt;
bool ctrl = mods1->ctrl && mods2->ctrl;
bool meta = mods1->meta && mods2->meta;
return !(shift || alt || ctrl || meta);
}
static char *
modifiers_to_str(const struct config_key_modifiers *mods)
{
char *ret = xasprintf("%s%s%s%s",
mods->ctrl ? "Control+" : "",
mods->alt ? "Alt+": "",
mods->meta ? "Meta+": "",
mods->shift ? "Shift+": "");
ret[strlen(ret) - 1] = '\0';
return ret;
}
static char *
mouse_combo_to_str(const struct key_combo *combo)
{
char *combo_modifiers_str = modifiers_to_str(&combo->modifiers);
const char *combo_button_str = mouse_event_code_get_name(combo->m.button);
xassert(combo_button_str != NULL);
char *ret;
if (combo->m.count == 1)
ret = xasprintf("%s+%s", combo_modifiers_str, combo_button_str);
else
ret = xasprintf("%s+%s-%d",
combo_modifiers_str,
combo_button_str,
combo->m.count);
free (combo_modifiers_str);
return ret;
}
static bool
selection_override_interferes_with_mouse_binding(struct context *ctx,
int action,
const struct key_combo_list *key_combos,
bool blame_modifiers)
{
struct config *conf = ctx->conf;
if (action == BIND_ACTION_NONE)
return false;
const struct config_key_modifiers *override_mods =
&conf->mouse.selection_override_modifiers;
for (size_t i = 0; i < key_combos->count; i++) {
const struct key_combo *combo = &key_combos->combos[i];
if (!modifiers_disjoint(&combo->modifiers, override_mods)) {
char *modifiers_str = modifiers_to_str(override_mods);
char *combo_str = mouse_combo_to_str(combo);
if (blame_modifiers) {
LOG_CONTEXTUAL_ERR(
"modifiers conflict with existing binding %s=%s",
binding_action_map[action],
combo_str);
} else {
LOG_CONTEXTUAL_ERR(
"binding conflicts with selection override modifiers (%s)",
modifiers_str);
}
free (modifiers_str);
free (combo_str);
return false;
}
}
return false;
}
static bool
has_mouse_binding_collisions(struct context *ctx,
const struct key_combo_list *key_combos)
@ -2071,14 +2179,10 @@ has_mouse_binding_collisions(struct context *ctx,
const struct config_key_modifiers *mods1 = &combo1->modifiers;
const struct config_key_modifiers *mods2 = &combo2->modifiers;
bool shift = mods1->shift == mods2->shift;
bool alt = mods1->alt == mods2->alt;
bool ctrl = mods1->ctrl == mods2->ctrl;
bool meta = mods1->meta == mods2->meta;
bool button = combo1->button == combo2->m.button;
bool count = combo1->count == combo2->m.count;
if (shift && alt && ctrl && meta && button && count) {
if (modifiers_equal(mods1, mods2) && button && count) {
bool has_pipe = combo1->pipe.argv.args != NULL;
LOG_CONTEXTUAL_ERR("%s already mapped to '%s%s%s%s'",
combo2->text,
@ -2102,6 +2206,37 @@ parse_section_mouse_bindings(struct context *ctx)
const char *key = ctx->key;
const char *value = ctx->value;
if (strcmp(ctx->key, "selection-override-modifiers") == 0) {
if (!parse_modifiers(ctx, ctx->value, strlen(ctx->value),
&conf->mouse.selection_override_modifiers)) {
LOG_CONTEXTUAL_ERR("%s: invalid modifiers '%s'", key, ctx->value);
return false;
}
/* Ensure no existing bindings use these modifiers */
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
const struct config_mouse_binding *binding = &conf->bindings.mouse.arr[i];
struct key_combo combo = {
.modifiers = binding->modifiers,
.m = {
.button = binding->button,
.count = binding->count,
},
};
struct key_combo_list key_combos = {
.count = 1,
.combos = &combo,
};
if (selection_override_interferes_with_mouse_binding(ctx, binding->action, &key_combos, true)) {
return false;
}
}
return true;
}
struct argv pipe_argv;
ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &pipe_argv);
@ -2136,7 +2271,8 @@ parse_section_mouse_bindings(struct context *ctx)
struct key_combo_list key_combos = {0};
if (!value_to_mouse_combos(ctx, &key_combos) ||
has_mouse_binding_collisions(ctx, &key_combos))
has_mouse_binding_collisions(ctx, &key_combos) ||
selection_override_interferes_with_mouse_binding(ctx, action, &key_combos, false))
{
free_argv(&pipe_argv);
free_key_combo_list(&key_combos);
@ -2780,6 +2916,12 @@ config_load(struct config *conf, const char *conf_path,
.mouse = {
.hide_when_typing = false,
.alternate_scroll_mode = true,
.selection_override_modifiers = {
.shift = true,
.alt = false,
.ctrl = false,
.meta = false,
},
},
.csd = {
.preferred = CONF_CSD_PREFER_SERVER,

View file

@ -193,6 +193,7 @@ struct config {
struct {
bool hide_when_typing;
bool alternate_scroll_mode;
struct config_key_modifiers selection_override_modifiers;
} mouse;
struct {

View file

@ -865,10 +865,6 @@ of the modifiers *must* be valid XKB key names, and the button name
*must* be a valid libinput name. You can find the button names using
*libinput debug-events*.
Note that *Shift* cannot be used as a modifier in mouse bindings since
it is used to enable selection when the client application is grabbing
the mouse.
The trailing *COUNT* is optional and specifies the click count
required to trigger the binding. The default if *COUNT* is omitted is
_1_.
@ -879,7 +875,19 @@ say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since
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 used here as well.
*selection-override-modifiers*
The modifiers set in this set (which may be set to any combination
of modifiers, e.g. _mod1+mod2+mod3_, as well as _none_) are used
to enable selecting text with the mouse irrespective of whether a
client application currently has the mouse grabbed.
These modifiers cannot be used as modifiers in mouse bindings.
Because the order of bindings is significant, it is best to set
this prior to any other mouse bindings that might use modifiers in
the default set.
Default: _Shift_
The actions to which mouse combos can be bound are listed below. All
actions listed under *key-bindings* can be used here as well.
*select-begin*
Begin an interactive selection. The selection is finalized, and

View file

@ -169,6 +169,7 @@
# toggle-url-visible=t
[mouse-bindings]
# selection-override-modifiers=Shift
# primary-paste=BTN_MIDDLE
# select-begin=BTN_LEFT
# select-begin-block=Control+BTN_LEFT

14
input.c
View file

@ -701,6 +701,11 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN");
}
/* Set selection-override modmask from configured mods and seat's mod indices */
const struct config_key_modifiers* override_mods =
&wayl->conf->mouse.selection_override_modifiers;
seat->kbd.selection_override_modmask = conf_modifiers_to_mask(seat, override_mods);
munmap(map_str, size);
close(fd);
@ -983,7 +988,7 @@ UNITTEST
xassert(strcmp(info->seq, "\033[27;3;13~") == 0);
}
static void
void
get_current_modifiers(const struct seat *seat,
xkb_mod_mask_t *effective,
xkb_mod_mask_t *consumed, uint32_t key)
@ -2372,11 +2377,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
get_current_modifiers(seat, &mods, NULL, 0);
mods &= seat->kbd.bind_significant;
/* Ignore Shift when matching modifiers, since it is
* used to enable selection in mouse grabbing client
* applications */
if (seat->kbd.mod_shift != XKB_MOD_INVALID)
mods &= ~(1 << seat->kbd.mod_shift);
/* Ignore selection override modifiers when matching modifiers */
mods &= ~seat->kbd.selection_override_modmask;
const struct mouse_binding *match = NULL;

View file

@ -27,4 +27,9 @@ extern const struct wl_pointer_listener pointer_listener;
void input_repeat(struct seat *seat, uint32_t key);
void get_current_modifiers(const struct seat *seat,
xkb_mod_mask_t *effective,
xkb_mod_mask_t *consumed,
uint32_t key);
const char *xcursor_for_csd_border(struct terminal *term, int x, int y);

View file

@ -2826,10 +2826,15 @@ term_mouse_grabbed(const struct terminal *term, struct seat *seat)
/*
* Mouse is grabbed by us, regardless of whether mouse tracking has been enabled or not.
*/
xkb_mod_mask_t mods;
get_current_modifiers(seat, &mods, NULL, 0);
const xkb_mod_mask_t override_modmask = seat->kbd.selection_override_modmask;
bool override_mods_pressed = (mods & override_modmask) == override_modmask;
return term->mouse_tracking == MOUSE_NONE ||
(seat->kbd_focus == term &&
seat->kbd.shift &&
!seat->kbd.alt && /*!seat->kbd.ctrl &&*/ !seat->kbd.super);
(seat->kbd_focus == term && override_mods_pressed);
}
void

View file

@ -201,6 +201,8 @@ struct seat {
xkb_mod_mask_t bind_significant;
xkb_mod_mask_t kitty_significant;
xkb_mod_mask_t selection_override_modmask;
xkb_keycode_t key_arrow_up;
xkb_keycode_t key_arrow_down;