selection: auto-scroll: selection keeps scrolling while mouse is outside grid

Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.

On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.

The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.

The timer FD is created and destroyed on-demand.

Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.

The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.

If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.

The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
This commit is contained in:
Daniel Eklöf 2020-10-11 15:44:20 +02:00
parent 2303affc87
commit 7fedf2f801
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 136 additions and 7 deletions

22
input.c
View file

@ -1328,17 +1328,25 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
const bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0; const bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0;
const bool scroll_up = y < term->margins.top; const bool scroll_up = y < term->margins.top;
const bool scroll_down = y >= term->height - term->margins.bottom; const bool scroll_down = y > term->height - term->margins.bottom;
if (!scroll_up && !scroll_down)
selection_stop_scroll_timer(term);
/* Update selection */ /* Update selection */
if (!term->is_searching) { if (!term->is_searching) {
if (scroll_up || scroll_down) { if (scroll_up || scroll_down) {
if (scroll_up) int distance = scroll_up
cmd_scrollback_up(term, 1); ? term->margins.top - y
if (scroll_down) : y - (term->height - term->margins.bottom);
cmd_scrollback_down(term, 1);
cursor_is_on_new_cell = true; assert(distance > 0);
distance /= term->scale;
selection_start_scroll_timer(
term, 100000000 / (distance > 0 ? distance : 1),
scroll_up ? SELECTION_SCROLL_UP : SELECTION_SCROLL_DOWN,
selection_col);
} }
if (cursor_is_on_new_cell || term->selection.end.row < 0) if (cursor_is_on_new_cell || term->selection.end.row < 0)

View file

@ -15,6 +15,7 @@
#include "log.h" #include "log.h"
#include "async.h" #include "async.h"
#include "commands.h"
#include "config.h" #include "config.h"
#include "extract.h" #include "extract.h"
#include "grid.h" #include "grid.h"
@ -619,6 +620,7 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial)
if (!term->selection.ongoing) if (!term->selection.ongoing)
return; return;
selection_stop_scroll_timer(term);
term->selection.ongoing = false; term->selection.ongoing = false;
if (term->selection.start.row < 0 || term->selection.end.row < 0) if (term->selection.start.row < 0 || term->selection.end.row < 0)
@ -647,6 +649,10 @@ selection_cancel(struct terminal *term)
term->selection.start.row, term->selection.start.col, term->selection.start.row, term->selection.start.col,
term->selection.end.row, term->selection.end.col); term->selection.end.row, term->selection.end.col);
if (term->selection.auto_scroll.fd >= 0) {
fdm_del(term->fdm, term->selection.auto_scroll.fd);
term->selection.auto_scroll.fd = -1;
}
if (term->selection.start.row >= 0 && term->selection.end.row >= 0) { if (term->selection.start.row >= 0 && term->selection.end.row >= 0) {
foreach_selected( foreach_selected(
@ -789,6 +795,103 @@ selection_mark_row(
selection_finalize(seat, term, serial); selection_finalize(seat, term, serial);
} }
static bool
fdm_scroll_timer(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t expiration_count;
ssize_t ret = read(
term->selection.auto_scroll.fd,
&expiration_count, sizeof(expiration_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read selection scroll timer");
return false;
}
switch (term->selection.auto_scroll.direction) {
case SELECTION_SCROLL_UP:
for (uint64_t i = 0; i < expiration_count; i++)
cmd_scrollback_up(term, 1);
selection_update(term, term->selection.auto_scroll.col, 0);
break;
case SELECTION_SCROLL_DOWN:
for (uint64_t i = 0; i < expiration_count; i++)
cmd_scrollback_down(term, 1);
selection_update(term, term->selection.auto_scroll.col, term->rows - 1);
break;
}
return true;
}
void
selection_start_scroll_timer(struct terminal *term, int interval_ns,
enum selection_scroll_direction direction, int col)
{
if (!term->selection.ongoing)
return;
if (term->selection.auto_scroll.fd < 0) {
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if (fd < 0) {
LOG_ERRNO("failed to create selection scroll timer");
goto err;
}
if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_scroll_timer, term)) {
close(fd);
return;
}
term->selection.auto_scroll.fd = fd;
}
struct itimerspec timer;
if (timerfd_gettime(term->selection.auto_scroll.fd, &timer) < 0) {
LOG_ERRNO("failed to get current selection scroll timer value");
goto err;
}
if (timer.it_value.tv_sec == 0 && timer.it_value.tv_nsec == 0)
timer.it_value.tv_nsec = 1;
timer.it_interval.tv_sec = interval_ns / 1000000000;
timer.it_interval.tv_nsec = interval_ns % 1000000000;
if (timerfd_settime(term->selection.auto_scroll.fd, 0, &timer, NULL) < 0) {
LOG_ERRNO("failed to set new selection scroll timer value");
goto err;
}
term->selection.auto_scroll.direction = direction;
term->selection.auto_scroll.col = col;
return;
err:
selection_stop_scroll_timer(term);
return;
}
void
selection_stop_scroll_timer(struct terminal *term)
{
if (term->selection.auto_scroll.fd < 0)
return;
fdm_del(term->fdm, term->selection.auto_scroll.fd);
term->selection.auto_scroll.fd = -1;
}
static void static void
target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) target(void *data, struct wl_data_source *wl_data_source, const char *mime_type)

View file

@ -74,3 +74,8 @@ void text_from_primary(
struct seat *seat, struct terminal *term, struct seat *seat, struct terminal *term,
void (*cb)(const char *data, size_t size, void *user), void (*cb)(const char *data, size_t size, void *user),
void (*dont)(void *user), void *user); void (*dont)(void *user), void *user);
void selection_start_scroll_timer(
struct terminal *term, int interval_ns,
enum selection_scroll_direction direction, int col);
void selection_stop_scroll_timer(struct terminal *term);

