mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
sixel: implement private mode 80 - sixel scrolling
When enabled (the default), sixels behave much like normal output; the start where the cursor is, and the cursor moves with the sixel. I.e. after emitting a sixel the cursor is left after the image; either to the right, if private mode 8452 is enabled, or otherwise on the next line. Terminal content is scrolled up if the sixel is larger than the screen. When disabled, sixels *always* start at (0,0), the cursor never moves, and the terminal content never scrolls. In other words, the ‘disabled’ mode is a much simpler mode. All we need to do to support both modes is re-write the sixel-emitting loop to: * break early if we’re “out of rows”, i.e. we’ve reached the bottom of the screen. * not linefeed, or move the cursor when scrolling is disabled This patch also fixes a bug in the (new) implementation of private mode 8452. When emitting a sixel, we may break it up into smaller pieces, to ensure a single sixel (as tracked internally) does not cross the scrollback wrap-around. The code that checked if we should do a linefeed or not, would skip the linefeed on the last row of *each* such sixel piece. The correct thing to do is to skip it only on the last row of the *last* piece. I chose not to fix this bug in a separate patch since doing so would have meant re-writing it again when implementing private mode 80.
This commit is contained in:
parent
792202bf29
commit
849427bf10
5 changed files with 75 additions and 41 deletions
|
|
@ -45,6 +45,8 @@
|
|||
`footclient` (https://codeberg.org/dnkl/foot/issues/337).
|
||||
* `-D,--working-directory=DIR` to both `foot` and `footclient`
|
||||
(https://codeberg.org/dnkl/foot/issues/347)
|
||||
* `DECSET 80` - sixel scrolling
|
||||
(https://codeberg.org/dnkl/foot/issues/361).
|
||||
* `DECSET 1070` - sixel private color palette
|
||||
(https://codeberg.org/dnkl/foot/issues/362).
|
||||
* `DECSET 8452` - position cursor to the right of sixels
|
||||
|
|
|
|||
7
csi.c
7
csi.c
|
|
@ -401,6 +401,10 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
term->reverse_wrap = enable;
|
||||
break;
|
||||
|
||||
case 80:
|
||||
term->sixel.scrolling = enable;
|
||||
break;
|
||||
|
||||
case 1000:
|
||||
if (enable)
|
||||
term->mouse_tracking = MOUSE_CLICK;
|
||||
|
|
@ -599,6 +603,7 @@ decrqm(const struct terminal *term, unsigned param, bool *enabled)
|
|||
case 12: *enabled = term->cursor_blink.decset; return true;
|
||||
case 25: *enabled = !term->hide_cursor; return true;
|
||||
case 45: *enabled = term->reverse_wrap; return true;
|
||||
case 80: *enabled = term->sixel.scrolling; return true;
|
||||
case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true;
|
||||
case 1001: *enabled = false; return true;
|
||||
case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true;
|
||||
|
|
@ -640,6 +645,7 @@ xtsave(struct terminal *term, unsigned param)
|
|||
case 25: term->xtsave.show_cursor = !term->hide_cursor; break;
|
||||
case 45: term->xtsave.reverse_wrap = term->reverse_wrap; break;
|
||||
case 47: term->xtsave.alt_screen = term->grid == &term->alt; break;
|
||||
case 80: term->xtsave.sixel_scrolling = term->sixel.scrolling; break;
|
||||
case 1000: term->xtsave.mouse_click = term->mouse_tracking == MOUSE_CLICK; break;
|
||||
case 1001: break;
|
||||
case 1002: term->xtsave.mouse_drag = term->mouse_tracking == MOUSE_DRAG; break;
|
||||
|
|
@ -680,6 +686,7 @@ xtrestore(struct terminal *term, unsigned param)
|
|||
case 25: enable = term->xtsave.show_cursor; break;
|
||||
case 45: enable = term->xtsave.reverse_wrap; break;
|
||||
case 47: enable = term->xtsave.alt_screen; break;
|
||||
case 80: enable = term->xtsave.sixel_scrolling; break;
|
||||
case 1000: enable = term->xtsave.mouse_click; break;
|
||||
case 1001: return;
|
||||
case 1002: enable = term->xtsave.mouse_drag; break;
|
||||
|
|
|
|||
99
sixel.c
99
sixel.c
|
|
@ -701,24 +701,43 @@ sixel_unhook(struct terminal *term)
|
|||
const int stride = term->sixel.image.width * sizeof(uint32_t);
|
||||
|
||||
/*
|
||||
* Need to 'remember' current cursor column.
|
||||
* When sixel scrolling is enabled (the default), sixels behave
|
||||
* pretty much like normal output; the sixel starts at the current
|
||||
* cursor position and the cursor is moved to a point after the
|
||||
* sixel.
|
||||
*
|
||||
* If we split up the sixel (to avoid scrollback wrap-around), we
|
||||
* will emit a carriage-return (after several linefeeds), which
|
||||
* will reset the cursor column to 0. If we use _that_ column for
|
||||
* the subsequent image parts, the image will look sheared.
|
||||
* Furthermore, if the sixel reaches the bottom of the scrolling
|
||||
* region, the terminal content is scrolled.
|
||||
*
|
||||
* When scrolling is disabled, sixels always start at (0,0), the
|
||||
* cursor is not moved at all, and the terminal content never
|
||||
* scrolls.
|
||||
*/
|
||||
const int start_col = term->grid->cursor.point.col;
|
||||
|
||||
const bool do_scroll = term->sixel.scrolling;
|
||||
|
||||
/* Number of rows we're allowed to use.
|
||||
*
|
||||
* When scrolling is enabled, we always allow the entire sixel to
|
||||
* be emitted.
|
||||
*
|
||||
* When disabled, only the number of screen rows may be used. */
|
||||
int rows_avail = do_scroll
|
||||
? (term->sixel.image.height + term->cell_height - 1) / term->cell_height
|
||||
: term->rows;
|
||||
|
||||
/* Initial sixel coordinates */
|
||||
int start_row = do_scroll ? term->grid->cursor.point.row : 0;
|
||||
const int start_col = do_scroll ? term->grid->cursor.point.col : 0;
|
||||
|
||||
/* We do not allow sixels to cross the scrollback wrap-around, as
|
||||
* this makes intersection calculations much more complicated */
|
||||
while (pixel_rows_left > 0) {
|
||||
const struct coord *cursor = &term->grid->cursor.point;
|
||||
while (pixel_rows_left > 0 && rows_avail > 0) {
|
||||
const int cur_row = (term->grid->offset + start_row) & (term->grid->num_rows - 1);
|
||||
const int rows_left_until_wrap_around = term->grid->num_rows - cur_row;
|
||||
const int usable_rows = min(rows_avail, rows_left_until_wrap_around);
|
||||
|
||||
const int cur_row = (term->grid->offset + cursor->row) & (term->grid->num_rows - 1);
|
||||
const int rows_avail = term->grid->num_rows - cur_row;
|
||||
|
||||
const int pixel_rows_avail = rows_avail * term->cell_height;
|
||||
const int pixel_rows_avail = usable_rows * term->cell_height;
|
||||
|
||||
const int width = term->sixel.image.width;
|
||||
const int height = min(pixel_rows_left, pixel_rows_avail);
|
||||
|
|
@ -755,12 +774,15 @@ sixel_unhook(struct terminal *term)
|
|||
image.width, image.height,
|
||||
img_data, stride);
|
||||
|
||||
/* Allocate space *first* (by emitting line-feeds), then insert */
|
||||
pixel_row_idx += height;
|
||||
pixel_rows_left -= height;
|
||||
rows_avail -= image.rows;
|
||||
|
||||
/* Dirty touched cells, and scroll terminal content if necessary */
|
||||
for (size_t i = 0; i < image.rows; i++) {
|
||||
struct row *row = term->grid->cur_row;
|
||||
struct row *row = term->grid->rows[cur_row + i];
|
||||
row->dirty = true;
|
||||
|
||||
/* Mark cells touched by the sixel as dirty */
|
||||
for (int col = image.pos.col;
|
||||
col < min(image.pos.col + image.cols, term->cols);
|
||||
col++)
|
||||
|
|
@ -768,36 +790,37 @@ sixel_unhook(struct terminal *term)
|
|||
row->cells[col].attrs.clean = 0;
|
||||
}
|
||||
|
||||
if (i < image.rows - 1 || !term->sixel.cursor_right_of_graphics)
|
||||
term_linefeed(term);
|
||||
if (do_scroll) {
|
||||
/*
|
||||
* Linefeed, *unless* we're on the very last row of
|
||||
* the final image (not just this chunk) and private
|
||||
* mode 8452 (leave cursor at the right of graphics)
|
||||
* is enabled.
|
||||
*/
|
||||
if (term->sixel.cursor_right_of_graphics &&
|
||||
rows_avail == 0 &&
|
||||
i >= image.rows - 1)
|
||||
{
|
||||
term_cursor_to(
|
||||
term,
|
||||
term->grid->cursor.point.row,
|
||||
min(image.pos.col + image.cols, term->cols - 1));
|
||||
} else {
|
||||
term_linefeed(term);
|
||||
term_carriage_return(term);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Position cursor
|
||||
*
|
||||
* Private mode 8452 controls where we leave the cursor after
|
||||
* emitting a sixel:
|
||||
*
|
||||
* When disabled (the default), the cursor is positioned on a
|
||||
* new line.
|
||||
*
|
||||
* When enabled, the cursor is positioned to the right of the
|
||||
* sixel.
|
||||
*/
|
||||
term_cursor_to(
|
||||
term,
|
||||
term->grid->cursor.point.row,
|
||||
(term->sixel.cursor_right_of_graphics
|
||||
? min(image.pos.col + image.cols, term->cols - 1)
|
||||
: 0));
|
||||
|
||||
_sixel_overwrite_by_rectangle(
|
||||
term, image.pos.row, image.pos.col, image.rows, image.cols);
|
||||
|
||||
sixel_insert(term, image);
|
||||
|
||||
pixel_row_idx += height;
|
||||
pixel_rows_left -= height;
|
||||
if (do_scroll)
|
||||
start_row = term->grid->cursor.point.row;
|
||||
else
|
||||
start_row -= image.rows;
|
||||
}
|
||||
|
||||
term->sixel.image.data = NULL;
|
||||
|
|
|
|||
|
|
@ -1178,6 +1178,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
|||
.upper_fd = delay_upper_fd,
|
||||
},
|
||||
.sixel = {
|
||||
.scrolling = true,
|
||||
.use_private_palette = true,
|
||||
.palette_size = SIXEL_MAX_COLORS,
|
||||
.max_width = SIXEL_MAX_WIDTH,
|
||||
|
|
|
|||
|
|
@ -360,6 +360,7 @@ struct terminal {
|
|||
bool modify_escape_key:1;
|
||||
bool ime:1;
|
||||
|
||||
bool sixel_scrolling:1;
|
||||
bool sixel_private_palette:1;
|
||||
bool sixel_cursor_right_of_graphics:1;
|
||||
} xtsave;
|
||||
|
|
@ -533,14 +534,14 @@ struct terminal {
|
|||
bool autosize;
|
||||
} image;
|
||||
|
||||
bool use_private_palette:1; /* Private mode 1070 */
|
||||
bool scrolling:1; /* Private mode 80 */
|
||||
bool use_private_palette:1; /* Private mode 1070 */
|
||||
bool cursor_right_of_graphics:1; /* Private mode 8452 */
|
||||
|
||||
unsigned params[5]; /* Collected parameters, for RASTER, COLOR_SPEC */
|
||||
unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */
|
||||
unsigned param_idx; /* Parameters seen */
|
||||
|
||||
bool cursor_right_of_graphics:1; /* Private mode 8452 */
|
||||
|
||||
/* Application configurable */
|
||||
unsigned palette_size; /* Number of colors in palette */
|
||||
unsigned max_width; /* Maximum image width, in pixels */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue