diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb25068..31a28a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/config.c b/config.c index 129fe99a..aeea0b32 100644 --- a/config.c +++ b/config.c @@ -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}}}, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 8726da0c..085dd82f 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -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 diff --git a/foot.ini b/foot.ini index 359b2cf7..262556e7 100644 --- a/foot.ini +++ b/foot.ini @@ -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 diff --git a/input.c b/input.c index c95d3f28..51b75233 100644 --- a/input.c +++ b/input.c @@ -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) { diff --git a/key-binding.h b/key-binding.h index f607644f..4d3ac541 100644 --- a/key-binding.h +++ b/key-binding.h @@ -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,