Merge branch 'sixel-private-mode-80-scrolling'

Closes #361
This commit is contained in:
Daniel Eklöf 2021-02-27 11:49:47 +01:00
commit 3f57cc9c01
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 75 additions and 41 deletions

View file

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

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

@ -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->scroll_region.end;
/* 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;

View file

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

View file

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