From 2303affc8740ace55a9a546797653ddf244208f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 12:03:20 +0200 Subject: [PATCH 1/9] input: motion: scroll up/down while selecting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the cursor moves above the grid top, or below the grid bottom, while selecting text, scroll up/down the scrollback before updating the selection. TODO: this only scrolls when the mouse is *moved*. I.e. you can’t move the cursor outside the grid once and for all, and then let foot auto-scroll. --- input.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/input.c b/input.c index 20c1381c..27bbbbcd 100644 --- a/input.c +++ b/input.c @@ -1325,10 +1325,22 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, = old_col != seat->mouse.col || old_row != seat->mouse.row; /* Cursor is inside the grid, i.e. *not* in the margins */ - 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_down = y >= term->height - term->margins.bottom; /* Update selection */ if (!term->is_searching) { + + if (scroll_up || scroll_down) { + if (scroll_up) + cmd_scrollback_up(term, 1); + if (scroll_down) + cmd_scrollback_down(term, 1); + cursor_is_on_new_cell = true; + } + if (cursor_is_on_new_cell || term->selection.end.row < 0) selection_update(term, selection_col, selection_row); } From 7fedf2f80160d4cb96b77ae58137573b78cbfeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 15:44:20 +0200 Subject: [PATCH 2/9] selection: auto-scroll: selection keeps scrolling while mouse is outside grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- input.c | 22 +++++++---- selection.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++ selection.h | 5 +++ terminal.c | 6 +++ terminal.h | 7 ++++ 5 files changed, 136 insertions(+), 7 deletions(-) diff --git a/input.c b/input.c index 27bbbbcd..27009810 100644 --- a/input.c +++ b/input.c @@ -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 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 */ if (!term->is_searching) { - if (scroll_up || scroll_down) { - if (scroll_up) - cmd_scrollback_up(term, 1); - if (scroll_down) - cmd_scrollback_down(term, 1); - cursor_is_on_new_cell = true; + int distance = scroll_up + ? term->margins.top - y + : y - (term->height - term->margins.bottom); + + 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) diff --git a/selection.c b/selection.c index 7475f229..c774c713 100644 --- a/selection.c +++ b/selection.c @@ -15,6 +15,7 @@ #include "log.h" #include "async.h" +#include "commands.h" #include "config.h" #include "extract.h" #include "grid.h" @@ -619,6 +620,7 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) if (!term->selection.ongoing) return; + selection_stop_scroll_timer(term); term->selection.ongoing = false; 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.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) { foreach_selected( @@ -789,6 +795,103 @@ selection_mark_row( 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 target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) diff --git a/selection.h b/selection.h index 547da2fc..888b2f05 100644 --- a/selection.h +++ b/selection.h @@ -74,3 +74,8 @@ void text_from_primary( struct seat *seat, struct terminal *term, void (*cb)(const char *data, size_t size, 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); diff --git a/terminal.c b/terminal.c index 0afe39d5..2cb53ad3 100644 --- a/terminal.c +++ b/terminal.c @@ -985,6 +985,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .selection = { .start = {-1, -1}, .end = {-1, -1}, + .auto_scroll = { + .fd = -1, + }, }, .normal = {.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); + 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->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd); @@ -1175,6 +1179,7 @@ term_shutdown(struct terminal *term) else close(term->ptmx); + term->selection.auto_scroll.fd = -1; term->render.app_sync_updates.timer_fd = -1; term->delayed_render_timer.lower_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->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd); diff --git a/terminal.h b/terminal.h index b6fb2aec..0a71abb4 100644 --- a/terminal.h +++ b/terminal.h @@ -182,6 +182,7 @@ enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK }; enum selection_direction {SELECTION_UNDIR, SELECTION_LEFT, SELECTION_RIGHT}; +enum selection_scroll_direction {SELECTION_SCROLL_UP, SELECTION_SCROLL_DOWN}; struct ptmx_buffer { void *data; @@ -346,6 +347,12 @@ struct terminal { struct coord start; struct coord end; bool ongoing; + + struct { + int fd; + int col; + enum selection_scroll_direction direction; + } auto_scroll; } selection; bool is_searching; From dd14e0b3c3005fa79cd6640c65960d6883b2ed2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:09:30 +0200 Subject: [PATCH 3/9] input: auto-scroll: apply scrollback.multiplier to the interval divisor --- input.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 27009810..b862408b 100644 --- a/input.c +++ b/input.c @@ -1341,10 +1341,11 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, : y - (term->height - term->margins.bottom); assert(distance > 0); - distance /= term->scale; + int divisor + = distance * term->conf->scrollback.multiplier / term->scale; selection_start_scroll_timer( - term, 100000000 / (distance > 0 ? distance : 1), + term, 400000000 / (divisor > 0 ? divisor : 1), scroll_up ? SELECTION_SCROLL_UP : SELECTION_SCROLL_DOWN, selection_col); } From 4ad7fdc19c386fada4e1f8401d6da95f2930c201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:18:18 +0200 Subject: [PATCH 4/9] =?UTF-8?q?selection:=20auto-scroll:=20add=20SELECTION?= =?UTF-8?q?=5FSCROLL=5FNOT=20as=20a=20scroll=20=E2=80=98direction=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 29 ++++++++++++++++++++++------- selection.c | 11 +++++++++-- terminal.h | 2 +- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/input.c b/input.c index b862408b..a486ca8b 100644 --- a/input.c +++ b/input.c @@ -1327,16 +1327,32 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, /* Cursor is inside the grid, i.e. *not* in the margins */ 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_down = y > term->height - term->margins.bottom; + enum selection_scroll_direction auto_scroll_direction + = y < term->margins.top ? SELECTION_SCROLL_UP + : y > term->height - term->margins.bottom ? SELECTION_SCROLL_DOWN + : SELECTION_SCROLL_NOT; - if (!scroll_up && !scroll_down) + if (auto_scroll_direction == SELECTION_SCROLL_NOT) selection_stop_scroll_timer(term); /* Update selection */ if (!term->is_searching) { - if (scroll_up || scroll_down) { - int distance = scroll_up + if (auto_scroll_direction != SELECTION_SCROLL_NOT) { + /* + * Start ‘selection auto-scrolling’ + * + * The speed of the scrolling is proportional to the + * distance between the mouse and the grid; the + * further away the mouse is, the fast we scroll. + * + * Note that the speed is measures in ‘intervals (in + * ns) between each timed scroll of a single line’. + * + * Thus, the further away the mouse is, the smaller + * interval value we use. + */ + + int distance = auto_scroll_direction == SELECTION_SCROLL_UP ? term->margins.top - y : y - (term->height - term->margins.bottom); @@ -1346,8 +1362,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, selection_start_scroll_timer( term, 400000000 / (divisor > 0 ? divisor : 1), - scroll_up ? SELECTION_SCROLL_UP : SELECTION_SCROLL_DOWN, - selection_col); + auto_scroll_direction, selection_col); } if (cursor_is_on_new_cell || term->selection.end.row < 0) diff --git a/selection.c b/selection.c index c774c713..20ed011c 100644 --- a/selection.c +++ b/selection.c @@ -817,6 +817,9 @@ fdm_scroll_timer(struct fdm *fdm, int fd, int events, void *data) } switch (term->selection.auto_scroll.direction) { + case SELECTION_SCROLL_NOT: + return true; + case SELECTION_SCROLL_UP: for (uint64_t i = 0; i < expiration_count; i++) cmd_scrollback_up(term, 1); @@ -838,6 +841,8 @@ void selection_start_scroll_timer(struct terminal *term, int interval_ns, enum selection_scroll_direction direction, int col) { + assert(direction != SELECTION_SCROLL_NOT); + if (!term->selection.ongoing) return; @@ -875,7 +880,6 @@ selection_start_scroll_timer(struct terminal *term, int interval_ns, term->selection.auto_scroll.direction = direction; term->selection.auto_scroll.col = col; - return; err: @@ -886,11 +890,14 @@ err: void selection_stop_scroll_timer(struct terminal *term) { - if (term->selection.auto_scroll.fd < 0) + if (term->selection.auto_scroll.fd < 0) { + assert(term->selection.auto_scroll.direction == SELECTION_SCROLL_NOT); return; + } fdm_del(term->fdm, term->selection.auto_scroll.fd); term->selection.auto_scroll.fd = -1; + term->selection.auto_scroll.direction = SELECTION_SCROLL_NOT; } static void diff --git a/terminal.h b/terminal.h index 0a71abb4..544d67a7 100644 --- a/terminal.h +++ b/terminal.h @@ -182,7 +182,7 @@ enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK }; enum selection_direction {SELECTION_UNDIR, SELECTION_LEFT, SELECTION_RIGHT}; -enum selection_scroll_direction {SELECTION_SCROLL_UP, SELECTION_SCROLL_DOWN}; +enum selection_scroll_direction {SELECTION_SCROLL_NOT, SELECTION_SCROLL_UP, SELECTION_SCROLL_DOWN}; struct ptmx_buffer { void *data; From 2203422d8a15df4e0aca8d5a7619c75806455f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:19:38 +0200 Subject: [PATCH 5/9] changelog: auto-scroll while selecting --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7445bb14..0abe9f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ (https://codeberg.org/dnkl/foot/issues/157). * **word-delimiters** option to `foot.ini` (https://codeberg.org/dnkl/foot/issues/156). +* Terminal content is now auto-scrolled when moving the mouse above or + below the window while selecting + (https://codeberg.org/dnkl/foot/issues/149). ### Changed From 0f67549e041986c7aba33a96285a0341d1e93027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:22:29 +0200 Subject: [PATCH 6/9] =?UTF-8?q?input:=20comment:=20=E2=80=98fast=E2=80=99?= =?UTF-8?q?=20->=20=E2=80=98faster=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index a486ca8b..c5d6b662 100644 --- a/input.c +++ b/input.c @@ -1343,7 +1343,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, * * The speed of the scrolling is proportional to the * distance between the mouse and the grid; the - * further away the mouse is, the fast we scroll. + * further away the mouse is, the faster we scroll. * * Note that the speed is measures in ‘intervals (in * ns) between each timed scroll of a single line’. From 674e565b5aee75106cb2e22086463f5eb34d7ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:23:01 +0200 Subject: [PATCH 7/9] =?UTF-8?q?input:=20comment:=20=E2=80=98measures?= =?UTF-8?q?=E2=80=99=20->=20=E2=80=98measured=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index c5d6b662..f3993978 100644 --- a/input.c +++ b/input.c @@ -1345,7 +1345,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, * distance between the mouse and the grid; the * further away the mouse is, the faster we scroll. * - * Note that the speed is measures in ‘intervals (in + * Note that the speed is measured in ‘intervals (in * ns) between each timed scroll of a single line’. * * Thus, the further away the mouse is, the smaller From 0cff7c7c4f5fb5e4d141c97b55ccffdcc01a398d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:24:47 +0200 Subject: [PATCH 8/9] selection: cancel: call selection_stop_scroll_timer() Instead of re-implementing the same logic, simply call selection_stop_scroll_timer(). --- selection.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/selection.c b/selection.c index 20ed011c..54a50ff4 100644 --- a/selection.c +++ b/selection.c @@ -649,10 +649,7 @@ selection_cancel(struct terminal *term) term->selection.start.row, term->selection.start.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; - } + selection_stop_scroll_timer(term); if (term->selection.start.row >= 0 && term->selection.end.row >= 0) { foreach_selected( From 0d8344d3b904c3ae4c18f6bffb9ef7b05c8361e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Oct 2020 18:26:24 +0200 Subject: [PATCH 9/9] selection: auto-scroll: call cmd_scrollback_{up,down}() with number of rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to loop and call cmd_scrollback_{up,down}() with ‘1’ each loop iteration; just call it with the total number of lines to scroll. --- selection.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/selection.c b/selection.c index 54a50ff4..2d40b38b 100644 --- a/selection.c +++ b/selection.c @@ -818,14 +818,12 @@ fdm_scroll_timer(struct fdm *fdm, int fd, int events, void *data) return true; case SELECTION_SCROLL_UP: - for (uint64_t i = 0; i < expiration_count; i++) - cmd_scrollback_up(term, 1); + cmd_scrollback_up(term, expiration_count); 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); + cmd_scrollback_down(term, expiration_count); selection_update(term, term->selection.auto_scroll.col, term->rows - 1); break; }