View file

@ -985,6 +985,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.selection = { .selection = {
.start = {-1, -1}, .start = {-1, -1},
.end = {-1, -1}, .end = {-1, -1},
.auto_scroll = {
.fd = -1,
},
}, },
.normal = {.scroll_damage = tll_init(), .sixel_images = tll_init()}, .normal = {.scroll_damage = tll_init(), .sixel_images = tll_init()},
.alt = {.scroll_damage = tll_init(), .sixel_images = tll_init()}, .alt = {.scroll_damage = tll_init(), .sixel_images = tll_init()},
@ -1163,6 +1166,7 @@ term_shutdown(struct terminal *term)
term_cursor_blink_disable(term); term_cursor_blink_disable(term);
fdm_del(term->fdm, term->selection.auto_scroll.fd);
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd); fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
fdm_del(term->fdm, term->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
fdm_del(term->fdm, term->delayed_render_timer.upper_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
@ -1175,6 +1179,7 @@ term_shutdown(struct terminal *term)
else else
close(term->ptmx); close(term->ptmx);
term->selection.auto_scroll.fd = -1;
term->render.app_sync_updates.timer_fd = -1; term->render.app_sync_updates.timer_fd = -1;
term->delayed_render_timer.lower_fd = -1; term->delayed_render_timer.lower_fd = -1;
term->delayed_render_timer.upper_fd = -1; term->delayed_render_timer.upper_fd = -1;
@ -1225,6 +1230,7 @@ term_destroy(struct terminal *term)
} }
} }
fdm_del(term->fdm, term->selection.auto_scroll.fd);
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd); fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
fdm_del(term->fdm, term->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
fdm_del(term->fdm, term->delayed_render_timer.upper_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd);

View file

@ -182,6 +182,7 @@ enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR };
enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK }; enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK };
enum selection_direction {SELECTION_UNDIR, SELECTION_LEFT, SELECTION_RIGHT}; enum selection_direction {SELECTION_UNDIR, SELECTION_LEFT, SELECTION_RIGHT};
enum selection_scroll_direction {SELECTION_SCROLL_UP, SELECTION_SCROLL_DOWN};
struct ptmx_buffer { struct ptmx_buffer {
void *data; void *data;
@ -346,6 +347,12 @@ struct terminal {
struct coord start; struct coord start;
struct coord end; struct coord end;
bool ongoing; bool ongoing;
struct {
int fd;
int col;
enum selection_scroll_direction direction;
} auto_scroll;
} selection; } selection;
bool is_searching; bool is_searching;