diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b7aba5..9351816b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,16 @@ (https://codeberg.org/dnkl/foot/issues/77). * Click count support in mouse bindings, i.e double- and triple-click (https://codeberg.org/dnkl/foot/issues/78). +* Modifier support in mouse bindings + (https://codeberg.org/dnkl/foot/issues/77). +* Click count support in mouse bindings, i.e double- and triple-click + (https://codeberg.org/dnkl/foot/issues/78). +* All mouse actions (begin selection, select word, select row etc) are + now configurable, via the new **select-begin**, + **select-begin-block**, **select-extend**, **select-word**, + **select-word-whitespace** and **select-row** options in the + **mouse-bindings** section in `footrc` + (https://codeberg.org/dnkl/foot/issues/79). ### Deprecated diff --git a/config.c b/config.c index 8e51b608..40a7e81b 100644 --- a/config.c +++ b/config.c @@ -69,6 +69,14 @@ static const char *const binding_action_map[] = { [BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback", [BIND_ACTION_PIPE_VIEW] = "pipe-visible", [BIND_ACTION_PIPE_SELECTED] = "pipe-selected", + + /* Mouse-specific actions */ + [BIND_ACTION_SELECT_BEGIN] = "select-begin", + [BIND_ACTION_SELECT_BEGIN_BLOCK] = "select-begin-block", + [BIND_ACTION_SELECT_EXTEND] = "select-extend", + [BIND_ACTION_SELECT_WORD] = "select-word", + [BIND_ACTION_SELECT_WORD_WS] = "select-word-whitespace", + [BIND_ACTION_SELECT_ROW] = "select-row", }; static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT, @@ -643,7 +651,8 @@ parse_section_csd(const char *key, const char *value, struct config *conf, } else { - LOG_AND_NOTIFY_ERR("%s:%u: [csd]: %s: invalid key", path, lineno, key); + LOG_AND_NOTIFY_ERR("%s:%u: [csd]: %s: invalid action", + path, lineno, key); return false; } @@ -859,7 +868,7 @@ parse_section_key_bindings( } for (enum bind_action_normal action = 0; - action < BIND_ACTION_COUNT; + action < BIND_ACTION_KEY_COUNT; action++) { if (binding_action_map[action] == NULL) @@ -932,7 +941,8 @@ parse_section_key_bindings( return true; } - LOG_AND_NOTIFY_ERR("%s:%u: [key-bindings]: %s: invalid key", path, lineno, key); + LOG_AND_NOTIFY_ERR("%s:%u: [key-bindings]: %s: invalid action", + path, lineno, key); return false; } @@ -1016,6 +1026,11 @@ parse_mouse_combos(struct config *conf, const char *combos, key_combo_list_t *ke *key = '\0'; if (!parse_modifiers(conf, combo, key - combo, &modifiers, path, lineno)) goto err; + if (modifiers.shift) { + LOG_AND_NOTIFY_ERR("%s:%d: Shift cannot be used in mosue bindings", + path, lineno); + goto err; + } key++; /* Skip past the '+' */ } @@ -1121,7 +1136,10 @@ parse_section_mouse_bindings( const char *key, const char *value, struct config *conf, const char *path, unsigned lineno) { - for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) { + for (enum bind_action_normal action = 0; + action < BIND_ACTION_COUNT; + action++) + { if (binding_action_map[action] == NULL) continue; @@ -1519,8 +1537,15 @@ add_default_mouse_bindings(struct config *conf) } while (0) const struct config_key_modifiers none = {}; + const struct config_key_modifiers ctrl = {.ctrl = true}; add_binding(BIND_ACTION_PRIMARY_PASTE, none, BTN_MIDDLE, 1); + add_binding(BIND_ACTION_SELECT_BEGIN, none, BTN_LEFT, 1); + add_binding(BIND_ACTION_SELECT_BEGIN_BLOCK, ctrl, BTN_LEFT, 1); + add_binding(BIND_ACTION_SELECT_EXTEND, none, BTN_RIGHT, 1); + add_binding(BIND_ACTION_SELECT_WORD, none, BTN_LEFT, 2); + add_binding(BIND_ACTION_SELECT_WORD_WS, ctrl, BTN_LEFT, 2); + add_binding(BIND_ACTION_SELECT_ROW, none, BTN_LEFT, 3); #undef add_binding } diff --git a/doc/footrc.5.scd b/doc/footrc.5.scd index 5f39f72d..d5ea808d 100644 --- a/doc/footrc.5.scd +++ b/doc/footrc.5.scd @@ -364,27 +364,53 @@ This section lets you override the default mouse bindings. The general format is _action=combo1...comboN_. That is, each action may have one or more key combinations, space separated. Each -combination is on the form _mod1+mod2+BTN\__[-COUNT]. The names -of the modifiers and the key *must* be valid XKB key names. You can -find the button names using *libinput debug-events*. +combination is on the form _mod1+mod2+BTN\_[-COUNT]_. The names +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 is *COUNT* is omitted is +required to trigger the binding. The default if *COUNT* is omitted is _1_. -Example: *primary-paste=Control+BTN_LEFT-2* - -Trigger _primary-paste_ when *ctrl* is pressed, and the left mouse -button is double clicked. - -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 -binding for *primary-paste*, you first need to unmap the default -binding. This can be done by setting _action=none_; -e.g. *primary-paste=none*. +A modifier+button combination can only be mapped to *one* action. Lets +say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since +*BTN\_MIDDLE* is the default binding for *primary-paste*, you first +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 user here as well. +*select-begin* + Begin an interactive selection. The selection is finalized, and + copied to the _primary selection_, when the button is + released. Default: _BTN\_LEFT_. + +*select-begin-block* + Begin an interactive block selection. The selection is finalized, + and copied to the _primary selection_, when the button is + released. Default: _Control+BTN\_LEFT_. + +*select-extend* + Interactively extend an existing selection. The selection is + finalized, and copied to the _primary selection_, when the button + is released. Default: _BTN\_RIGHT_. + +*select-word* + Select the _word_ (separated by spaces, period, comma, parenthesis + etc) under the pointer. Default: _BTN\_LEFT-2_. + +*select-word-whitespace* + Select the _word_ (separated by spaces _only_) under the + pointer. Default: Control+_BTN\_LEFT-2_. + +*select-row* + Select the whole row under the pointer. Default: _BTN\_LEFT-3_. + *primary-paste* Pastes from the _primary selection_. Default: _BTN\_MIDDLE_. diff --git a/footrc b/footrc index ec36bbc1..207ee657 100644 --- a/footrc +++ b/footrc @@ -93,3 +93,9 @@ [mouse-bindings] # primary-paste=BTN_MIDDLE +# select-begin=BTN_LEFT +# select-begin-block=Control+BTN_LEFT +# select-extend=BTN_RIGHT +# select-word=BTN_LEFT-2 +# select-word-whitespace=Control+BTN_LEFT-2 +# select-row=BTN_LEFT-3 diff --git a/input.c b/input.c index b7c5031a..3cd6830d 100644 --- a/input.c +++ b/input.c @@ -81,6 +81,8 @@ execute_binding(struct seat *seat, struct terminal *term, enum bind_action_normal action, char *const *pipe_argv, uint32_t serial) { + const bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0; + switch (action) { case BIND_ACTION_NONE: break; @@ -254,6 +256,46 @@ execute_binding(struct seat *seat, struct terminal *term, break; } + case BIND_ACTION_SELECT_BEGIN: + if (selection_enabled(term, seat) && cursor_is_on_grid) { + selection_start( + term, seat->mouse.col, seat->mouse.row, SELECTION_NORMAL); + } + break; + + case BIND_ACTION_SELECT_BEGIN_BLOCK: + if (selection_enabled(term, seat) && cursor_is_on_grid) { + selection_start( + term, seat->mouse.col, seat->mouse.row, SELECTION_BLOCK); + } + break; + + case BIND_ACTION_SELECT_EXTEND: + if (selection_enabled(term, seat) && cursor_is_on_grid) { + selection_extend( + seat, term, seat->mouse.col, seat->mouse.row, serial); + } + break; + + case BIND_ACTION_SELECT_WORD: + if (selection_enabled(term, seat) && cursor_is_on_grid) { + selection_mark_word( + seat, term, seat->mouse.col, seat->mouse.row, false, serial); + } + break; + + case BIND_ACTION_SELECT_WORD_WS: + if (selection_enabled(term, seat) && cursor_is_on_grid) { + selection_mark_word( + seat, term, seat->mouse.col, seat->mouse.row, true, serial); + } + break; + + case BIND_ACTION_SELECT_ROW: + if (selection_enabled(term, seat) && cursor_is_on_grid) + selection_mark_row(seat, term, seat->mouse.row, serial); + break; + case BIND_ACTION_COUNT: assert(false); break; @@ -1254,7 +1296,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0; /* Update selection */ - if (seat->mouse.button == BTN_LEFT || seat->mouse.button == BTN_RIGHT) { + if (!term->is_searching) { if (cursor_is_on_new_cell || term->selection.end.row < 0) selection_update(term, selection_col, selection_row); } @@ -1437,44 +1479,19 @@ 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 && mods == 0) { - selection_cancel(term); - - if (selection_enabled(term, seat) && cursor_is_on_grid) { - switch (seat->mouse.count) { - case 1: - selection_start( - term, seat->mouse.col, seat->mouse.row, - seat->kbd.ctrl ? SELECTION_BLOCK : SELECTION_NORMAL); - break; - - case 2: - selection_mark_word( - seat, term, seat->mouse.col, seat->mouse.row, - seat->kbd.ctrl, serial); - break; - - case 3: - selection_mark_row(seat, term, seat->mouse.row, serial); - break; - } - } - } - - 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); - } - } - - else if (seat->wl_keyboard != NULL) { + if (seat->wl_keyboard != NULL) { /* Seat has keyboard - use mouse bindings *with* modifiers */ + + xkb_mod_mask_t mods = xkb_state_serialize_mods( + seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED); + + /* Ignore Shift when matching modifiers, since it is + * used to enable selection in mouse grabbing client + * applications */ + mods &= ~(1 << seat->kbd.mod_shift); + tll_foreach(seat->mouse.bindings, it) { const struct mouse_binding *binding = &it->item; @@ -1533,8 +1550,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, } case WL_POINTER_BUTTON_STATE_RELEASED: - if (button == BTN_LEFT && term->selection.end.col != -1) - selection_finalize(seat, term, serial); + selection_finalize(seat, term, serial); if (!term_mouse_grabbed(term, seat) && cursor_is_on_grid) { term_mouse_up( diff --git a/selection.c b/selection.c index a078a698..0f1731e6 100644 --- a/selection.c +++ b/selection.c @@ -237,6 +237,7 @@ selection_start(struct terminal *term, int col, int row, term->selection.kind = kind; term->selection.start = (struct coord){col, term->grid->view + row}; term->selection.end = (struct coord){-1, -1}; + term->selection.ongoing = true; } static bool @@ -310,6 +311,9 @@ selection_update(struct terminal *term, int col, int row) if (term->selection.start.row < 0) return; + if (!term->selection.ongoing) + return; + LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d", term->selection.start.row, term->selection.start.col, term->selection.end.row, term->selection.end.col, @@ -532,6 +536,8 @@ selection_extend(struct seat *seat, struct terminal *term, return; } + term->selection.ongoing = true; + row += term->grid->view; if ((row == term->selection.start.row && col == term->selection.start.col) || @@ -563,6 +569,11 @@ selection_extend(struct seat *seat, struct terminal *term, void selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) { + if (!term->selection.ongoing) + return; + + term->selection.ongoing = false; + if (term->selection.start.row < 0 || term->selection.end.row < 0) return; @@ -600,6 +611,7 @@ selection_cancel(struct terminal *term) term->selection.start = (struct coord){-1, -1}; term->selection.end = (struct coord){-1, -1}; term->selection.direction = SELECTION_UNDIR; + term->selection.ongoing = false; } void diff --git a/terminal.h b/terminal.h index 8509634a..2f2d8881 100644 --- a/terminal.h +++ b/terminal.h @@ -311,6 +311,7 @@ struct terminal { enum selection_direction direction; struct coord start; struct coord end; + bool ongoing; } selection; bool is_searching; diff --git a/wayland.h b/wayland.h index b08c25fd..85cc1ca7 100644 --- a/wayland.h +++ b/wayland.h @@ -42,6 +42,17 @@ enum bind_action_normal { BIND_ACTION_PIPE_SCROLLBACK, BIND_ACTION_PIPE_VIEW, BIND_ACTION_PIPE_SELECTED, + + BIND_ACTION_KEY_COUNT = BIND_ACTION_PIPE_SELECTED, + + /* Mouse specific actions - i.e. they require a mouse coordinate */ + BIND_ACTION_SELECT_BEGIN, + BIND_ACTION_SELECT_BEGIN_BLOCK, + BIND_ACTION_SELECT_EXTEND, + BIND_ACTION_SELECT_WORD, + BIND_ACTION_SELECT_WORD_WS, + BIND_ACTION_SELECT_ROW, + BIND_ACTION_COUNT, };