diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba2ef59..5a5fe627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,24 +63,24 @@ `0x0` terminal size (https://codeberg.org/dnkl/foot/issues/20). * Glyphs overflowing into surrounding cells (https://codeberg.org/dnkl/foot/issues/21). -* Sixel images being erased when printing text next to them. * Crash when last rendered cursor cell had scrolled off screen and `\E[J3` was executed. * Assert (debug builds) when an `\e]4` OSC escape was not followed by a `;`. +* Window title always being set to "foot" on reset. +* Terminfo entry `kb2` (center keypad key); it is now set to `\EOu` + (which is what foot emits) instead of the incorrect value `\EOE`. * Palette re-use in sixel images. Previously, the palette was reset after each image. * Do not auto-resize a sixel image for which the cllent has specified a size. This fixes an issue where an image would incorrectly overflow into the cell row beneath. -* Window title always being set to "foot" on reset. -* Erase scrolled out sixel image that crossed the scrollback wrap - around boundary. * Text printed, or other sixel images drawn, on top of a sixel image no longer erases the entire image, only the part(s) covered by the new text or image. -* Terminfo entry `kb2` (center keypad key); it is now set to `\EOu` - (which is what foot emits) instead of the incorrect value `\EOE`. +* Sixel images being erased when printing text next to them. +* Sixel handling when resizing window. +* Sixel handling when scrollback wraps around. ### Security diff --git a/grid.c b/grid.c index d1d625c6..cdc5db72 100644 --- a/grid.c +++ b/grid.c @@ -76,7 +76,10 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, * at the output that is *oldest* */ int offset = grid->offset + old_screen_rows; - tll(struct sixel) new_sixels = tll_init(); + tll(struct sixel) old_sixels = tll_init(); + tll_foreach(grid->sixel_images, it) + tll_push_back(old_sixels, it->item); + tll_free(grid->sixel_images); /* Turn cursor coordinates into grid absolute coordinates */ struct coord cursor = grid->cursor.point; @@ -106,31 +109,57 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, if (old_row == NULL) continue; - /* - * Update 'row' in all sixels that *begin* at current row - * - * Since we might end up pushing the sixel down, we can't - * simply update the row inline - we'd then end up pushing the - * sixel down again, when we reach the next 'old' - * row. Instead, copy the sixel (with 'row' updated), to a - * temporary list and remove the original sixel. - * - * After we've reflowed the grid we'll move the sixels back to - * the "real" sixel list. - */ - tll_foreach(grid->sixel_images, it) { - if (it->item.pos.row == old_row_idx) { - struct sixel six = it->item; - six.pos.row = new_row_idx; + /* Map sixels on current "old" row to current "new row" */ + tll_foreach(old_sixels, it) { + if (it->item.pos.row != old_row_idx) + continue; + + struct sixel sixel = it->item; + sixel.pos.row = new_row_idx; + + /* Make sure it doesn't cross the wrap-around after being re-based */ + int end = (sixel.pos.row + sixel.rows - 1) & (new_rows - 1); + if (end < sixel.pos.row) { + /* TODO: split instead of destroying */ + sixel_destroy(&it->item); + } else { + + /* Insert sixel into the *sorted* list. */ + + /* Based on rebase_row() in sixel.c */ + /* Uses 'old' offset to ensure old sixels are treated as such */ +#define rebase_row(t, row) \ + (((row) - (grid->offset + new_screen_rows) + new_rows) & (new_rows - 1)) + + int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1); + + /* + * TODO: this is basically sixel_insert(), except we + * cannot use it since: + * + * a) we don't have a 'term' reference + * b) the grid hasn't been fully * updated yet + * (e.g. grid->num_rows is invalid etc). + */ + + bool inserted = false; + tll_foreach(grid->sixel_images, it2) { + const struct sixel *s = &it2->item; + if (rebase_row(term, s->pos.row + s->rows - 1) < end_row) { + tll_insert_before(grid->sixel_images, it2, sixel); + inserted = true; + break; + } + } + + if (!inserted) + tll_push_back(grid->sixel_images, sixel); - int end = (six.pos.row + six.rows - 1) & (new_rows - 1); - if (end < six.pos.row) { - /* TODO: split sixel instead of removing it... */ - sixel_destroy(&it->item); - } else - tll_push_back(new_sixels, six); - tll_remove(grid->sixel_images, it); } + + /* Sixel has been either re-mapped, or destroyed */ + tll_remove(old_sixels, it); +#undef rebase_row } #define line_wrap() \ @@ -145,6 +174,12 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, } else { \ memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \ new_row->linebreak = false; \ + tll_foreach(grid->sixel_images, it) { \ + if (it->item.pos.row == new_row_idx) { \ + sixel_destroy(&it->item); \ + tll_remove(grid->sixel_images, it); \ + } \ + } \ } \ } while(0) @@ -279,15 +314,10 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, grid->cursor.lcf = false; grid->saved_cursor.lcf = false; - /* Destroy any non-moved sixels */ - tll_foreach(grid->sixel_images, it) + /* Free sixels we failed to "map" to the new grid */ + tll_foreach(old_sixels, it) sixel_destroy(&it->item); - tll_free(grid->sixel_images); - - /* Move updated sixels back */ - tll_foreach(new_sixels, it) - tll_push_back(grid->sixel_images, it->item); - tll_free(new_sixels); + tll_free(old_sixels); tll_free(tracking_points); } diff --git a/render.c b/render.c index 8fd7d42e..391115d6 100644 --- a/render.c +++ b/render.c @@ -795,8 +795,42 @@ render_sixel(struct terminal *term, pixman_image_t *pix, static void render_sixel_images(struct terminal *term, pixman_image_t *pix) { - tll_foreach(term->grid->sixel_images, it) + if (likely(tll_length(term->grid->sixel_images)) == 0) + return; + + const int scrollback_end + = (term->grid->offset + term->rows) & (term->grid->num_rows - 1); + + const int view_start + = (term->grid->view + - scrollback_end + + term->grid->num_rows) & (term->grid->num_rows - 1); + + const int view_end = view_start + term->rows - 1; + + //LOG_DBG("SIXELS: %zu images, view=%d-%d", + // tll_length(term->grid->sixel_images), view_start, view_end); + + tll_foreach(term->grid->sixel_images, it) { + const struct sixel *six = &it->item; + const int start + = (six->pos.row + - scrollback_end + + term->grid->num_rows) & (term->grid->num_rows - 1); + const int end = start + six->rows - 1; + + //LOG_DBG(" sixel: %d-%d", start, end); + if (start > view_end) { + /* Sixel starts after view ends, no need to try to render it */ + continue; + } else if (end < view_start) { + /* Image ends before view starts. Since the image list is + * sorted, we can safely stop here */ + break; + } + render_sixel(term, pix, &it->item); + } } static void diff --git a/sixel.c b/sixel.c index 80ac3843..0a28defd 100644 --- a/sixel.c +++ b/sixel.c @@ -98,102 +98,92 @@ sixel_erase(struct terminal *term, struct sixel *sixel) sixel_destroy(sixel); } -/* Row numbers are absolute */ -static void -sixel_delete_at_point(struct terminal *term, int row, int col) +static int +rebase_row(const struct terminal *term, int abs_row) { - assert(row >= 0); - assert(row < term->grid->num_rows); - assert(col < term->grid->num_cols); + int scrollback_start = term->grid->offset + term->rows; + int rebased_row = abs_row - scrollback_start + term->grid->num_rows; - if (likely(tll_length(term->grid->sixel_images) == 0)) - return; + rebased_row &= term->grid->num_rows - 1; + return rebased_row; +} + +static bool +verify_sixel_list_order(const struct terminal *term) +{ +#if defined(_DEBUG) + int prev_row = INT_MAX; tll_foreach(term->grid->sixel_images, it) { - struct sixel *six = &it->item; - const int six_start = six->pos.row; - const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); + int row = rebase_row(term, it->item.pos.row + it->item.rows - 1); + assert(row < prev_row); + if (row >= prev_row) + return false; + prev_row = row; + } +#endif + return true; +} - /* We should never generate scrollback wrapping sixels */ - assert(six_end >= six_start); +static void +sixel_insert(struct terminal *term, struct sixel sixel) +{ + int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1); - if (row >= six_start && row <= six_end) { - const int col_start = six->pos.col; - const int col_end = six->pos.col + six->cols; - - if (col < 0 || (col >= col_start && col < col_end)) { - sixel_erase(term, six); - tll_remove(term->grid->sixel_images, it); - } + tll_foreach(term->grid->sixel_images, it) { + if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) { + tll_insert_before(term->grid->sixel_images, it, sixel); + goto out; } } -} -/* TODO: remove */ -void -sixel_delete_at_row(struct terminal *term, int row) -{ - if (likely(tll_length(term->grid->sixel_images) == 0)) - return; - - sixel_delete_at_point( - term, (term->grid->offset + row) & (term->grid->num_rows - 1), -1); -} - -/* Row numbers are absolute */ -static void -_sixel_delete_in_range(struct terminal *term, int start, int end) -{ - assert(end >= start); - assert(start >= 0); - assert(start < term->grid->num_rows); - assert(end >= 0); - assert(end < term->grid->num_rows); + tll_push_back(term->grid->sixel_images, sixel); +out: +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG + LOG_DBG("sixel list after insertion:"); tll_foreach(term->grid->sixel_images, it) { + LOG_DBG(" rows=%d+%d", it->item.pos.row, it->item.rows); + } +#else + verify_sixel_list_order(term); +#endif +} + +void +sixel_scroll_up(struct terminal *term, int rows) +{ + tll_rforeach(term->grid->sixel_images, it) { struct sixel *six = &it->item; - const int six_start = six->pos.row; - const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); - - /* We should never generate scrollback wrapping sixels */ - assert(six_end >= six_start); - - if ((start <= six_start && end >= six_start) || /* Crosses sixel start boundary */ - (start <= six_end && end >= six_end) || /* Crosses sixel end boundary */ - (start >= six_start && end <= six_end)) /* Fully within sixel range */ - { - sixel_erase(term, six); + int six_start = rebase_row(term, six->pos.row); + if (six_start < rows) { + sixel_destroy(six); tll_remove(term->grid->sixel_images, it); - } + } else + break; } + + verify_sixel_list_order(term); } void -sixel_delete_in_range(struct terminal *term, int _start, int _end) +sixel_scroll_down(struct terminal *term, int rows) { - if (likely(tll_length(term->grid->sixel_images) == 0)) - return; + assert(term->grid->num_rows >= rows); - if (_start == _end) { - /* Avoid expensive wrap calculation */ - return sixel_delete_at_point( - term, (term->grid->offset + _start) & (term->grid->num_rows - 1), -1); + tll_foreach(term->grid->sixel_images, it) { + struct sixel *six = &it->item; + + int six_end = rebase_row(term, six->pos.row + six->rows - 1); + if (six_end >= term->grid->num_rows - rows) { + sixel_destroy(six); + tll_remove(term->grid->sixel_images, it); + } else + break; } - assert(_end >= _start); - const int lines = _end - _start + 1; - const int start = (term->grid->offset + _start) & (term->grid->num_rows - 1); - const int end = (start + lines - 1) & (term->grid->num_rows - 1); - const bool wraps = end < start; - - if (wraps) { - int rows_to_wrap_around = term->grid->num_rows - start; - assert(lines - rows_to_wrap_around > 0); - _sixel_delete_in_range(term, start, term->grid->num_rows); - _sixel_delete_in_range(term, 0, lines - rows_to_wrap_around); - } else - _sixel_delete_in_range(term, start, end); + verify_sixel_list_order(term); } static void @@ -295,7 +285,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six, PIXMAN_a8r8g8b8, imgs[i].width, imgs[i].height, imgs[i].data, imgs[i].width * sizeof(uint32_t)); - tll_push_front(term->grid->sixel_images, imgs[i]); + sixel_insert(term, imgs[i]); } } @@ -304,24 +294,27 @@ static void _sixel_overwrite_by_rectangle( struct terminal *term, int row, int col, int height, int width) { - assert(row + height <= term->grid->num_rows); - assert(row >= 0); assert(row + height <= term->grid->num_rows); assert(col >= 0); assert(col + width <= term->grid->num_cols); - /* We don't handle rectangle wrapping around */ - assert(row + height <= term->grid->num_rows); - const int start = row; const int end = row + height - 1; + const int scrollback_rel_start = rebase_row(term, start); + tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; const int six_start = six->pos.row; const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); + const int six_scrollback_rel_end = rebase_row(term, six_end); + + if (six_scrollback_rel_end < scrollback_rel_start) { + /* All remaining sixels are *before* our rectangle */ + break; + } /* We should never generate scrollback wrapping sixels */ assert(six_end >= six_start); @@ -365,20 +358,23 @@ sixel_overwrite_by_rectangle( _sixel_overwrite_by_rectangle(term, start, col, height, width); } -/* Row numbers are absolute */ -static void -_sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) +/* Row numbers are relative to grid offset */ +void +sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width) { assert(col >= 0); - assert(row >= 0); - assert(row < term->grid->num_rows); + assert(_row >= 0); + assert(_row < term->rows); assert(col >= 0); assert(col + width <= term->grid->num_cols); if (likely(tll_length(term->grid->sixel_images) == 0)) return; + const int row = (term->grid->offset + _row) & (term->grid->num_rows - 1); + const int scrollback_rel_row = rebase_row(term, row); + tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; const int six_start = six->pos.row; @@ -387,6 +383,13 @@ _sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) /* We should never generate scrollback wrapping sixels */ assert(six_end >= six_start); + const int six_scrollback_rel_end = rebase_row(term, six_end); + + if (six_scrollback_rel_end < scrollback_rel_row) { + /* All remaining sixels are *before* "our" row */ + break; + } + if (row >= six_start && row <= six_end) { const int col_start = six->pos.col; const int col_end = six->pos.col + six->cols - 1; @@ -404,19 +407,10 @@ _sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) } void -sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) -{ - _sixel_overwrite_by_row( - term, - (term->grid->offset + row) & (term->grid->num_rows - 1), - col, width); -} - -void -sixel_overwrite_at_cursor(struct terminal *term) +sixel_overwrite_at_cursor(struct terminal *term, int width) { sixel_overwrite_by_row( - term, term->grid->cursor.point.row, term->grid->cursor.point.col, 1); + term, term->grid->cursor.point.row, term->grid->cursor.point.col, width); } void @@ -475,7 +469,7 @@ sixel_unhook(struct terminal *term) term_formfeed(term); render_refresh(term); - tll_push_back(term->grid->sixel_images, image); + sixel_insert(term, image); pixel_row_idx += height; pixel_rows_left -= height; diff --git a/sixel.h b/sixel.h index fc59d6e9..666b8c40 100644 --- a/sixel.h +++ b/sixel.h @@ -13,15 +13,8 @@ void sixel_unhook(struct terminal *term); void sixel_destroy(struct sixel *sixel); void sixel_destroy_all(struct terminal *term); -/* - * Deletes all sixels that are touched by the specified row(s). Used - * when scrolling, to competely remove sixels that has either - * completely, or partly scrolled out of history. - * - * Row numbers are relative to the current grid offset. - */ -void sixel_delete_in_range(struct terminal *term, int row_start, int row_end); -void sixel_delete_at_row(struct terminal *term, int row); +void sixel_scroll_up(struct terminal *term, int rows); +void sixel_scroll_down(struct terminal *term, int rows); /* * Remove sixel data from the specified location. Used when printing @@ -33,7 +26,7 @@ void sixel_delete_at_row(struct terminal *term, int row); void sixel_overwrite_by_rectangle( struct terminal *term, int row, int col, int height, int width); void sixel_overwrite_by_row(struct terminal *term, int row, int col, int width); -void sixel_overwrite_at_cursor(struct terminal *term); +void sixel_overwrite_at_cursor(struct terminal *term, int width); void sixel_colors_report_current(struct terminal *term); void sixel_colors_reset(struct terminal *term); diff --git a/terminal.c b/terminal.c index c3a36b45..c5960c90 100644 --- a/terminal.c +++ b/terminal.c @@ -1802,6 +1802,8 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows } } + sixel_scroll_up(term, rows); + bool view_follows = term->grid->view == term->grid->offset; term->grid->offset += rows; term->grid->offset &= term->grid->num_rows - 1; @@ -1823,7 +1825,6 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows for (int r = region.end - rows; r < region.end; r++) erase_line(term, grid_row_and_alloc(term->grid, r)); - sixel_delete_in_range(term, region.end - rows, region.end - 1); term_damage_scroll(term, DAMAGE_SCROLL, region, rows); term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); @@ -1864,6 +1865,8 @@ term_scroll_reverse_partial(struct terminal *term, } } + sixel_scroll_down(term, rows); + bool view_follows = term->grid->view == term->grid->offset; term->grid->offset -= rows; while (term->grid->offset < 0) @@ -1890,7 +1893,6 @@ term_scroll_reverse_partial(struct terminal *term, for (int r = region.start; r < region.start + rows; r++) erase_line(term, grid_row_and_alloc(term->grid, r)); - sixel_delete_in_range(term, region.start, region.start + rows - 1); term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); @@ -2407,7 +2409,7 @@ term_print(struct terminal *term, wchar_t wc, int width) print_linewrap(term); print_insert(term, width); - sixel_overwrite_at_cursor(term); + sixel_overwrite_at_cursor(term, width); /* *Must* get current cell *after* linewrap+insert */ struct row *row = term->grid->cur_row;