mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-25 09:05:47 -04:00
input: rework mouse button/motion handling
Store a list of currently pressed buttons, and which surface they belong to (i.e. which surface that received the press). Then, in motion events (with a button pressed, aka drag operations), send the event to the “original” surface (that received the press). Also send release events to the originating surface. This means a surface receiving a press will always receive a corresponding release. And no one will receive a release event without a corresponding press event. Motion events with a button pressed will *always* use the *first* button that was pressed. I.e. if you press a button, start dragging, and then press another button, we keep generating motion events for the *first* button.
This commit is contained in:
parent
a1a0b489ee
commit
ff96ce1e91
4 changed files with 145 additions and 115 deletions
231
input.c
231
input.c
|
|
@ -1250,8 +1250,9 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
|
||||||
/* Reset mouse state */
|
/* Reset mouse state */
|
||||||
seat->mouse.x = seat->mouse.y = 0;
|
seat->mouse.x = seat->mouse.y = 0;
|
||||||
seat->mouse.col = seat->mouse.row = 0;
|
seat->mouse.col = seat->mouse.row = 0;
|
||||||
seat->mouse.button = seat->mouse.last_button = seat->mouse.button_for_motion_events = seat->mouse.count = 0;
|
tll_free(seat->mouse.buttons);
|
||||||
seat->mouse.consumed = false;
|
seat->mouse.count = 0;
|
||||||
|
seat->mouse.last_released_button = 0;
|
||||||
memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time));
|
memset(&seat->mouse.last_time, 0, sizeof(seat->mouse.last_time));
|
||||||
seat->mouse.axis_aggregated = 0.0;
|
seat->mouse.axis_aggregated = 0.0;
|
||||||
seat->mouse.have_discrete = false;
|
seat->mouse.have_discrete = false;
|
||||||
|
|
@ -1320,7 +1321,18 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
||||||
seat->mouse.x = x;
|
seat->mouse.x = x;
|
||||||
seat->mouse.y = y;
|
seat->mouse.y = y;
|
||||||
|
|
||||||
switch (term->active_surface) {
|
enum term_surface surf_kind = term->active_surface;
|
||||||
|
int button = 0;
|
||||||
|
bool send_to_client = false;
|
||||||
|
|
||||||
|
if (tll_length(seat->mouse.buttons) > 0) {
|
||||||
|
const struct button_tracker *tracker = &tll_front(seat->mouse.buttons);
|
||||||
|
surf_kind = tracker->surf_kind;
|
||||||
|
button = tracker->button;
|
||||||
|
send_to_client = tracker->send_to_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (surf_kind) {
|
||||||
case TERM_SURF_NONE:
|
case TERM_SURF_NONE:
|
||||||
case TERM_SURF_SEARCH:
|
case TERM_SURF_SEARCH:
|
||||||
case TERM_SURF_SCROLLBACK_INDICATOR:
|
case TERM_SURF_SCROLLBACK_INDICATOR:
|
||||||
|
|
@ -1334,7 +1346,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
||||||
/* We've started a 'move' timer, but user started dragging
|
/* We've started a 'move' timer, but user started dragging
|
||||||
* right away - abort the timer and initiate the actual move
|
* right away - abort the timer and initiate the actual move
|
||||||
* right away */
|
* right away */
|
||||||
if (seat->mouse.button == BTN_LEFT && win->csd.move_timeout_fd != -1) {
|
if (button == BTN_LEFT && win->csd.move_timeout_fd != -1) {
|
||||||
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
|
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
|
||||||
win->csd.move_timeout_fd = -1;
|
win->csd.move_timeout_fd = -1;
|
||||||
xdg_toplevel_move(win->xdg_toplevel, seat->wl_seat, win->csd.serial);
|
xdg_toplevel_move(win->xdg_toplevel, seat->wl_seat, win->csd.serial);
|
||||||
|
|
@ -1388,7 +1400,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
||||||
* pressed while the cursor was inside the grid area), then
|
* pressed while the cursor was inside the grid area), then
|
||||||
* make sure it receives valid coordinates.
|
* make sure it receives valid coordinates.
|
||||||
*/
|
*/
|
||||||
if (seat->mouse.button_for_motion_events > 0) {
|
if (send_to_client) {
|
||||||
seat->mouse.col = selection_col;
|
seat->mouse.col = selection_col;
|
||||||
seat->mouse.row = selection_row;
|
seat->mouse.row = selection_row;
|
||||||
}
|
}
|
||||||
|
|
@ -1443,24 +1455,24 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
||||||
auto_scroll_direction, selection_col);
|
auto_scroll_direction, selection_col);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor_is_on_new_cell || term->selection.end.row < 0)
|
if (term->selection.ongoing && (cursor_is_on_new_cell ||
|
||||||
|
term->selection.end.row < 0))
|
||||||
|
{
|
||||||
selection_update(term, selection_col, selection_row);
|
selection_update(term, selection_col, selection_row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send mouse event to client application */
|
/* Send mouse event to client application */
|
||||||
if (!seat->mouse.consumed &&
|
if (!term_mouse_grabbed(term, seat) &&
|
||||||
!term_mouse_grabbed(term, seat) &&
|
|
||||||
cursor_is_on_new_cell &&
|
cursor_is_on_new_cell &&
|
||||||
(seat->mouse.button_for_motion_events > 0 ||
|
((button == 0 && cursor_is_on_grid) ||
|
||||||
(seat->mouse.button == 0 && cursor_is_on_grid)))
|
(button != 0 && send_to_client)))
|
||||||
{
|
{
|
||||||
assert(seat->mouse.col < term->cols);
|
assert(seat->mouse.col < term->cols);
|
||||||
assert(seat->mouse.row < term->rows);
|
assert(seat->mouse.row < term->rows);
|
||||||
|
|
||||||
term_mouse_motion(
|
term_mouse_motion(
|
||||||
term,
|
term, button,
|
||||||
(seat->mouse.button_for_motion_events > 0
|
|
||||||
? seat->mouse.button_for_motion_events : seat->mouse.button),
|
|
||||||
seat->mouse.row, seat->mouse.col,
|
seat->mouse.row, seat->mouse.col,
|
||||||
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
||||||
}
|
}
|
||||||
|
|
@ -1504,29 +1516,54 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
|
|
||||||
assert(term != NULL);
|
assert(term != NULL);
|
||||||
|
|
||||||
/* Update double/triple click state */
|
enum term_surface surf_kind;
|
||||||
|
bool send_to_client = false;
|
||||||
|
|
||||||
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||||||
/* Time since last click */
|
/* Time since last click */
|
||||||
struct timeval now, since_last;
|
struct timeval now, since_last;
|
||||||
gettimeofday(&now, NULL);
|
gettimeofday(&now, NULL);
|
||||||
timersub(&now, &seat->mouse.last_time, &since_last);
|
timersub(&now, &seat->mouse.last_time, &since_last);
|
||||||
|
|
||||||
/* Double- or triple click? */
|
if (seat->mouse.last_released_button == button &&
|
||||||
if (button == seat->mouse.last_button &&
|
since_last.tv_sec == 0 && since_last.tv_usec <= 300 * 1000)
|
||||||
since_last.tv_sec == 0 &&
|
|
||||||
since_last.tv_usec <= 300 * 1000)
|
|
||||||
{
|
{
|
||||||
seat->mouse.count++;
|
seat->mouse.count++;
|
||||||
} else
|
} else
|
||||||
seat->mouse.count = 1;
|
seat->mouse.count = 1;
|
||||||
|
|
||||||
seat->mouse.button = button; /* For motion events */
|
#if defined(_DEBUG)
|
||||||
seat->mouse.last_button = button;
|
tll_foreach(seat->mouse.buttons, it)
|
||||||
seat->mouse.last_time = now;
|
assert(it->item.button != button);
|
||||||
} else
|
#endif
|
||||||
seat->mouse.button = 0; /* For motion events */
|
|
||||||
|
|
||||||
switch (term->active_surface) {
|
tll_push_back(
|
||||||
|
seat->mouse.buttons,
|
||||||
|
((struct button_tracker){
|
||||||
|
.button = button,
|
||||||
|
.surf_kind = term->active_surface,
|
||||||
|
.send_to_client = false}));
|
||||||
|
|
||||||
|
seat->mouse.last_time = now;
|
||||||
|
|
||||||
|
surf_kind = term->active_surface;
|
||||||
|
send_to_client = false; /* For now, may be set to true if a binding consumes the button */
|
||||||
|
} else {
|
||||||
|
bool UNUSED have_button = false;
|
||||||
|
tll_foreach(seat->mouse.buttons, it) {
|
||||||
|
if (it->item.button == button) {
|
||||||
|
have_button = true;
|
||||||
|
surf_kind = it->item.surf_kind;
|
||||||
|
send_to_client = it->item.send_to_client;
|
||||||
|
tll_remove(seat->mouse.buttons, it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(have_button);
|
||||||
|
seat->mouse.last_released_button = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (surf_kind) {
|
||||||
case TERM_SURF_TITLE:
|
case TERM_SURF_TITLE:
|
||||||
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||||||
|
|
||||||
|
|
@ -1633,93 +1670,94 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
||||||
if (!seat->mouse.consumed) {
|
bool consumed = false;
|
||||||
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
|
|
||||||
/* Seat has keyboard - use mouse bindings *with* modifiers */
|
|
||||||
|
|
||||||
xkb_mod_mask_t mods = xkb_state_serialize_mods(
|
if (seat->wl_keyboard != NULL && seat->kbd.xkb_state != NULL) {
|
||||||
seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED);
|
/* Seat has keyboard - use mouse bindings *with* modifiers */
|
||||||
|
|
||||||
/* Ignore Shift when matching modifiers, since it is
|
xkb_mod_mask_t mods = xkb_state_serialize_mods(
|
||||||
* used to enable selection in mouse grabbing client
|
seat->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED);
|
||||||
* applications */
|
|
||||||
mods &= ~(1 << seat->kbd.mod_shift);
|
|
||||||
|
|
||||||
const struct mouse_binding *match = NULL;
|
/* 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 *match = NULL;
|
||||||
const struct mouse_binding *binding = &it->item;
|
|
||||||
|
|
||||||
if (binding->button != button) {
|
tll_foreach(seat->mouse.bindings, it) {
|
||||||
/* Wrong button */
|
const struct mouse_binding *binding = &it->item;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (binding->mods != mods) {
|
if (binding->button != button) {
|
||||||
/* Modifier mismatch */
|
/* Wrong button */
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (binding->count > seat->mouse.count) {
|
|
||||||
/* Not correct click count */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match == NULL || binding->count > match->count)
|
|
||||||
match = binding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match != NULL) {
|
if (binding->mods != mods) {
|
||||||
seat->mouse.consumed = execute_binding(
|
/* Modifier mismatch */
|
||||||
seat, term, match->action, match->pipe_argv, serial);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (binding->count > seat->mouse.count) {
|
||||||
|
/* Not correct click count */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match == NULL || binding->count > match->count)
|
||||||
|
match = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
if (match != NULL) {
|
||||||
/* Seat does NOT have a keyboard - use mouse bindings *without* modifiers */
|
consumed = execute_binding(
|
||||||
const struct config_mouse_binding *match = NULL;
|
seat, term, match->action, match->pipe_argv, serial);
|
||||||
|
|
||||||
tll_foreach(seat->wayl->conf->bindings.mouse, it) {
|
|
||||||
const struct config_mouse_binding *binding = &it->item;
|
|
||||||
|
|
||||||
if (binding->button != button) {
|
|
||||||
/* Wrong button */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (binding->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->count > match->count)
|
|
||||||
match = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match != NULL) {
|
|
||||||
seat->mouse.consumed = execute_binding(
|
|
||||||
seat, term, match->action, match->pipe.argv, serial);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seat->mouse.consumed &&
|
else {
|
||||||
|
/* Seat does NOT have a keyboard - use mouse bindings *without* modifiers */
|
||||||
|
const struct config_mouse_binding *match = NULL;
|
||||||
|
|
||||||
|
tll_foreach(seat->wayl->conf->bindings.mouse, it) {
|
||||||
|
const struct config_mouse_binding *binding = &it->item;
|
||||||
|
|
||||||
|
if (binding->button != button) {
|
||||||
|
/* Wrong button */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding->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->count > match->count)
|
||||||
|
match = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match != NULL) {
|
||||||
|
consumed = execute_binding(
|
||||||
|
seat, term, match->action, match->pipe.argv, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send_to_client = !consumed && cursor_is_on_grid;
|
||||||
|
|
||||||
|
if (send_to_client)
|
||||||
|
tll_back(seat->mouse.buttons).send_to_client = true;
|
||||||
|
|
||||||
|
if (send_to_client &&
|
||||||
!term_mouse_grabbed(term, seat) &&
|
!term_mouse_grabbed(term, seat) &&
|
||||||
cursor_is_on_grid)
|
cursor_is_on_grid)
|
||||||
{
|
{
|
||||||
term_mouse_down(
|
term_mouse_down(
|
||||||
term, button, seat->mouse.row, seat->mouse.col,
|
term, button, seat->mouse.row, seat->mouse.col,
|
||||||
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
||||||
|
|
||||||
if (seat->mouse.button_for_motion_events == 0)
|
|
||||||
seat->mouse.button_for_motion_events = button;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1727,20 +1765,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
case WL_POINTER_BUTTON_STATE_RELEASED:
|
case WL_POINTER_BUTTON_STATE_RELEASED:
|
||||||
selection_finalize(seat, term, serial);
|
selection_finalize(seat, term, serial);
|
||||||
|
|
||||||
if (!seat->mouse.consumed &&
|
if (send_to_client && !term_mouse_grabbed(term, seat)) {
|
||||||
!term_mouse_grabbed(term, seat) &&
|
|
||||||
((cursor_is_on_grid && seat->mouse.button_for_motion_events > 0) ||
|
|
||||||
seat->mouse.button_for_motion_events == button))
|
|
||||||
{
|
|
||||||
term_mouse_up(
|
term_mouse_up(
|
||||||
term, button, seat->mouse.row, seat->mouse.col,
|
term, button, seat->mouse.row, seat->mouse.col,
|
||||||
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
seat->kbd.shift, seat->kbd.alt, seat->kbd.ctrl);
|
||||||
|
|
||||||
if (seat->mouse.button_for_motion_events == button)
|
|
||||||
seat->mouse.button_for_motion_events = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seat->mouse.consumed = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,8 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial)
|
||||||
if (!term->selection.ongoing)
|
if (!term->selection.ongoing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
LOG_DBG("selection finalize");
|
||||||
|
|
||||||
selection_stop_scroll_timer(term);
|
selection_stop_scroll_timer(term);
|
||||||
term->selection.ongoing = false;
|
term->selection.ongoing = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,8 @@ seat_destroy(struct seat *seat)
|
||||||
if (seat == NULL)
|
if (seat == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
tll_free(seat->mouse.buttons);
|
||||||
|
|
||||||
tll_foreach(seat->kbd.bindings.key, it)
|
tll_foreach(seat->kbd.bindings.key, it)
|
||||||
tll_free(it->item.bind.key_codes);
|
tll_free(it->item.bind.key_codes);
|
||||||
tll_free(seat->kbd.bindings.key);
|
tll_free(seat->kbd.bindings.key);
|
||||||
|
|
|
||||||
25
wayland.h
25
wayland.h
|
|
@ -131,6 +131,13 @@ struct wl_primary {
|
||||||
uint32_t serial;
|
uint32_t serial;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Maps a mouse button to its "owning" surface */
|
||||||
|
struct button_tracker {
|
||||||
|
int button;
|
||||||
|
int surf_kind; /* TODO: this is really an "enum term_surface" */
|
||||||
|
bool send_to_client; /* Only valid when surface is the main grid surface */
|
||||||
|
};
|
||||||
|
|
||||||
struct seat {
|
struct seat {
|
||||||
struct wayland *wayl;
|
struct wayland *wayl;
|
||||||
struct wl_seat *wl_seat;
|
struct wl_seat *wl_seat;
|
||||||
|
|
@ -201,23 +208,13 @@ struct seat {
|
||||||
int y;
|
int y;
|
||||||
int col;
|
int col;
|
||||||
int row;
|
int row;
|
||||||
int button;
|
|
||||||
|
|
||||||
/*
|
/* Mouse buttons currently being pressed, and their "owning" surfaces */
|
||||||
* Button to send in motion events to the client. This is
|
tll(struct button_tracker) buttons;
|
||||||
* always the *first* button pressed on the grid. If multiple
|
|
||||||
* buttons are pressed, the first button is still the one used
|
|
||||||
* in motion events.
|
|
||||||
*
|
|
||||||
* A non-zero value of this *also* indicates that the client
|
|
||||||
* should receive events even if the pointer is outside the
|
|
||||||
* grid.
|
|
||||||
*/
|
|
||||||
int button_for_motion_events;
|
|
||||||
bool consumed; /* True if a button press was consumed - i.e. if a binding claimed it */
|
|
||||||
|
|
||||||
|
/* Double- and triple click state */
|
||||||
int count;
|
int count;
|
||||||
int last_button;
|
int last_released_button;
|
||||||
struct timeval last_time;
|
struct timeval last_time;
|
||||||
|
|
||||||
/* We used a discrete axis event in the current pointer frame */
|
/* We used a discrete axis event in the current pointer frame */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue