input: make wheel events mappable

Un-grabbed wheel events are now passed through the mouse binding
matching logic, instead of being hardcoded to scrolling the terminal
contents.

They are mappable through the BTN_BACK and BTN_FORWARD buttons.

Since they're not actually button *presses*, they never generate a
click count other than 1. This limitation is documented, but not
checked in the config. This means it's possible to create bindings
like "BTN_BACK+3" (i.e. triple "click"). They will however never
trigger.

The old, hardcoded logic is now accessible through the new
scrollback-up-mouse and scrollback-down-mouse mouse
bindings. They (obiously) default to BTN_BACK and BTN_FORWARD,
respectively.

Example usage: keep the default of scrolling terminal contents with
the wheel, when used without modifiers, but map Control+wheel to font
zoom in/out:

  [mouse-bindings]
  font-increase=Control+BTN_FORWARD
  font-decrease=Control+BTN_BACK

(this also keeps the default key bindings to zoom in/out; ctrl-+ and
ctrl+-)

Closes #1077
This commit is contained in:
Daniel Eklöf 2023-09-18 16:36:39 +02:00
parent f0f0d02bf7
commit fe7aa25ad8
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
6 changed files with 162 additions and 105 deletions

View file

@ -50,6 +50,13 @@
### Added
* Support for building with _wayland-protocols_ as a subproject.
* Mouse wheel scrolls can now be used in `mouse-bindings`
([#1077](1077)).
* New mouse bindings: `scrollback-up-mouse` and
`scrollback-down-mouse`, bound to `BTN_BACK` and `BTN_FORWARD`
respectively.
[1077]: https://codeberg.org/dnkl/foot/issues/1077
### Changed

View file

@ -120,6 +120,8 @@ static const char *const binding_action_map[] = {
[BIND_ACTION_UNICODE_INPUT] = "unicode-input",
/* Mouse-specific actions */
[BIND_ACTION_SCROLLBACK_UP_MOUSE] = "scrollback-up-mouse",
[BIND_ACTION_SCROLLBACK_DOWN_MOUSE] = "scrollback-down-mouse",
[BIND_ACTION_SELECT_BEGIN] = "select-begin",
[BIND_ACTION_SELECT_BEGIN_BLOCK] = "select-begin-block",
[BIND_ACTION_SELECT_EXTEND] = "select-extend",
@ -2871,6 +2873,8 @@ static void
add_default_mouse_bindings(struct config *conf)
{
static const struct config_key_binding bindings[] = {
{BIND_ACTION_SCROLLBACK_UP_MOUSE, m_none, {.m = {BTN_BACK, 1}}},
{BIND_ACTION_SCROLLBACK_DOWN_MOUSE, m_none, {.m = {BTN_FORWARD, 1}}},
{BIND_ACTION_PRIMARY_PASTE, m_none, {.m = {BTN_MIDDLE, 1}}},
{BIND_ACTION_SELECT_BEGIN, m_none, {.m = {BTN_LEFT, 1}}},
{BIND_ACTION_SELECT_BEGIN_BLOCK, m_ctrl, {.m = {BTN_LEFT, 1}}},

View file

@ -1032,9 +1032,14 @@ 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*.
The trailing *COUNT* is optional and specifies the click count
required to trigger the binding. The default if *COUNT* is omitted is
_1_.
The trailing *COUNT* (number of times the button has to be clicked) is
optional and specifies the click count required to trigger the
binding. The default if *COUNT* is omitted is _1_.
To map wheel events (i.e. scrolling), use the button names *BTN_BACK*
(up) and *BTN_FORWARD* (down). Note that these events never generate a
*COUNT* larger than 1. That is, *BTN_BACK+2*, for example, will never
trigger.
A modifier+button combination can only be mapped to *one* action. Lets
say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since
@ -1056,6 +1061,22 @@ _action=none_; e.g. *primary-paste=none*.
The actions to which mouse combos can be bound are listed below. All
actions listed under *key-bindings* can be used here as well.
*scrollback-up-mouse*
Normal screen: scrolls up the contents.
Alt screen: send fake _KeyUP_ events to the client application, if
alternate scroll mode is enabled.
Default: _BTN_BACK_
*scrollback-down-mouse*
Normal screen: scrolls down the contents.
Alt screen: send fake _KeyDOWN_ events to the client application, if
alternate scroll mode is enabled.
Default: _BTN_FORWARD_
*select-begin*
Begin an interactive selection. The selection is finalized, and
copied to the _primary selection_, when the button is

View file

@ -190,6 +190,8 @@
# \x03=Mod4+c # Map Super+c -> Ctrl+c
[mouse-bindings]
# scrollback-up-mouse=BTN_BACK
# scrollback-down-mouse=BTN_FORWARD
# selection-override-modifiers=Shift
# primary-paste=BTN_MIDDLE
# select-begin=BTN_LEFT

225
input.c
View file

@ -81,9 +81,11 @@ pipe_closed:
return true;
}
static void alternate_scroll(struct seat *seat, int amount, int button);
static bool
execute_binding(struct seat *seat, struct terminal *term,
const struct key_binding *binding, uint32_t serial)
const struct key_binding *binding, uint32_t serial, int amount)
{
const enum bind_action_normal action = binding->action;
@ -115,6 +117,14 @@ execute_binding(struct seat *seat, struct terminal *term,
}
break;
case BIND_ACTION_SCROLLBACK_UP_MOUSE:
if (term->grid == &term->alt) {
if (term->alt_scrolling)
alternate_scroll(seat, amount, BTN_BACK);
} else
cmd_scrollback_up(term, amount);
break;
case BIND_ACTION_SCROLLBACK_DOWN_PAGE:
if (term->grid == &term->normal) {
cmd_scrollback_down(term, term->rows);
@ -136,6 +146,14 @@ execute_binding(struct seat *seat, struct terminal *term,
}
break;
case BIND_ACTION_SCROLLBACK_DOWN_MOUSE:
if (term->grid == &term->alt) {
if (term->alt_scrolling)
alternate_scroll(seat, amount, BTN_FORWARD);
} else
cmd_scrollback_down(term, amount);
break;
case BIND_ACTION_SCROLLBACK_HOME:
if (term->grid == &term->normal) {
cmd_scrollback_up(term, term->grid->num_rows);
@ -1478,7 +1496,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
/* Match translated symbol */
if (bind->k.sym == sym &&
bind->mods == (bind_mods & ~bind_consumed) &&
execute_binding(seat, term, bind, serial))
execute_binding(seat, term, bind, serial, 1))
{
goto maybe_repeat;
}
@ -1489,7 +1507,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
/* Match untranslated symbols */
for (size_t i = 0; i < raw_count; i++) {
if (bind->k.sym == raw_syms[i] &&
execute_binding(seat, term, bind, serial))
execute_binding(seat, term, bind, serial, 1))
{
goto maybe_repeat;
}
@ -1498,7 +1516,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
/* Match raw key code */
tll_foreach(bind->k.key_codes, code) {
if (code->item == key &&
execute_binding(seat, term, bind, serial))
execute_binding(seat, term, bind, serial, 1))
{
goto maybe_repeat;
}
@ -2152,6 +2170,95 @@ fdm_csd_move(struct fdm *fdm, int fd, int events, void *data)
return true;
}
static const struct key_binding *
match_mouse_binding(const struct seat *seat, const struct terminal *term,
int button)
{
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
/* Seat has keyboard - use mouse bindings *with* modifiers */
const struct key_binding_set *bindings =
key_binding_for(term->wl->key_binding_manager, term->conf, seat);
xassert(bindings != NULL);
xkb_mod_mask_t mods;
get_current_modifiers(seat, &mods, NULL, 0);
mods &= seat->kbd.bind_significant;
/* Ignore selection override modifiers when
* matching modifiers */
mods &= ~bindings->selection_overrides;
const struct key_binding *match = NULL;
tll_foreach(bindings->mouse, it) {
const struct key_binding *binding = &it->item;
if (binding->m.button != button) {
/* Wrong button */
continue;
}
if (binding->mods != mods) {
/* Modifier mismatch */
continue;
}
if (binding->m.count > seat->mouse.count) {
/* Not correct click count */
continue;
}
if (match == NULL || binding->m.count > match->m.count)
match = binding;
}
return match;
}
else {
/* Seat does NOT have a keyboard - use mouse bindings *without*
* modifiers */
const struct config_key_binding *match = NULL;
const struct config *conf = term->conf;
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
const struct config_key_binding *binding =
&conf->bindings.mouse.arr[i];
if (binding->m.button != button) {
/* Wrong button */
continue;
}
if (binding->m.count > seat->mouse.count) {
/* Incorrect click count */
continue;
}
const struct config_key_modifiers no_mods = {0};
if (memcmp(&binding->modifiers, &no_mods, sizeof(no_mods)) != 0) {
/* Binding has modifiers */
continue;
}
if (match == NULL || binding->m.count > match->m.count)
match = binding;
}
if (match != NULL) {
static struct key_binding bind;
bind.action = match->action;
bind.aux = &match->aux;
return &bind;
}
return NULL;
}
BUG("should not get here");
}
static void
wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
@ -2426,86 +2533,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
bool consumed = false;
if (cursor_is_on_grid && term_mouse_grabbed(term, seat)) {
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
/* Seat has keyboard - use mouse bindings *with* modifiers */
const struct key_binding *match =
match_mouse_binding(seat, term, button);
const struct key_binding_set *bindings = key_binding_for(
wayl->key_binding_manager, term->conf, seat);
xassert(bindings != NULL);
xkb_mod_mask_t mods;
get_current_modifiers(seat, &mods, NULL, 0);
mods &= seat->kbd.bind_significant;
/* Ignore selection override modifiers when
* matching modifiers */
mods &= ~bindings->selection_overrides;
const struct key_binding *match = NULL;
tll_foreach(bindings->mouse, it) {
const struct key_binding *binding = &it->item;
if (binding->m.button != button) {
/* Wrong button */
continue;
}
if (binding->mods != mods) {
/* Modifier mismatch */
continue;
}
if (binding->m.count > seat->mouse.count) {
/* Not correct click count */
continue;
}
if (match == NULL || binding->m.count > match->m.count)
match = binding;
}
if (match != NULL)
consumed = execute_binding(seat, term, match, serial);
}
else {
/* Seat does NOT have a keyboard - use mouse bindings *without* modifiers */
const struct config_key_binding *match = NULL;
const struct config *conf = term->conf;
for (size_t i = 0; i < conf->bindings.mouse.count; i++) {
const struct config_key_binding *binding =
&conf->bindings.mouse.arr[i];
if (binding->m.button != button) {
/* Wrong button */
continue;
}
if (binding->m.count > seat->mouse.count) {
/* Incorrect click count */
continue;
}
const struct config_key_modifiers no_mods = {0};
if (memcmp(&binding->modifiers, &no_mods, sizeof(no_mods)) != 0) {
/* Binding has modifiers */
continue;
}
if (match == NULL || binding->m.count > match->m.count)
match = binding;
}
if (match != NULL) {
struct key_binding bind = {
.action = match->action,
.aux = &match->aux,
};
consumed = execute_binding(seat, term, &bind, serial);
}
}
if (match != NULL)
consumed = execute_binding(seat, term, match, serial, 1);
}
send_to_client = !consumed && cursor_is_on_grid;
@ -2580,26 +2612,15 @@ mouse_scroll(struct seat *seat, int amount, enum wl_pointer_axis axis)
amount = abs(amount);
if (term_mouse_grabbed(term, seat)) {
if (term->grid == &term->alt) {
if (term->alt_scrolling) {
switch (button) {
case BTN_BACK:
case BTN_FORWARD:
alternate_scroll(seat, amount, button);
break;
}
}
} else {
switch (button) {
case BTN_BACK:
cmd_scrollback_up(term, amount);
break;
seat->mouse.count = 1;
case BTN_FORWARD:
cmd_scrollback_down(term, amount);
break;
}
}
const struct key_binding *match =
match_mouse_binding(seat, term, button);
if (match != NULL)
execute_binding(seat, term, match, seat->pointer.serial, amount);
seat->mouse.last_released_button = button;
}
else if (seat->mouse.col >= 0 && seat->mouse.row >= 0) {

View file

@ -41,6 +41,8 @@ enum bind_action_normal {
BIND_ACTION_UNICODE_INPUT,
/* Mouse specific actions - i.e. they require a mouse coordinate */
BIND_ACTION_SCROLLBACK_UP_MOUSE,
BIND_ACTION_SCROLLBACK_DOWN_MOUSE,
BIND_ACTION_SELECT_BEGIN,
BIND_ACTION_SELECT_BEGIN_BLOCK,
BIND_ACTION_SELECT_EXTEND,