mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Merge branch 'delayed-reflow'
This commit is contained in:
commit
3949e34271
8 changed files with 288 additions and 93 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
6
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));
|
||||
|
|
@ -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
170
render.c
|
|
@ -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, 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->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. 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 = 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)
|
|||
* 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->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
146
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
|
||||
|
|
|
|||
4
sixel.h
4
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);
|
||||
|
||||
/*
|
||||
|
|
|
|||
26
terminal.c
26
terminal.c
|
|
@ -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)) {
|
||||
/*
|
||||
* 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_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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue