Merge branch 'delayed-reflow'

This commit is contained in:
Daniel Eklöf 2022-10-10 17:19:43 +02:00
commit 3949e34271
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
8 changed files with 288 additions and 93 deletions

View file

@ -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

View file

@ -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

6
grid.c
View file

@ -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));
@ -483,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;
@ -1045,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;

170
render.c
View file

@ -3663,13 +3663,64 @@ tiocswinsz(struct terminal *term)
}
}
static void
delayed_reflow_of_normal_grid(struct terminal *term)
{
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,
};
/* Reflow the original (since before the resize was started) grid,
* to the *current* dimensions */
grid_resize_and_reflow(
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->interactive_resizing.grid;
free(term->interactive_resizing.grid);
/* Reset */
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);
}
term_ptmx_resume(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 +3737,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) {
@ -3730,8 +3782,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);
}
}
}
@ -3846,9 +3900,65 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
const uint32_t scrollback_lines = term->render.scrollback_lines;
/*
* Since text reflow is slow, dont 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->interactive_resizing.grid == NULL) {
term_ptmx_pause(term);
/* Stash the current normal grid, as-is, to be used when
* doing the final reflow */
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. Theyll be "restored" when we do the final reflow.
*
* We use the alt screens row count, since we dont 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 = term->alt.num_rows,
.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 */
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,8 +3992,11 @@ 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->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;
}
@ -3906,16 +4019,45 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
* selections 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->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_alt_grid_rows, new_cols, old_rows, new_rows);
} else {
/* Full text reflow */
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->interactive_resizing.grid;
free(term->interactive_resizing.grid);
old_rows = term->interactive_resizing.old_screen_rows;
term->interactive_resizing.grid = NULL;
term->interactive_resizing.old_screen_rows = 0;
term->interactive_resizing.new_rows = 0;
term_ptmx_resume(term);
}
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);

146
sixel.c
View file

@ -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 didnt overlap may now do so, which isnt
* 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 didnt overlap may now do so, which isnt
* 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

View file

@ -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);
/*

View file

@ -7,6 +7,7 @@
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/wait.h>
@ -255,8 +256,18 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
cursor_blink_rearm_timer(term);
}
if (unlikely(term->interactive_resizing.grid != NULL)) {
/*
* Dont consume PTMX while were doing an interactive resize,
* since the normal grid were 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_t)-1ll;
const size_t max_iterations = !hup ? 10 : SIZE_MAX;
for (size_t i = 0; i < max_iterations && pollin; i++) {
xassert(pollin);
@ -278,6 +289,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);
}
@ -358,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)
{

View file

@ -598,6 +598,12 @@ struct terminal {
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 ", $, -, ? ... ~ */
@ -805,6 +811,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);
bool term_ptmx_pause(struct terminal *term);
bool term_ptmx_resume(struct terminal *term);
static inline void term_reset_grapheme_state(struct terminal *term)
{
#if defined(FOOT_GRAPHEME_CLUSTERING)