From 8179d73daa95dc3b3bdd76eaecc78e456bb4686a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Oct 2022 17:05:44 +0200 Subject: [PATCH 01/12] =?UTF-8?q?render:=20delay=20reflow=20for=20?= =?UTF-8?q?=E2=80=98resize-delay-ms=E2=80=99=20milliseconds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflowing a large scrollback is *slow*. During an interactive resize, it can easily take long enough that the compositor fills the Wayland socket with configure events. Eventually, the socket becomes full and the compositor terminates the connection, causing foot to exit. This patch is work-in-progress, and the first step towards alleviating this. It delays the reflow by: * Snapshotting (copying) the original grid when an interactive resize is started. * While resizing, we apply a simple truncation resize of the grid (like we handle the alt screen). * When the resize is done, or paused for ‘resize-delay-ms’, the grid is reflowed. TODO: we *must* not allow any changes to the temporary (truncated) grid during the resize. Any changes to the grid would be lost when the final reflow is applied. That is, we must completely pause the ptmx pipe while a resize is in progress. Future improvements: The initial copy can be slow. We should be able to avoid it by rewriting the reflow algorithm to not free anything. This is complicated by the fact that some resources (e.g. sixel images) are currently *moved* to the new grid. They’d instead have to be copied. --- doc/foot.ini.5.scd | 18 ++++--- grid.c | 2 + render.c | 118 ++++++++++++++++++++++++++++++++++++++++----- terminal.h | 5 ++ 4 files changed, 123 insertions(+), 20 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 46a6d33e..6fe97c09 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -213,15 +213,19 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. Default: _0x0_. *resize-delay-ms* - Time, in milliseconds, of "idle time" before foot sends the new - window dimensions to the client application while doing an - interactive resize of a foot window. Idle time in this context is - a period of time where the window size is not changing. + + Time, in milliseconds, of "idle time" before foot performs text + reflow, and sends the new window dimensions to the client + application while doing an interactive resize of a foot + window. Idle time in this context is a period of time where the + window size is not changing. In other words, while you are fiddling with the window size, foot - does not send the updated dimensions to the client. Only when you - pause the fiddling for *resize-delay-ms* milliseconds is the - client updated. + does not send the updated dimensions to the client. It also does a + fast "truncating" resize of the grid, instead of actually + reflowing the contents. Only when you pause the fiddling for + *resize-delay-ms* milliseconds is the client updated, and the + contents properly reflowed. Emphasis is on _while_ here; as soon as the interactive resize ends (i.e. when you let go of the window border), the final diff --git a/grid.c b/grid.c index 7bfef5cb..bccac529 100644 --- a/grid.c +++ b/grid.c @@ -210,6 +210,8 @@ grid_snapshot(const struct grid *grid) clone->offset = grid->offset; clone->view = grid->view; clone->cursor = grid->cursor; + clone->saved_cursor = grid->saved_cursor; + clone->kitty_kbd = grid->kitty_kbd; clone->rows = xcalloc(grid->num_rows, sizeof(clone->rows[0])); memset(&clone->scroll_damage, 0, sizeof(clone->scroll_damage)); memset(&clone->sixel_images, 0, sizeof(clone->sixel_images)); diff --git a/render.c b/render.c index f14911d4..034c9946 100644 --- a/render.c +++ b/render.c @@ -3663,13 +3663,54 @@ tiocswinsz(struct terminal *term) } } +static void +delayed_reflow_of_normal_grid(struct terminal *term) +{ + if (term->render.resizing.grid == NULL) + return; + + struct coord *const tracking_points[] = { + &term->selection.coords.start, + &term->selection.coords.end, + }; + + /* Reflow the original (since before the resize was started) grid, + * to the *current* dimensions */ + grid_resize_and_reflow( + term->render.resizing.grid, + term->grid->num_rows, term->grid->num_cols, + term->render.resizing.screen_rows, term->rows, + term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, + tracking_points); + + /* Replace the current, truncated, “normal” grid with the + * correctly reflowed one */ + grid_free(&term->normal); + term->normal = *term->render.resizing.grid; + free(term->render.resizing.grid); + + /* Reset */ + term->render.resizing.grid = NULL; + term->render.resizing.screen_rows = 0; + + /* Invalidate render pointers */ + shm_unref(term->render.last_buf); + term->render.last_buf = NULL; + term->render.last_cursor.row = NULL; + + if (term->grid == &term->normal) + term_damage_view(term); +} + static bool fdm_tiocswinsz(struct fdm *fdm, int fd, int events, void *data) { struct terminal *term = data; - if (events & EPOLLIN) + if (events & EPOLLIN) { tiocswinsz(term); + delayed_reflow_of_normal_grid(term); + } if (term->window->resize_timeout_fd >= 0) { fdm_del(fdm, term->window->resize_timeout_fd); @@ -3686,6 +3727,7 @@ send_dimensions_to_client(struct terminal *term) if (!win->is_resizing || term->conf->resize_delay_ms == 0) { /* Send new dimensions to client immediately */ tiocswinsz(term); + delayed_reflow_of_normal_grid(term); /* And make sure to reset and deallocate a lingering timer */ if (win->resize_timeout_fd >= 0) { @@ -3846,9 +3888,30 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const uint32_t scrollback_lines = term->render.scrollback_lines; + /* + * Snapshot the “normal” grid. + * + * Since text reflow is slow, don’t do it *while* resizing. Only + * do it when done, or after “pausing” the resize for sufficiently + * long. We re-use the TIOCSWINSZ timer to handle this. See + * send_dimensions_to_client() and fdm_tiocswinsz(). + * + * To be able to do the final reflow correctly, we need a copy of + * the original grid, before the resize started. + */ + if (term->window->is_resizing && term->render.resizing.grid == NULL) { + /* + * TODO: snapshotting a large grid is slow. To improve, move + * normal -> resizing.grid, and instantiate a small (screen + * sized) new “normal” + */ + term->render.resizing.grid = grid_snapshot(&term->normal); + term->render.resizing.screen_rows = term->rows; + } + /* Screen rows/cols before resize */ - const int old_cols = term->cols; - const int old_rows = term->rows; + int old_cols = term->cols; + int old_rows = term->rows; /* Screen rows/cols after resize */ const int new_cols = (term->width - 2 * pad_x) / term->cell_width; @@ -3882,7 +3945,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force) xassert(term->margins.top >= pad_y); xassert(term->margins.bottom >= pad_y); - if (new_cols == old_cols && new_rows == old_rows) { + if (new_cols == old_cols && new_rows == old_rows && + (term->render.resizing.grid == NULL || term->window->is_resizing)) + { LOG_DBG("grid layout unaffected; skipping reflow"); goto damage_view; } @@ -3906,16 +3971,43 @@ maybe_resize(struct terminal *term, int width, int height, bool force) * selection’s pivot point coordinates *must* be added to the * tracking points list. */ - struct coord *const tracking_points[] = { - &term->selection.coords.start, - &term->selection.coords.end, - }; - /* Resize grids */ - grid_resize_and_reflow( - &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, - term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, - tracking_points); + if (term->window->is_resizing) { + /* Simple truncating resize, *while* an interactive resize is + * ongoing. */ + xassert(term->render.resizing.grid != NULL); + grid_resize_without_reflow( + &term->normal, + new_normal_grid_rows, new_cols, + old_rows, + new_rows); + } else { + /* Full text reflow */ + + if (term->render.resizing.grid != NULL) { + /* Throw away the current, truncated, “normal” grid, and + * use the original grid instead (from before the resize + * started) */ + grid_free(&term->normal); + term->normal = *term->render.resizing.grid; + free(term->render.resizing.grid); + + old_rows = term->render.resizing.screen_rows; + + term->render.resizing.grid = NULL; + term->render.resizing.screen_rows = 0; + } + + struct coord *const tracking_points[] = { + &term->selection.coords.start, + &term->selection.coords.end, + }; + + grid_resize_and_reflow( + &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows, + term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, + tracking_points); + } grid_resize_without_reflow( &term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows); diff --git a/terminal.h b/terminal.h index 0dde6330..3afd101d 100644 --- a/terminal.h +++ b/terminal.h @@ -595,6 +595,11 @@ struct terminal { size_t search_glyph_offset; + struct { + struct grid *grid; + int screen_rows; + } resizing; + struct timespec input_time; } render; From 3565cbd636dacf15049504d686219b787783733c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 6 Oct 2022 17:09:32 +0200 Subject: [PATCH 02/12] render: performance improvements during interactive resize Instead of copying the entire grid when an interactive resize is started, stash the complete grid (to be used in the final reflow). Copy the current viewport only, to be used during the interactive resize. This gets rid of the initial "pause" when snapshotting the grid when an interactive resize is started. --- render.c | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/render.c b/render.c index 034c9946..d2fce8b0 100644 --- a/render.c +++ b/render.c @@ -3900,13 +3900,45 @@ maybe_resize(struct terminal *term, int width, int height, bool force) * the original grid, before the resize started. */ if (term->window->is_resizing && term->render.resizing.grid == NULL) { - /* - * TODO: snapshotting a large grid is slow. To improve, move - * normal -> resizing.grid, and instantiate a small (screen - * sized) new “normal” - */ - term->render.resizing.grid = grid_snapshot(&term->normal); + /* Stash the current ‘normal’ grid, as-is, to be used when + * doing the final reflow */ term->render.resizing.screen_rows = term->rows; + term->render.resizing.grid = xmalloc(sizeof(*term->render.resizing.grid)); + *term->render.resizing.grid = term->normal; + + + /* + * Copy the current viewport to a new grid that will be used + * during the resize. For now, throw away sixels and OSC-8 + * URLs. They’ll be "restored" when we do the final reflow. + * + * TODO: + * - sixels? + * - OSC-8? + */ + struct grid g = { + .num_rows = 1 << (32 - __builtin_clz(term->rows - 1)), + .num_cols = term->cols, + .offset = 0, + .view = 0, + .cursor = term->normal.cursor, + .saved_cursor = term->normal.saved_cursor, + .rows = xcalloc(g.num_rows, sizeof(g.rows[0])), + .cur_row = NULL, + .scroll_damage = tll_init(), + .sixel_images = tll_init(), + .kitty_kbd = term->normal.kitty_kbd, + }; + + for (size_t i = 0, j = term->normal.view; i < term->rows; + i++, j = (j + 1) & (term->normal.num_rows - 1)) + { + g.rows[i] = grid_row_alloc(term->cols, false); + memcpy(g.rows[i]->cells, term->normal.rows[j]->cells, + term->cols * sizeof(g.rows[i]->cells[0])); + } + + term->normal = g; } /* Screen rows/cols before resize */ From f4f1989b6ef341557cc90a6805d745fef2a7a815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 6 Oct 2022 17:23:56 +0200 Subject: [PATCH 03/12] render: resize: ignore ptmx read events during interactive resize --- render.c | 4 ++++ terminal.c | 12 ++++++++++++ terminal.h | 3 +++ 3 files changed, 19 insertions(+) diff --git a/render.c b/render.c index d2fce8b0..3cb544cd 100644 --- a/render.c +++ b/render.c @@ -3700,6 +3700,8 @@ delayed_reflow_of_normal_grid(struct terminal *term) if (term->grid == &term->normal) term_damage_view(term); + + term_ptmx_resume(term); } static bool @@ -3939,6 +3941,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) } term->normal = g; + term_ptmx_pause(term); } /* Screen rows/cols before resize */ @@ -4028,6 +4031,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->render.resizing.grid = NULL; term->render.resizing.screen_rows = 0; + term_ptmx_resume(term); } struct coord *const tracking_points[] = { diff --git a/terminal.c b/terminal.c index df17201b..ee12ae32 100644 --- a/terminal.c +++ b/terminal.c @@ -233,6 +233,18 @@ static struct timespec last = {0}; static bool cursor_blink_rearm_timer(struct terminal *term); +void +term_ptmx_pause(struct terminal *term) +{ + fdm_event_del(term->fdm, term->ptmx, EPOLLIN); +} + +void +term_ptmx_resume(struct terminal *term) +{ + fdm_event_add(term->fdm, term->ptmx, EPOLLIN); +} + /* Externally visible, but not declared in terminal.h, to enable pgo * to call this function directly */ bool diff --git a/terminal.h b/terminal.h index 3afd101d..7a281e72 100644 --- a/terminal.h +++ b/terminal.h @@ -810,6 +810,9 @@ void term_collect_urls(struct terminal *term); void term_osc8_open(struct terminal *term, uint64_t id, const char *uri); void term_osc8_close(struct terminal *term); +void term_ptmx_pause(struct terminal *term); +void term_ptmx_resume(struct terminal *term); + static inline void term_reset_grapheme_state(struct terminal *term) { #if defined(FOOT_GRAPHEME_CLUSTERING) From b52262da8e422a9b3b62a642bf7108ab03fa4cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 6 Oct 2022 17:26:38 +0200 Subject: [PATCH 04/12] changelog: fixed crash when resizing window with a very large scrollback --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1606f3b4..f97cb252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,8 @@ `resize-delay-ms` option. * Missing backslash in ST terminator in escape sequences in the built-in terminfo (accessed via XTGETTCAP). +* Crash when interactively resizing the window with a very large + scrollback. [1173]: https://codeberg.org/dnkl/foot/issues/1173 From f70c34c5a8c6e89f6591e08fe3e26b9f0f44e6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:01:11 +0200 Subject: [PATCH 05/12] sixel: add sixel_reflow_grid() This function reflows all sixels in the specified grid. The pre-existing sixel_reflow() function is a shortcut for sixel_reflow_grid(term, &term->normal) sixel_reflow_grid(term, &term->alt); --- sixel.c | 146 +++++++++++++++++++++++++++++--------------------------- sixel.h | 4 ++ 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/sixel.c b/sixel.c index 0c94117f..c80a92a3 100644 --- a/sixel.c +++ b/sixel.c @@ -838,85 +838,89 @@ sixel_cell_size_changed(struct terminal *term) } void -sixel_reflow(struct terminal *term) +sixel_reflow_grid(struct terminal *term, struct grid *grid) { - struct grid *g = term->grid; + /* Meh - the sixel functions we call use term->grid... */ + struct grid *active_grid = term->grid; + term->grid = grid; - for (size_t i = 0; i < 2; i++) { - struct grid *grid = i == 0 ? &term->normal : &term->alt; + /* Need the “real” list to be empty from the beginning */ + tll(struct sixel) copy = tll_init(); + tll_foreach(grid->sixel_images, it) + tll_push_back(copy, it->item); + tll_free(grid->sixel_images); - term->grid = grid; + tll_rforeach(copy, it) { + struct sixel *six = &it->item; + int start = six->pos.row; + int end = (start + six->rows - 1) & (grid->num_rows - 1); - /* Need the “real” list to be empty from the beginning */ - tll(struct sixel) copy = tll_init(); - tll_foreach(grid->sixel_images, it) - tll_push_back(copy, it->item); - tll_free(grid->sixel_images); - - tll_rforeach(copy, it) { - struct sixel *six = &it->item; - int start = six->pos.row; - int end = (start + six->rows - 1) & (grid->num_rows - 1); - - if (end < start) { - /* Crosses scrollback wrap-around */ - /* TODO: split image */ - sixel_destroy(six); - continue; - } - - if (six->rows > grid->num_rows) { - /* Image too large */ - /* TODO: keep bottom part? */ - sixel_destroy(six); - continue; - } - - /* Drop sixels that now cross the current scrollback end - * border. This is similar to a sixel that have been - * scrolled out */ - /* TODO: should be possible to optimize this */ - bool sixel_destroyed = false; - int last_row = -1; - - for (int j = 0; j < six->rows; j++) { - int row_no = grid_row_abs_to_sb( - term->grid, term->rows, six->pos.row + j); - if (last_row != -1 && last_row >= row_no) { - sixel_destroy(six); - sixel_destroyed = true; - break; - } - - last_row = row_no; - } - - if (sixel_destroyed) { - LOG_WARN("destroyed sixel that now crossed history"); - continue; - } - - /* Sixels that didn’t overlap may now do so, which isn’t - * allowed of course */ - _sixel_overwrite_by_rectangle( - term, six->pos.row, six->pos.col, six->rows, six->cols, - &it->item.pix, &it->item.opaque); - - if (it->item.data != pixman_image_get_data(it->item.pix)) { - it->item.data = pixman_image_get_data(it->item.pix); - it->item.width = pixman_image_get_width(it->item.pix); - it->item.height = pixman_image_get_height(it->item.pix); - it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width; - it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height; - } - - sixel_insert(term, it->item); + if (end < start) { + /* Crosses scrollback wrap-around */ + /* TODO: split image */ + sixel_destroy(six); + continue; } - tll_free(copy); + if (six->rows > grid->num_rows) { + /* Image too large */ + /* TODO: keep bottom part? */ + sixel_destroy(six); + continue; + } + + /* Drop sixels that now cross the current scrollback end + * border. This is similar to a sixel that have been + * scrolled out */ + /* TODO: should be possible to optimize this */ + bool sixel_destroyed = false; + int last_row = -1; + + for (int j = 0; j < six->rows; j++) { + int row_no = grid_row_abs_to_sb( + term->grid, term->rows, six->pos.row + j); + if (last_row != -1 && last_row >= row_no) { + sixel_destroy(six); + sixel_destroyed = true; + break; + } + + last_row = row_no; + } + + if (sixel_destroyed) { + LOG_WARN("destroyed sixel that now crossed history"); + continue; + } + + /* Sixels that didn’t overlap may now do so, which isn’t + * allowed of course */ + _sixel_overwrite_by_rectangle( + term, six->pos.row, six->pos.col, six->rows, six->cols, + &it->item.pix, &it->item.opaque); + + if (it->item.data != pixman_image_get_data(it->item.pix)) { + it->item.data = pixman_image_get_data(it->item.pix); + it->item.width = pixman_image_get_width(it->item.pix); + it->item.height = pixman_image_get_height(it->item.pix); + it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width; + it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height; + } + + sixel_insert(term, it->item); } - term->grid = g; + tll_free(copy); + term->grid = active_grid; +} + +void +sixel_reflow(struct terminal *term) +{ + for (size_t i = 0; i < 2; i++) { + struct grid *grid = i == 0 ? &term->normal : &term->alt; + sixel_reflow_grid(term, grid); + } } void diff --git a/sixel.h b/sixel.h index a57957c3..f72b4dc4 100644 --- a/sixel.h +++ b/sixel.h @@ -19,6 +19,10 @@ void sixel_scroll_up(struct terminal *term, int rows); void sixel_scroll_down(struct terminal *term, int rows); void sixel_cell_size_changed(struct terminal *term); + +void sixel_reflow_grid(struct terminal *term, struct grid *grid); + +/* Shortcut for sixel_reflow_grid(normal) + sixel_reflow_grid(alt) */ void sixel_reflow(struct terminal *term); /* From d4b0b0887e73c80cf5ff22fdbac82947897b2b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:11:49 +0200 Subject: [PATCH 06/12] render: delayed reflow: not enough to damage current view; need to refresh too --- render.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/render.c b/render.c index 3cb544cd..c7174228 100644 --- a/render.c +++ b/render.c @@ -3698,8 +3698,10 @@ delayed_reflow_of_normal_grid(struct terminal *term) term->render.last_buf = NULL; term->render.last_cursor.row = NULL; - if (term->grid == &term->normal) + if (term->grid == &term->normal) { term_damage_view(term); + render_refresh(term); + } term_ptmx_resume(term); } From 18ef36523f55cb8c8a6a9a9c3269274cc08c0a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:12:18 +0200 Subject: [PATCH 07/12] grid: resize: assert grid->cur_row is not NULL after a grid resize --- grid.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/grid.c b/grid.c index bccac529..3bcc8a55 100644 --- a/grid.c +++ b/grid.c @@ -485,6 +485,8 @@ grid_resize_without_reflow( grid->saved_cursor.point = saved_cursor; grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; + xassert(grid->cur_row != NULL); + grid->cursor.lcf = false; grid->saved_cursor.lcf = false; @@ -1047,6 +1049,8 @@ grid_resize_and_reflow( saved_cursor.col = min(saved_cursor.col, new_cols - 1); grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; + xassert(grid->cur_row != NULL); + grid->cursor.point = cursor; grid->saved_cursor.point = saved_cursor; From 66e4592d91312568c8948ddda850ba8b1c31c87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:14:49 +0200 Subject: [PATCH 08/12] term: use SIZE_MAX instead of (size_t)-1ll --- terminal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index ee12ae32..5dd261bb 100644 --- a/terminal.c +++ b/terminal.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -268,7 +269,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) } uint8_t buf[24 * 1024]; - const size_t max_iterations = !hup ? 10 : (size_t)-1ll; + const size_t max_iterations = !hup ? 10 : SIZE_MAX; for (size_t i = 0; i < max_iterations && pollin; i++) { xassert(pollin); From 54d637e2b47fd55e4abde1e28f7f665e69c2a995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:15:29 +0200 Subject: [PATCH 09/12] =?UTF-8?q?term:=20ptmx:=20don=E2=80=99t=20consume?= =?UTF-8?q?=20anything=20while=20doing=20an=20interactive=20resize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ‘normal’ grid in use during an interactive resize is temporary; all changes done to it will be lost when the resize is finished. --- terminal.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/terminal.c b/terminal.c index 5dd261bb..4fb13f44 100644 --- a/terminal.c +++ b/terminal.c @@ -268,6 +268,16 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) cursor_blink_rearm_timer(term); } + if (unlikely(term->interactive_resizing.grid != NULL)) { + /* + * Don’t consume PTMX while we’re doing an interactive resize, + * since the ‘normal’ grid we’re currently using is a + * temporary one - all changes done to it will be lost when + * the interactive resize ends. + */ + return 0; + } + uint8_t buf[24 * 1024]; const size_t max_iterations = !hup ? 10 : SIZE_MAX; @@ -291,6 +301,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) break; } + xassert(term->interactive_resizing.grid == NULL); vt_from_slave(term, buf, count); } From c5c97c2fd4a674d0fcce16d40e0e56994195e9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:16:23 +0200 Subject: [PATCH 10/12] term_ptmx_{pause,resume}: return success/fail --- terminal.c | 24 ++++++++++++------------ terminal.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/terminal.c b/terminal.c index 4fb13f44..16486d82 100644 --- a/terminal.c +++ b/terminal.c @@ -234,18 +234,6 @@ static struct timespec last = {0}; static bool cursor_blink_rearm_timer(struct terminal *term); -void -term_ptmx_pause(struct terminal *term) -{ - fdm_event_del(term->fdm, term->ptmx, EPOLLIN); -} - -void -term_ptmx_resume(struct terminal *term) -{ - fdm_event_add(term->fdm, term->ptmx, EPOLLIN); -} - /* Externally visible, but not declared in terminal.h, to enable pgo * to call this function directly */ bool @@ -382,6 +370,18 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) return true; } +bool +term_ptmx_pause(struct terminal *term) +{ + return fdm_event_del(term->fdm, term->ptmx, EPOLLIN); +} + +bool +term_ptmx_resume(struct terminal *term) +{ + return fdm_event_add(term->fdm, term->ptmx, EPOLLIN); +} + static bool fdm_flash(struct fdm *fdm, int fd, int events, void *data) { diff --git a/terminal.h b/terminal.h index 7a281e72..16c8e776 100644 --- a/terminal.h +++ b/terminal.h @@ -810,8 +810,8 @@ void term_collect_urls(struct terminal *term); void term_osc8_open(struct terminal *term, uint64_t id, const char *uri); void term_osc8_close(struct terminal *term); -void term_ptmx_pause(struct terminal *term); -void term_ptmx_resume(struct terminal *term); +bool term_ptmx_pause(struct terminal *term); +bool term_ptmx_resume(struct terminal *term); static inline void term_reset_grapheme_state(struct terminal *term) { From c550d67cd856ba806c616ac2f15bdbca283e0a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:16:50 +0200 Subject: [PATCH 11/12] render: resize: do delayed reflow immediately when failing to arm tiocswinsz timer --- render.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/render.c b/render.c index c7174228..2f79c6c1 100644 --- a/render.c +++ b/render.c @@ -3776,8 +3776,10 @@ send_dimensions_to_client(struct terminal *term) successfully_scheduled = true; } - if (!successfully_scheduled) + if (!successfully_scheduled) { tiocswinsz(term); + delayed_reflow_of_normal_grid(term); + } } } From 298f210ed93a83a531a44f772172da7f64510217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Oct 2022 16:17:22 +0200 Subject: [PATCH 12/12] render: rename term->render.resizing -> term->interactive_resizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit But also, more importantly, logical fixes: * Stash the number of new scrollback lines the stashed ‘normal’ grid should be resized *to*. There’s also a couple of performance changes here: * When doing a delayed reflow (tiocswinsz timer), call sixel_reflow_grid(term, &term->normal) - there’s no need to reflow sixels in the ‘alt’ screen. * When doing a delayed reflow, free all scroll damage. It’s not needed, since we’re damaging the entire window anyway. * Use minimum size for the temporary ‘normal’ grid (that contains the current viewport). We just need it to be large enough to fit the current viewport, and be a valid grid row count (power of 2). This just so happens to be the current ‘alt’ grid’s row count... --- render.c | 68 +++++++++++++++++++++++++++++++----------------------- terminal.h | 11 +++++---- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/render.c b/render.c index 2f79c6c1..d50606cb 100644 --- a/render.c +++ b/render.c @@ -3666,9 +3666,11 @@ tiocswinsz(struct terminal *term) static void delayed_reflow_of_normal_grid(struct terminal *term) { - if (term->render.resizing.grid == NULL) + if (term->interactive_resizing.grid == NULL) return; + xassert(term->interactive_resizing.new_rows > 0); + struct coord *const tracking_points[] = { &term->selection.coords.start, &term->selection.coords.end, @@ -3677,27 +3679,31 @@ delayed_reflow_of_normal_grid(struct terminal *term) /* Reflow the original (since before the resize was started) grid, * to the *current* dimensions */ grid_resize_and_reflow( - term->render.resizing.grid, - term->grid->num_rows, term->grid->num_cols, - term->render.resizing.screen_rows, term->rows, + term->interactive_resizing.grid, + term->interactive_resizing.new_rows, term->normal.num_cols, + term->interactive_resizing.old_screen_rows, term->rows, term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, tracking_points); /* Replace the current, truncated, “normal” grid with the * correctly reflowed one */ grid_free(&term->normal); - term->normal = *term->render.resizing.grid; - free(term->render.resizing.grid); + term->normal = *term->interactive_resizing.grid; + free(term->interactive_resizing.grid); /* Reset */ - term->render.resizing.grid = NULL; - term->render.resizing.screen_rows = 0; + term->interactive_resizing.grid = NULL; + term->interactive_resizing.old_screen_rows = 0; + term->interactive_resizing.new_rows = 0; /* Invalidate render pointers */ shm_unref(term->render.last_buf); term->render.last_buf = NULL; term->render.last_cursor.row = NULL; + tll_free(term->normal.scroll_damage); + sixel_reflow_grid(term, &term->normal); + if (term->grid == &term->normal) { term_damage_view(term); render_refresh(term); @@ -3895,8 +3901,6 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const uint32_t scrollback_lines = term->render.scrollback_lines; /* - * Snapshot the “normal” grid. - * * Since text reflow is slow, don’t do it *while* resizing. Only * do it when done, or after “pausing” the resize for sufficiently * long. We re-use the TIOCSWINSZ timer to handle this. See @@ -3905,25 +3909,30 @@ maybe_resize(struct terminal *term, int width, int height, bool force) * To be able to do the final reflow correctly, we need a copy of * the original grid, before the resize started. */ - if (term->window->is_resizing && term->render.resizing.grid == NULL) { + if (term->window->is_resizing && term->interactive_resizing.grid == NULL) { + term_ptmx_pause(term); + /* Stash the current ‘normal’ grid, as-is, to be used when * doing the final reflow */ - term->render.resizing.screen_rows = term->rows; - term->render.resizing.grid = xmalloc(sizeof(*term->render.resizing.grid)); - *term->render.resizing.grid = term->normal; - + term->interactive_resizing.old_screen_rows = term->rows; + term->interactive_resizing.grid = xmalloc(sizeof(*term->interactive_resizing.grid)); + *term->interactive_resizing.grid = term->normal; /* * Copy the current viewport to a new grid that will be used * during the resize. For now, throw away sixels and OSC-8 * URLs. They’ll be "restored" when we do the final reflow. * + * We use the ‘alt’ screen’s row count, since we don’t want to + * instantiate an unnecessarily large grid. + * * TODO: * - sixels? * - OSC-8? */ + xassert(1 << (32 - __builtin_clz(term->rows)) == term->alt.num_rows); struct grid g = { - .num_rows = 1 << (32 - __builtin_clz(term->rows - 1)), + .num_rows = term->alt.num_rows, .num_cols = term->cols, .offset = 0, .view = 0, @@ -3945,7 +3954,6 @@ maybe_resize(struct terminal *term, int width, int height, bool force) } term->normal = g; - term_ptmx_pause(term); } /* Screen rows/cols before resize */ @@ -3985,9 +3993,10 @@ maybe_resize(struct terminal *term, int width, int height, bool force) xassert(term->margins.bottom >= pad_y); if (new_cols == old_cols && new_rows == old_rows && - (term->render.resizing.grid == NULL || term->window->is_resizing)) + (term->interactive_resizing.grid == NULL || term->window->is_resizing)) { LOG_DBG("grid layout unaffected; skipping reflow"); + term->interactive_resizing.new_rows = new_normal_grid_rows; goto damage_view; } @@ -4014,27 +4023,28 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (term->window->is_resizing) { /* Simple truncating resize, *while* an interactive resize is * ongoing. */ - xassert(term->render.resizing.grid != NULL); + xassert(term->interactive_resizing.grid != NULL); + xassert(new_normal_grid_rows > 0); + term->interactive_resizing.new_rows = new_normal_grid_rows; + grid_resize_without_reflow( - &term->normal, - new_normal_grid_rows, new_cols, - old_rows, - new_rows); + &term->normal, new_alt_grid_rows, new_cols, old_rows, new_rows); } else { /* Full text reflow */ - if (term->render.resizing.grid != NULL) { + if (term->interactive_resizing.grid != NULL) { /* Throw away the current, truncated, “normal” grid, and * use the original grid instead (from before the resize * started) */ grid_free(&term->normal); - term->normal = *term->render.resizing.grid; - free(term->render.resizing.grid); + term->normal = *term->interactive_resizing.grid; + free(term->interactive_resizing.grid); - old_rows = term->render.resizing.screen_rows; + old_rows = term->interactive_resizing.old_screen_rows; - term->render.resizing.grid = NULL; - term->render.resizing.screen_rows = 0; + term->interactive_resizing.grid = NULL; + term->interactive_resizing.old_screen_rows = 0; + term->interactive_resizing.new_rows = 0; term_ptmx_resume(term); } diff --git a/terminal.h b/terminal.h index 16c8e776..d5ed1ba2 100644 --- a/terminal.h +++ b/terminal.h @@ -595,14 +595,15 @@ struct terminal { size_t search_glyph_offset; - struct { - struct grid *grid; - int screen_rows; - } resizing; - struct timespec input_time; } render; + struct { + struct grid *grid; /* Original ‘normal’ grid, before resize started */ + int old_screen_rows; /* term->rows before resize started */ + int new_rows; /* New number of scrollback rows */ + } interactive_resizing; + struct { enum { SIXEL_DECSIXEL, /* DECSIXEL body part ", $, -, ? ... ~ */