diff --git a/README.md b/README.md index f67ac691..46cd07cc 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ * Scrollback search * Color emoji support * "Server" mode (one master process, many windows) +* [Sixel image support](https://en.wikipedia.org/wiki/Sixel) + + ![wow](doc/sixel-wow.png "Sixel screenshot") + ## Non-features diff --git a/commands.c b/commands.c index 572dae68..53d0dfb3 100644 --- a/commands.c +++ b/commands.c @@ -55,6 +55,9 @@ cmd_scrollback_up(struct terminal *term, int rows) new_view = end + 1; } + while (term->grid->rows[new_view] == NULL) + new_view = (new_view + 1) % term->grid->num_rows; + #if defined(_DEBUG) for (int r = 0; r < term->rows; r++) assert(term->grid->rows[(new_view + r) % term->grid->num_rows] != NULL); diff --git a/csi.c b/csi.c index 3fc64ee7..38d017f5 100644 --- a/csi.c +++ b/csi.c @@ -15,6 +15,7 @@ #include "grid.h" #include "vt.h" #include "selection.h" +#include "sixel.h" #define min(x, y) ((x) < (y) ? (x) : (y)) @@ -372,7 +373,7 @@ csi_dispatch(struct terminal *term, uint8_t final) * - 28 Rectangular editing. * - 29 ANSI text locator (i.e., DEC Locator mode). */ - const char *reply = "\033[?62;6;15;17;22c"; + const char *reply = "\033[?62;4;6;15;17;22c"; term_to_slave(term, reply, strlen(reply)); break; } @@ -1227,6 +1228,32 @@ csi_dispatch(struct terminal *term, uint8_t final) } break; + case 'S': { + unsigned target = vt_param_get(term, 0, 0); + unsigned operation = vt_param_get(term, 1, 0); + + switch (target) { + case 1: + switch (operation) { + case 1: sixel_colors_report_current(term); break; + case 2: sixel_colors_reset(term); break; + case 3: sixel_colors_set(term, vt_param_get(term, 2, 0)); break; + case 4: sixel_colors_report_max(term); + } + break; + + case 2: + switch (operation) { + case 1: sixel_geometry_report_current(term); break; + case 2: sixel_geometry_reset(term); break; + case 3: sixel_geometry_set(term, vt_param_get(term, 2, 0), vt_param_get(term, 3, 0)); break; + case 4: sixel_geometry_report_max(term); + } + break; + } + break; + } + default: UNHANDLED(); break; diff --git a/dcs.c b/dcs.c index 048cd62e..f9e3c10c 100644 --- a/dcs.c +++ b/dcs.c @@ -3,6 +3,7 @@ #define LOG_MODULE "dcs" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "sixel.h" #include "vt.h" static void @@ -35,9 +36,20 @@ dcs_hook(struct terminal *term, uint8_t final) assert(term->vt.dcs.data == NULL); assert(term->vt.dcs.size == 0); + assert(term->vt.dcs.put_handler == NULL); assert(term->vt.dcs.unhook_handler == NULL); switch (term->vt.private[0]) { + case 0: + switch (final) { + case 'q': + sixel_init(term); + term->vt.dcs.put_handler = &sixel_put; + term->vt.dcs.unhook_handler = &sixel_unhook; + break; + } + break; + case '=': switch (final) { case 's': @@ -75,9 +87,13 @@ void dcs_put(struct terminal *term, uint8_t c) { LOG_DBG("PUT: %c", c); - if (!ensure_size(term, term->vt.dcs.idx + 1)) - return; - term->vt.dcs.data[term->vt.dcs.idx++] = c; + if (term->vt.dcs.put_handler != NULL) + term->vt.dcs.put_handler(term, c); + else { + if (!ensure_size(term, term->vt.dcs.idx + 1)) + return; + term->vt.dcs.data[term->vt.dcs.idx++] = c; + } } void @@ -87,6 +103,7 @@ dcs_unhook(struct terminal *term) term->vt.dcs.unhook_handler(term); term->vt.dcs.unhook_handler = NULL; + term->vt.dcs.put_handler = NULL; free(term->vt.dcs.data); term->vt.dcs.data = NULL; diff --git a/doc/sixel-wow.png b/doc/sixel-wow.png new file mode 100644 index 00000000..da481ac8 Binary files /dev/null and b/doc/sixel-wow.png differ diff --git a/meson.build b/meson.build index 2da5c65f..21c23718 100644 --- a/meson.build +++ b/meson.build @@ -117,6 +117,8 @@ executable( 'selection.c', 'selection.h', 'server.c', 'server.h', 'shm.c', 'shm.h', + 'sixel.c', 'sixel.h', + 'sixel-hls.c', 'sixel-hls.h', 'slave.c', 'slave.h', 'terminal.c', 'terminal.h', 'tokenize.c', 'tokenize.h', diff --git a/render.c b/render.c index bb229288..a4741b90 100644 --- a/render.c +++ b/render.c @@ -519,6 +519,79 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, } } +static void +render_sixel(struct terminal *term, pixman_image_t *pix, + const struct sixel *sixel) +{ + int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1); + int first_visible_row = -1; + + for (size_t i = sixel->pos.row; i < sixel->pos.row + sixel->rows; i++) { + int row = i & (term->grid->num_rows - 1); + + if (view_end >= term->grid->view) { + /* Not wrapped */ + if (row >= term->grid->view && row <= view_end) { + first_visible_row = i; + break; + } + } else { + /* Wrapped */ + if (row >= term->grid->view || row <= view_end) { + first_visible_row = i; + break; + } + } + } + + if (first_visible_row < 0) + return; + + /* First visible (0 based) row of the image */ + const int first_img_row = first_visible_row - sixel->pos.row; + + /* Map first visible line to current grid view */ + const int row = first_visible_row & (term->grid->num_rows - 1); + const int view_aligned = + (row - term->grid->view + term->grid->num_rows) & (term->grid->num_rows - 1); + + /* Translate row/column to x/y pixel values */ + const int x = term->x_margin + sixel->pos.col * term->cell_width; + const int y = max( + term->y_margin, term->y_margin + view_aligned * term->cell_height); + + /* Width/height, in pixels - and don't touch the window margins */ + const int width = min(sixel->width, term->width - x - term->x_margin); + const int height = min( + sixel->height - first_img_row * term->cell_height, + term->height - y - term->y_margin); + + /* Verify we're not stepping outside the grid */ + assert(x >= term->x_margin); + assert(y >= term->y_margin); + assert(x + width <= term->width - term->x_margin); + assert(y + height <= term->height - term->y_margin); + + pixman_image_composite( + PIXMAN_OP_SRC, + sixel->pix, + NULL, + pix, + 0, first_img_row * term->cell_height, + 0, 0, + x, y, + width, height); + + wl_surface_damage_buffer(term->window->surface, x, y, width, height); +} + +static void +render_sixel_images(struct terminal *term, pixman_image_t *pix) +{ + tll_foreach(term->sixel_images, it) + render_sixel(term, pix, &it->item); +} + static void render_row(struct terminal *term, pixman_image_t *pix, struct row *row, int row_no) { @@ -816,6 +889,8 @@ grid_render(struct terminal *term) cols_updated * term->cell_width, term->cell_height); } + render_sixel_images(term, pix); + if (term->flash.active) { /* Note: alpha is pre-computed in each color component */ /* TODO: dim while searching */ diff --git a/sixel-hls.c b/sixel-hls.c new file mode 100644 index 00000000..8b88b414 --- /dev/null +++ b/sixel-hls.c @@ -0,0 +1,117 @@ +// sixel.c (part of mintty) +// this function is derived from a part of graphics.c +// in Xterm pl#310 originally written by Ross Combs. +// +// Copyright 2013,2014 by Ross Combs +// +// All Rights Reserved +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name(s) of the above copyright +// holders shall not be used in advertising or otherwise to promote the +// sale, use or other dealings in this Software without prior written +// authorization. + +#include "sixel-hls.h" + +#define SIXEL_RGB(r, g, b) (((r) << 16) + ((g) << 8) + (b)) + +int +hls_to_rgb(int hue, int lum, int sat) +{ + double hs = (hue + 240) % 360; + double hv = hs / 360.0; + double lv = lum / 100.0; + double sv = sat / 100.0; + double c, x, m, c2; + double r1, g1, b1; + int r, g, b; + int hpi; + + if (sat == 0) { + r = g = b = lum * 255 / 100; + return SIXEL_RGB(r, g, b); + } + + if ((c2 = ((2.0 * lv) - 1.0)) < 0.0) { + c2 = -c2; + } + c = (1.0 - c2) * sv; + hpi = (int) (hv * 6.0); + x = (hpi & 1) ? c : 0.0; + m = lv - 0.5 * c; + + switch (hpi) { + case 0: + r1 = c; + g1 = x; + b1 = 0.0; + break; + case 1: + r1 = x; + g1 = c; + b1 = 0.0; + break; + case 2: + r1 = 0.0; + g1 = c; + b1 = x; + break; + case 3: + r1 = 0.0; + g1 = x; + b1 = c; + break; + case 4: + r1 = x; + g1 = 0.0; + b1 = c; + break; + case 5: + r1 = c; + g1 = 0.0; + b1 = x; + break; + default: + return SIXEL_RGB(255, 255, 255); + } + + r = (int) ((r1 + m) * 100.0 + 0.5); + g = (int) ((g1 + m) * 100.0 + 0.5); + b = (int) ((b1 + m) * 100.0 + 0.5); + + if (r < 0) { + r = 0; + } else if (r > 100) { + r = 100; + } + if (g < 0) { + g = 0; + } else if (g > 100) { + g = 100; + } + if (b < 0) { + b = 0; + } else if (b > 100) { + b = 100; + } + return SIXEL_RGB(r * 255 / 100, g * 255 / 100, b * 255 / 100); +} diff --git a/sixel-hls.h b/sixel-hls.h new file mode 100644 index 00000000..6d6b9f2f --- /dev/null +++ b/sixel-hls.h @@ -0,0 +1,9 @@ +#pragma once + +/* + * Primary color hues: + * blue: 0 degrees + * red: 120 degrees + * green: 240 degrees + */ +int hls_to_rgb(int hue, int lum, int sat); diff --git a/sixel.c b/sixel.c new file mode 100644 index 00000000..19dd6069 --- /dev/null +++ b/sixel.c @@ -0,0 +1,482 @@ +#include "sixel.h" + +#include + +#define LOG_MODULE "sixel" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "render.h" +#include "sixel-hls.h" + +#define ALEN(v) (sizeof(v) / sizeof(v[0])) +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + + +static size_t count; + +void +sixel_init(struct terminal *term) +{ + assert(term->sixel.palette == NULL); + assert(term->sixel.image.data == NULL); + assert(term->sixel.palette_size <= SIXEL_MAX_COLORS); + + term->sixel.state = SIXEL_DECSIXEL; + term->sixel.pos = (struct coord){0, 0}; + term->sixel.color_idx = 0; + term->sixel.max_col = 0; + term->sixel.param = 0; + term->sixel.param_idx = 0; + memset(term->sixel.params, 0, sizeof(term->sixel.params)); + term->sixel.palette = calloc(term->sixel.palette_size, sizeof(term->sixel.palette[0])); + term->sixel.image.data = malloc(1 * 6 * sizeof(term->sixel.image.data[0])); + term->sixel.image.width = 1; + term->sixel.image.height = 6; + + for (size_t i = 0; i < 1 * 6; i++) + term->sixel.image.data[i] = term->colors.alpha / 256 << 24 | term->colors.bg; + + count = 0; + + /* TODO: default palette */ +} + +void +sixel_destroy(struct sixel *sixel) +{ + pixman_image_unref(sixel->pix); + free(sixel->data); + + sixel->pix = NULL; + sixel->data = NULL; +} + +void +sixel_purge_at_cursor(struct terminal *term) +{ + const int row = term->grid->offset + term->cursor.point.row; + + tll_foreach(term->sixel_images, it) { + const int start = it->item.pos.row; + const int end = start + it->item.rows; + + if (row >= start && row < end) { + sixel_destroy(&it->item); + tll_remove(term->sixel_images, it); + } + } +} + +void +sixel_unhook(struct terminal *term) +{ + free(term->sixel.palette); + term->sixel.palette = NULL; + + sixel_purge_at_cursor(term); + + struct sixel image = { + .data = term->sixel.image.data, + .width = term->sixel.image.width, + .height = term->sixel.image.height, + .rows = (term->sixel.image.height + term->cell_height - 1) / term->cell_height, + .pos = (struct coord){term->cursor.point.col, term->grid->offset + term->cursor.point.row}, + }; + + LOG_DBG("generating %dx%d pixman image", image.width, image.height); + + image.pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, + image.width, image.height, + term->sixel.image.data, + term->sixel.image.width * sizeof(uint32_t)); + + tll_push_back(term->sixel_images, image); + + term->sixel.image.data = NULL; + term->sixel.image.width = 0; + term->sixel.image.height = 0; + term->sixel.max_col = 0; + term->sixel.pos = (struct coord){0, 0}; + + for (size_t i = 0; i < image.rows; i++) + term_linefeed(term); + term_formfeed(term); + render_refresh(term); +} + +static unsigned +max_width(const struct terminal *term) +{ + /* foot extension - treat 0 to mean current terminal size */ + return term->sixel.max_width == 0 + ? term->cols * term->cell_width + : term->sixel.max_width; +} + +static unsigned +max_height(const struct terminal *term) +{ + /* foot extension - treat 0 to mean current terminal size */ + return term->sixel.max_height == 0 + ? term->rows * term->cell_height + : term->sixel.max_height; +} + +static bool +resize(struct terminal *term, int new_width, int new_height) +{ + LOG_DBG("resizing image: %dx%d -> %dx%d", + term->sixel.image.width, term->sixel.image.height, + new_width, new_height); + + uint32_t *old_data = term->sixel.image.data; + const int old_width = term->sixel.image.width; + const int old_height = term->sixel.image.height; + + assert(new_width >= old_width); + assert(new_height >= old_height); + + uint32_t *new_data = NULL; + + if (new_width == old_width) { + /* Width (and thus stride) is the same, so we can simply + * re-alloc the existing buffer */ + + new_data = realloc(old_data, new_width * new_height * sizeof(uint32_t)); + if (new_data == NULL) { + LOG_ERRNO("failed to reallocate sixel image buffer"); + return false; + } + + assert(new_height > old_height); + + } else { + /* Width (and thus stride) change - need to allocate a new buffer */ + assert(new_width > old_width); + new_data = malloc(new_width * new_height * sizeof(uint32_t)); + + /* Copy old rows, and initialize new columns to background color */ + for (int r = 0; r < old_height; r++) { + memcpy(&new_data[r * new_width], &old_data[r * old_width], old_width * sizeof(uint32_t)); + + for (int c = old_width; c < new_width; c++) + new_data[r * new_width + c] = term->colors.alpha / 256 << 24 | term->colors.bg; + } + free(old_data); + } + + /* Initialize new rows to background color */ + for (int r = old_height; r < new_height; r++) { + for (int c = 0; c < new_width; c++) + new_data[r * new_width + c] = term->colors.alpha / 256 << 24 | term->colors.bg; + } + + assert(new_data != NULL); + term->sixel.image.data = new_data; + term->sixel.image.width = new_width; + term->sixel.image.height = new_height; + + return true; +} + +static void +sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) +{ + //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); + + if (term->sixel.pos.col >= max_width(term) || + term->sixel.pos.row * 6 + 5 >= max_height(term)) + { + return; + } + + if (term->sixel.pos.col >= term->sixel.image.width || + term->sixel.pos.row * 6 + 5 >= term->sixel.image.height) + { + int width = max( + term->sixel.image.width, + max(term->sixel.max_col, term->sixel.pos.col + 1)); + + int height = max( + term->sixel.image.height, + (term->sixel.pos.row + 1) * 6); + + resize(term, width, height); + } + + for (int i = 0; i < 6; i++, sixel >>= 1) { + if (sixel & 1) { + size_t pixel_row = term->sixel.pos.row * 6 + i; + size_t stride = term->sixel.image.width; + size_t idx = pixel_row * stride + term->sixel.pos.col; + term->sixel.image.data[idx] = term->colors.alpha / 256 << 24 | color; + } + } + + assert(sixel == 0); + term->sixel.pos.col++; +} + +static void +decsixel(struct terminal *term, uint8_t c) +{ + switch (c) { + case '"': + term->sixel.state = SIXEL_DECGRA; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + case '!': + term->sixel.state = SIXEL_DECGRI; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + case '#': + term->sixel.state = SIXEL_DECGCI; + term->sixel.color_idx = 0; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + case '$': + if (term->sixel.pos.col > term->sixel.max_col) + term->sixel.max_col = term->sixel.pos.col; + term->sixel.pos.col = 0; + break; + + case '-': + if (term->sixel.pos.col > term->sixel.max_col) + term->sixel.max_col = term->sixel.pos.col; + term->sixel.pos.row++; + term->sixel.pos.col = 0; + break; + + case '?'...'~': + sixel_add(term, term->sixel.palette[term->sixel.color_idx], c - 63); + break; + + case ' ': + case '\n': + case '\r': + break; + + default: + LOG_WARN("invalid sixel charactwer: '%c' at idx=%zu", c, count); + break; + } +} + +static void +decgra(struct terminal *term, uint8_t c) +{ + switch (c) { + case '0'...'9': + term->sixel.param *= 10; + term->sixel.param += c - '0'; + break; + + case ';': + if (term->sixel.param_idx < ALEN(term->sixel.params)) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + term->sixel.param = 0; + break; + + default: { + if (term->sixel.param_idx < ALEN(term->sixel.params)) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + + int nparams = term->sixel.param_idx; + unsigned pan = nparams > 0 ? term->sixel.params[0] : 0; + unsigned pad = nparams > 1 ? term->sixel.params[1] : 0; + unsigned ph = nparams > 2 ? term->sixel.params[2] : 0; + unsigned pv = nparams > 3 ? term->sixel.params[3] : 0; + + pan = pan > 0 ? pan : 1; + pad = pad > 0 ? pad : 1; + + LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u", + pan, pad, pan / pad, ph, pv); + + if (ph >= term->sixel.image.height && pv >= term->sixel.image.width && + ph <= max_height(term) && pv <= max_width(term)) + { + resize(term, ph, pv); + } + + term->sixel.state = SIXEL_DECSIXEL; + sixel_put(term, c); + break; + } + } +} + +static void +decgri(struct terminal *term, uint8_t c) +{ + switch (c) { + case '0'...'9': + term->sixel.param *= 10; + term->sixel.param += c - '0'; + break; + + default: + //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); + for (unsigned i = 0; i < term->sixel.param; i++) + decsixel(term, c); + term->sixel.state = SIXEL_DECSIXEL; + break; + } +} + +static void +decgci(struct terminal *term, uint8_t c) +{ + switch (c) { + case '0'...'9': + term->sixel.param *= 10; + term->sixel.param += c - '0'; + break; + + case ';': + if (term->sixel.param_idx < ALEN(term->sixel.params)) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + term->sixel.param = 0; + break; + + default: { + if (term->sixel.param_idx < ALEN(term->sixel.params)) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + + int nparams = term->sixel.param_idx; + + if (nparams > 0) + term->sixel.color_idx = min(term->sixel.params[0], term->sixel.palette_size - 1); + + if (nparams > 4) { + unsigned format = term->sixel.params[1]; + unsigned c1 = term->sixel.params[2]; + unsigned c2 = term->sixel.params[3]; + unsigned c3 = term->sixel.params[4]; + + switch (format) { + case 1: { /* HLS */ + uint32_t rgb = hls_to_rgb(c1, c2, c3); + LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)", + term->sixel.color_idx, c1, c2, c3, rgb); + term->sixel.palette[term->sixel.color_idx] = rgb; + break; + } + + case 2: { /* RGB */ + uint8_t r = 255 * c1 / 100; + uint8_t g = 255 * c2 / 100; + uint8_t b = 255 * c3 / 100; + + LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu", + term->sixel.color_idx, r, g, b); + + term->sixel.palette[term->sixel.color_idx] = r << 16 | g << 8 | b; + break; + } + } + } + + term->sixel.state = SIXEL_DECSIXEL; + sixel_put(term, c); + break; + } + } +} + +void +sixel_put(struct terminal *term, uint8_t c) +{ + switch (term->sixel.state) { + case SIXEL_DECSIXEL: decsixel(term, c); break; + case SIXEL_DECGRA: decgra(term, c); break; + case SIXEL_DECGRI: decgri(term, c); break; + case SIXEL_DECGCI: decgci(term, c); break; + } + + count++; +} + +void +sixel_colors_report_current(struct terminal *term) +{ + char reply[24]; + snprintf(reply, sizeof(reply), "\033[?1;0;%uS", term->sixel.palette_size); + term_to_slave(term, reply, strlen(reply)); + LOG_DBG("query response for current color count: %u", term->sixel.palette_size); +} + +void +sixel_colors_reset(struct terminal *term) +{ + term->sixel.palette_size = SIXEL_MAX_COLORS; + LOG_DBG("sixel palette size reset to %u", SIXEL_MAX_COLORS); +} + +void +sixel_colors_set(struct terminal *term, unsigned count) +{ + unsigned new_palette_size = min(max(2, count), SIXEL_MAX_COLORS); + term->sixel.palette_size = new_palette_size; + LOG_DBG("sixel palette size set to %u", new_palette_size); +} + +void +sixel_colors_report_max(struct terminal *term) +{ + char reply[24]; + snprintf(reply, sizeof(reply), "\033[?1;0;%uS", SIXEL_MAX_COLORS); + term_to_slave(term, reply, strlen(reply)); + LOG_DBG("query response for max color count: %u", SIXEL_MAX_COLORS); +} + +void +sixel_geometry_report_current(struct terminal *term) +{ + char reply[64]; + snprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS", + max_width(term), max_height(term)); + term_to_slave(term, reply, strlen(reply)); + + LOG_DBG("query response for current sixel geometry: %ux%u", + max_width(term), max_height(term)); +} + +void +sixel_geometry_reset(struct terminal *term) +{ + term->sixel.max_width = 0; + term->sixel.max_height = 0; + LOG_DBG("sixel geometry reset to %ux%u", max_width(term), max_height(term)); +} + +void +sixel_geometry_set(struct terminal *term, unsigned width, unsigned height) +{ + term->sixel.max_width = width; + term->sixel.max_height = height; + LOG_DBG("sixel geometry set to %ux%u", + term->sixel.max_width, term->sixel.max_height); +} + +void +sixel_geometry_report_max(struct terminal *term) +{ + unsigned max_width = term->cols * term->cell_width; + unsigned max_height = term->rows * term->cell_height; + + char reply[64]; + snprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS", max_width, max_height); + term_to_slave(term, reply, strlen(reply)); + + LOG_DBG("query response for max sixel geometry: %ux%u", + max_width, max_height); +} diff --git a/sixel.h b/sixel.h new file mode 100644 index 00000000..c5c489af --- /dev/null +++ b/sixel.h @@ -0,0 +1,22 @@ +#pragma once + +#include "terminal.h" + +#define SIXEL_MAX_COLORS 1024u + +void sixel_init(struct terminal *term); +void sixel_put(struct terminal *term, uint8_t c); +void sixel_unhook(struct terminal *term); + +void sixel_destroy(struct sixel *sixel); +void sixel_purge_at_cursor(struct terminal *term); + +void sixel_colors_report_current(struct terminal *term); +void sixel_colors_reset(struct terminal *term); +void sixel_colors_set(struct terminal *term, unsigned count); +void sixel_colors_report_max(struct terminal *term); + +void sixel_geometry_report_current(struct terminal *term); +void sixel_geometry_reset(struct terminal *term); +void sixel_geometry_set(struct terminal *term, unsigned width, unsigned height); +void sixel_geometry_report_max(struct terminal *term); diff --git a/terminal.c b/terminal.c index ba19b552..5c7b94e5 100644 --- a/terminal.c +++ b/terminal.c @@ -19,12 +19,13 @@ #include "log.h" #include "async.h" +#include "config.h" #include "grid.h" #include "render.h" -#include "vt.h" #include "selection.h" -#include "config.h" +#include "sixel.h" #include "slave.h" +#include "vt.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -740,6 +741,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, .lower_fd = delay_lower_fd, .upper_fd = delay_upper_fd, }, + .sixel = { + .palette_size = SIXEL_MAX_COLORS, + }, + .sixel_images = tll_init(), .hold_at_exit = conf->hold_at_exit, .shutdown_cb = shutdown_cb, .shutdown_data = shutdown_data, @@ -993,6 +998,10 @@ term_destroy(struct terminal *term) tll_free(term->ptmx_buffer); tll_free(term->tab_stops); + tll_foreach(term->sixel_images, it) + sixel_destroy(&it->item); + tll_free(term->sixel_images); + free(term->foot_exe); free(term->cwd); @@ -1115,6 +1124,10 @@ term_reset(struct terminal *term, bool hard) term->meta.esc_prefix = true; term->meta.eight_bit = true; + tll_foreach(term->sixel_images, it) + sixel_destroy(&it->item); + tll_free(term->sixel_images); + if (!hard) return; @@ -1521,6 +1534,20 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows erase_line(term, grid_row_and_alloc(term->grid, r)); if (selection_on_row_in_view(term, r)) selection_cancel(term); + + + tll_foreach(term->sixel_images, it) { + /* Make it simple - remove the entire image if it starts + * getting scrolled out */ + + int img_top_row = it->item.pos.row & (term->grid->num_rows - 1); + int new_row = (term->grid->offset + r) & (term->grid->num_rows - 1); + + if (img_top_row == new_row) { + sixel_destroy(&it->item); + tll_remove(term->sixel_images, it); + } + } } term_damage_scroll(term, DAMAGE_SCROLL, region, rows); @@ -1572,6 +1599,21 @@ term_scroll_reverse_partial(struct terminal *term, erase_line(term, grid_row_and_alloc(term->grid, r)); if (selection_on_row_in_view(term, r)) selection_cancel(term); + + tll_foreach(term->sixel_images, it) { + /* Make it simple - remove the entire image if it starts + * getting scrolled out */ + + /* TODO: untested */ + + int img_bottom_row = (it->item.pos.row + it->item.rows) & (term->grid->num_rows - 1); + int new_row = (term->grid->offset + r) & (term->grid->num_rows - 1); + + if (img_bottom_row == new_row) { + sixel_destroy(&it->item); + tll_remove(term->sixel_images, it); + } + } } term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); @@ -2086,6 +2128,8 @@ term_print(struct terminal *term, wchar_t wc, int width) print_linewrap(term); print_insert(term, width); + sixel_purge_at_cursor(term); + /* *Must* get current cell *after* linewrap+insert */ struct row *row = term->grid->cur_row; struct cell *cell = &row->cells[term->cursor.point.col]; diff --git a/terminal.h b/terminal.h index d9727b32..5e491d29 100644 --- a/terminal.h +++ b/terminal.h @@ -131,6 +131,7 @@ struct vt { uint8_t *data; size_t size; size_t idx; + void (*put_handler)(struct terminal *term, uint8_t c); void (*unhook_handler)(struct terminal *term); } dcs; struct attributes attrs; @@ -174,6 +175,15 @@ struct ptmx_buffer { size_t idx; }; +struct sixel { + void *data; + pixman_image_t *pix; + int width; + int height; + int rows; + struct coord pos; +}; + struct terminal { struct fdm *fdm; const struct config *conf; @@ -344,6 +354,37 @@ struct terminal { int upper_fd; } delayed_render_timer; + struct { + enum { + SIXEL_DECSIXEL, /* DECSIXEL body part ", $, -, ? ... ~ */ + SIXEL_DECGRA, /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ + SIXEL_DECGRI, /* DECGRI Graphics Repeat Introducer ! Pn Ch */ + SIXEL_DECGCI, /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ + } state; + + struct coord pos; /* Current sixel coordinate */ + int color_idx; /* Current palette index */ + int max_col; /* Largest column index we've seen (aka the image width) */ + uint32_t *palette; /* Color palette */ + + struct { + uint32_t *data; /* Raw image data, in ARGB */ + int width; /* Image width, in pixels */ + int height; /* Image height, in pixels */ + } image; + + unsigned params[5]; /* Collected parmaeters, for RASTER, COLOR_SPEC */ + unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */ + unsigned param_idx; /* Parameters seen */ + + /* Application configurable */ + unsigned palette_size; /* Number of colors in palette */ + unsigned max_width; /* Maximum image width, in pixels */ + unsigned max_height; /* Maximum image height, in pixels */ + } sixel; + + tll(struct sixel) sixel_images; + bool hold_at_exit; bool is_shutting_down; void (*shutdown_cb)(void *data, int exit_code);