diff --git a/Makefile.am b/Makefile.am index cbdb4e863..7c3004d45 100644 --- a/Makefile.am +++ b/Makefile.am @@ -246,9 +246,10 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif -# Enable sixel support. -if ENABLE_SIXEL -dist_tmux_SOURCES += image.c image-sixel.c +# Enable image support. +if ENABLE_IMAGES +dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-ascii.c \ + image-diacritics.h endif # Add bits for fuzzing if enabled. diff --git a/configure.ac b/configure.ac index 51700a2ad..4ca124e79 100644 --- a/configure.ac +++ b/configure.ac @@ -482,15 +482,35 @@ if test "x$enable_cgroups" = xyes; then fi fi -# Enable sixel support. +# Enable protocol-neutral image support. +AC_ARG_WITH( + image-support, + AS_HELP_STRING(--with-image-support, enable Kitty/SIXEL/ASCII images) +) + +# Keep the old option as an alias for one compatibility cycle. AC_ARG_ENABLE( sixel, - AS_HELP_STRING(--enable-sixel, enable sixel images) + AS_HELP_STRING(--enable-sixel, deprecated alias for --with-image-support) ) if test "x$enable_sixel" = xyes; then + with_image_support=yes +fi +if test "x$with_image_support" = xyes; then + PKG_CHECK_MODULES( + IMAGE, + [libpng >= 1.6 zlib], + [ + AM_CPPFLAGS="$IMAGE_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + LIBS="$IMAGE_LIBS $LIBS" + ], + AC_MSG_ERROR("image support requires libpng 1.6 and zlib") + ) + AC_DEFINE(ENABLE_IMAGES) AC_DEFINE(ENABLE_SIXEL) fi -AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes]) +AM_CONDITIONAL(ENABLE_IMAGES, [test "x$with_image_support" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) diff --git a/format.c b/format.c index 5376243b1..fe47eba8e 100644 --- a/format.c +++ b/format.c @@ -2951,6 +2951,17 @@ format_cb_sixel_support(__unused struct format_tree *ft) #endif } +/* Callback for image_support. */ +static void * +format_cb_image_support(__unused struct format_tree *ft) +{ +#ifdef ENABLE_IMAGES + return (xstrdup("1")); +#else + return (xstrdup("0")); +#endif +} + /* Callback for active_window_index. */ static void * format_cb_active_window_index(struct format_tree *ft) @@ -3614,6 +3625,9 @@ static const struct format_table_entry format_table[] = { { "host_short", FORMAT_TABLE_STRING, format_cb_host_short }, + { "image_support", FORMAT_TABLE_STRING, + format_cb_image_support + }, { "insert_flag", FORMAT_TABLE_STRING, format_cb_insert_flag }, diff --git a/grid.c b/grid.c index d753e03f1..a782c2989 100644 --- a/grid.c +++ b/grid.c @@ -41,6 +41,9 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; /* @@ -49,16 +52,38 @@ const struct grid_cell grid_default_cell = { */ static const struct grid_cell grid_padding_cell = { { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; /* Cleared grid cell data. */ static const struct grid_cell grid_cleared_cell = { { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; static const struct grid_cell_entry grid_cleared_entry = { { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; +#ifdef ENABLE_IMAGES +/* Return the image referenced by a stored cell, or zero. */ +static u_int +grid_entry_image(struct grid_line *gl, struct grid_cell_entry *gce) +{ + struct grid_extd_entry *gee; + + if (~gce->flags & GRID_FLAG_EXTENDED || gce->offset >= gl->extdsize) + return (0); + gee = &gl->extddata[gce->offset]; + if (~gee->flags & GRID_FLAG_IMAGE) + return (0); + return (gee->image_id); +} +#endif + #ifdef __APPLE__ void grid_check_is_clear(struct grid *gd) @@ -134,6 +159,10 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->flags & GRID_FLAG_TAB) return (1); +#ifdef ENABLE_IMAGES + if (gc->flags & GRID_FLAG_IMAGE) + return (1); +#endif return (0); } @@ -181,6 +210,11 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, gee->bg = gc->bg; gee->us = gc->us; gee->link = gc->link; +#ifdef ENABLE_IMAGES + gee->image_id = gc->image_id; + gee->image_x = gc->image_x; + gee->image_y = gc->image_y; +#endif return (gee); } @@ -249,6 +283,12 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved) struct grid_extd_entry *gee; u_int old_offset = gce->offset; int had_extd = (gce->flags & GRID_FLAG_EXTENDED); +#ifdef ENABLE_IMAGES + u_int image_id = grid_entry_image(gl, gce); + + if (!moved && image_id != 0) + image_free(image_id); +#endif memcpy(gce, &grid_cleared_entry, sizeof *gce); if (!moved && had_extd && old_offset < gl->extdsize) { @@ -295,6 +335,13 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) return (0); if (gc1->link != gc2->link) return (0); +#ifdef ENABLE_IMAGES + if ((gc1->flags & GRID_FLAG_IMAGE) && + (gc1->image_id != gc2->image_id || + gc1->image_x != gc2->image_x || + gc1->image_y != gc2->image_y)) + return (0); +#endif return (1); } @@ -327,6 +374,10 @@ static void grid_free_line(struct grid *gd, u_int py) { struct grid_line *gl = &gd->linedata[py]; +#ifdef ENABLE_IMAGES + struct grid_cell_entry *gce; + u_int image_id, px; +#endif #ifdef __APPLE__ assert(gl->cellused <= gl->cellsize); @@ -334,6 +385,14 @@ grid_free_line(struct grid *gd, u_int py) assert(gl->cellsize == 0 || gl->celldata != NULL); #endif +#ifdef ENABLE_IMAGES + for (px = 0; px < gl->cellsize; px++) { + gce = &gl->celldata[px]; + image_id = grid_entry_image(gl, gce); + if (image_id != 0) + image_free(image_id); + } +#endif free(gl->celldata); free(gl->extddata); memset(gl, 0, sizeof *gl); @@ -605,6 +664,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->bg = gee->bg; gc->us = gee->us; gc->link = gee->link; +#ifdef ENABLE_IMAGES + gc->image_id = gee->image_id; + gc->image_x = gee->image_x; + gc->image_y = gee->image_y; +#endif if (gc->flags & GRID_FLAG_TAB) grid_set_tab(gc, gee->data); @@ -625,6 +689,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->us = 8; utf8_set(&gc->data, gce->data.data); gc->link = 0; +#ifdef ENABLE_IMAGES + gc->image_id = 0; + gc->image_x = 0; + gc->image_y = 0; +#endif } /* Get cell for reading. */ @@ -644,6 +713,9 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) { struct grid_line *gl; struct grid_cell_entry *gce; +#ifdef ENABLE_IMAGES + u_int old_id, new_id = 0; +#endif if (grid_check_y(gd, __func__, py) != 0) return; @@ -655,6 +727,17 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) gl->cellused = px + 1; gce = &gl->celldata[px]; +#ifdef ENABLE_IMAGES + old_id = grid_entry_image(gl, gce); + if (gc->flags & GRID_FLAG_IMAGE) + new_id = gc->image_id; + if (old_id != new_id) { + if (old_id != 0) + image_free(old_id); + if (new_id != 0) + image_ref(new_id); + } +#endif if (grid_need_extended_cell(gce, gc)) grid_extended_cell(gl, gce, gc); else @@ -689,6 +772,20 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, for (i = 0; i < slen; i++) { gce = &gl->celldata[px + i]; +#ifdef ENABLE_IMAGES + { + u_int old_id = grid_entry_image(gl, gce); + u_int new_id = (gc->flags & GRID_FLAG_IMAGE) ? + gc->image_id : 0; + + if (old_id != new_id) { + if (old_id != 0) + image_free(old_id); + if (new_id != 0) + image_ref(new_id); + } + } +#endif if (grid_need_extended_cell(gce, gc)) { gee = grid_extended_cell(gl, gce, gc); gee->data = utf8_build_one(s[i]); @@ -808,6 +905,9 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, { struct grid_line *gl; u_int xx; +#ifdef ENABLE_IMAGES + u_int image_id; +#endif if (nx == 0 || px == dx) return; @@ -818,6 +918,16 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, grid_expand_line(gd, py, px + nx, 8); grid_expand_line(gd, py, dx + nx, 8); +#ifdef ENABLE_IMAGES + /* References in the source range are transferred, not duplicated. */ + for (xx = dx; xx < dx + nx; xx++) { + if (xx >= px && xx < px + nx) + continue; + image_id = grid_entry_image(gl, &gl->celldata[xx]); + if (image_id != 0) + image_free(image_id); + } +#endif memmove(&gl->celldata[dx], &gl->celldata[px], nx * sizeof *gl->celldata); if (dx + nx > gl->cellused) @@ -1190,6 +1300,13 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, grid_get_cell(gd, xx, py, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; +#ifdef ENABLE_IMAGES + if (gc.flags & GRID_FLAG_IMAGE) { + utf8_set(&gc.data, ' '); + gc.flags &= ~GRID_FLAG_IMAGE; + gc.image_id = gc.image_x = gc.image_y = 0; + } +#endif if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, @@ -1257,6 +1374,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, { struct grid_line *dstl, *srcl; u_int yy; +#ifdef ENABLE_IMAGES + u_int px, image_id; +#endif if (dy + ny > dst->hsize + dst->sy) ny = dst->hsize + dst->sy - dy; @@ -1285,6 +1405,13 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, } else dstl->extddata = NULL; +#ifdef ENABLE_IMAGES + for (px = 0; px < dstl->cellsize; px++) { + image_id = grid_entry_image(dstl, &dstl->celldata[px]); + if (image_id != 0) + image_ref(image_id); + } +#endif sy++; dy++; } @@ -1420,8 +1547,7 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy, /* Remove the lines that were completely consumed. */ for (i = yy + 1; i < yy + 1 + lines; i++) { - free(gd->linedata[i].celldata); - free(gd->linedata[i].extddata); + grid_free_line(gd, i); grid_reflow_dead(&gd->linedata[i]); } @@ -1477,6 +1603,15 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy, } width += gc.data.width; grid_set_cell(target, xx, line, &gc); +#ifdef ENABLE_IMAGES + /* + * The tail of the original line is discarded below. Transfer + * its image reference to the new cell rather than duplicating + * it. + */ + if (gc.flags & GRID_FLAG_IMAGE) + image_free(gc.image_id); +#endif xx++; } if (flags & GRID_LINE_WRAPPED) diff --git a/image-sixel.c b/image-sixel.c index a004d78f8..7107de20f 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -397,6 +397,10 @@ sixel_log(struct sixel_image *si) void sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) { + if (si->xpixel == 0) + si->xpixel = 8; + if (si->ypixel == 0) + si->ypixel = 16; if ((si->x % si->xpixel) == 0) *x = (si->x / si->xpixel); else @@ -407,6 +411,92 @@ sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) *y = 1 + (si->y / si->ypixel); } +#ifdef ENABLE_IMAGES +static double +sixel_hue(double p, double q, double t) +{ + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t < 1.0 / 6) + return (p + (q - p) * 6 * t); + if (t < 1.0 / 2) + return (q); + if (t < 2.0 / 3) + return (p + (q - p) * (2.0 / 3 - t) * 6); + return (p); +} + +static void +sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b) +{ + u_int type = colour >> 25; + double h, l, s, p, q; + + if (type == 2) { + *r = ((colour >> 16) & 0x1ff) * 255 / 100; + *g = ((colour >> 8) & 0xff) * 255 / 100; + *b = (colour & 0xff) * 255 / 100; + return; + } + if (type != 1) { + *r = *g = *b = 0; + return; + } + + h = ((colour >> 16) & 0x1ff) / 360.0; + l = ((colour >> 8) & 0xff) / 100.0; + s = (colour & 0xff) / 100.0; + if (s == 0) { + *r = *g = *b = l * 255; + return; + } + q = l < 0.5 ? l * (1 + s) : l + s - l * s; + p = 2 * l - q; + *r = sixel_hue(p, q, h + 1.0 / 3) * 255; + *g = sixel_hue(p, q, h) * 255; + *b = sixel_hue(p, q, h - 1.0 / 3) * 255; +} + +/* Convert decoded SIXEL data into the protocol-neutral immutable image. */ +struct image * +sixel_to_image(struct sixel_image *si) +{ + u_char *pixels, *pixel, r, g, b; + u_int x, y, c, sx, sy; + struct image *im; + + if ((uint64_t)si->x * si->y * 4 > SIZE_MAX) + return (NULL); + pixels = xcalloc(si->x * si->y, 4); + for (y = 0; y < si->y; y++) { + for (x = 0; x < si->x; x++) { + c = sixel_get_pixel(si, x, y); + pixel = pixels + ((size_t)y * si->x + x) * 4; + if (c == 0) { + pixel[3] = si->p2 == 1 ? 0 : 255; + continue; + } + c--; + if (c < si->ncolours) + sixel_colour_to_rgb(si->colours[c], &r, &g, &b); + else + r = g = b = 0; + pixel[0] = r; + pixel[1] = g; + pixel[2] = b; + pixel[3] = 255; + } + } + sixel_size_in_cells(si, &sx, &sy); + im = image_create(si->x, si->y, sx, sy, pixels); + if (im == NULL) + free(pixels); + return (im); +} +#endif + struct sixel_image * sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, u_int oy, u_int sx, u_int sy, int colours) @@ -649,6 +739,115 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) return (buf); } +static struct sixel_image * +sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells, + u_int xpixel, u_int ypixel) +{ + struct sixel_image *si; + const u_char *pixel; + u_int x, y, sx, sy, sourcex, sourcey, sourcey0; + u_int sourcey1; + u_int r, g, b, colour, i; + + sx = cells * xpixel; + sy = ypixel; + if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT || + sy > SIXEL_HEIGHT_LIMIT) + return (NULL); + + si = xcalloc(1, sizeof *si); + si->xpixel = xpixel; + si->ypixel = ypixel; + si->p2 = 1; + si->set_ra = 1; + si->ra_x = sx; + si->ra_y = sy; + si->ncolours = si->used_colours = 216; + si->colours = xcalloc(si->ncolours, sizeof *si->colours); + for (i = 0; i < si->ncolours; i++) { + r = (i / 36) * 20; + g = ((i / 6) % 6) * 20; + b = (i % 6) * 20; + si->colours[i] = (2U << 25)|(r << 16)|(g << 8)|b; + } + + for (y = 0; y < sy; y++) { + sourcey0 = (uint64_t)oy * im->height / im->sy; + sourcey1 = (uint64_t)(oy + 1) * im->height / im->sy; + sourcey = sourcey0 + + (uint64_t)y * (sourcey1 - sourcey0) / sy; + if (sourcey >= im->height) + sourcey = im->height - 1; + for (x = 0; x < sx; x++) { + sourcex = ((uint64_t)ox * im->width / im->sx) + + (uint64_t)x * im->width * cells / + ((uint64_t)im->sx * sx); + if (sourcex >= im->width) + sourcex = im->width - 1; + pixel = im->pixels + sourcey * im->stride + sourcex * 4; + if (pixel[3] < 128) + continue; + r = (pixel[0] * 5 + 127) / 255; + g = (pixel[1] * 5 + 127) / 255; + b = (pixel[2] * 5 + 127) / 255; + colour = r * 36 + g * 6 + b + 1; + if (sixel_set_pixel(si, x, y, colour) != 0) { + sixel_free(si); + return (NULL); + } + } + } + return (si); +} + +void +sixel_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, + u_int nx, u_int atx, u_int aty) +{ + struct grid_cell gc, next; + struct image *im; + struct sixel_image *si; + char *data; + size_t size; + u_int i, run; + + for (i = 0; i < nx; i += run) { + grid_view_get_cell(s->grid, px + i, py, &gc); + if (~gc.flags & GRID_FLAG_IMAGE) { + run = 1; + continue; + } + im = image_find(gc.image_id); + if (im == NULL) { + run = 1; + continue; + } + for (run = 1; i + run < nx; run++) { + grid_view_get_cell(s->grid, px + i + run, py, &next); + if (~next.flags & GRID_FLAG_IMAGE || + next.image_id != gc.image_id || + next.image_y != gc.image_y || + next.image_x != gc.image_x + run) + break; + } + si = sixel_from_image(im, gc.image_x, gc.image_y, run, + tty->xpixel, tty->ypixel); + if (si == NULL) + continue; + data = sixel_print(si, NULL, &size); + sixel_free(si); + if (data == NULL) + continue; + tty_region_off(tty); + tty_margin_off(tty); + tty_cursor(tty, atx + i, aty); + tty->flags |= TTY_NOBLOCK; + tty_putn(tty, data, size, 0); + tty_invalidate(tty); + free(data); + } +} + struct screen * sixel_to_screen(struct sixel_image *si) { diff --git a/image.c b/image.c index f0543dc4f..755ee3a96 100644 --- a/image.c +++ b/image.c @@ -1,7 +1,7 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2007 Nicholas Marriott + * Copyright (c) 2026 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,200 +23,201 @@ #include "tmux.h" -static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); -static u_int all_images_count; -#define MAX_IMAGE_COUNT 20 +static struct images images = RB_INITIALIZER(&images); +static u_int image_next_id; -static void printflike(3, 4) -image_log(struct image *im, const char* from, const char* fmt, ...) +static int +image_cmp(struct image *a, struct image *b) { - va_list ap; - char s[128]; + if (a->id < b->id) + return (-1); + if (a->id > b->id) + return (1); + return (0); +} +RB_GENERATE_STATIC(images, image, entry, image_cmp); - if (log_get_level() == 0) - return; +struct image * +image_find(u_int id) +{ + struct image find; - if (fmt == NULL) { - log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy, - im->px, im->py); - return; - } - - va_start(ap, fmt); - vsnprintf(s, sizeof s, fmt, ap); - va_end(ap); - - log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy, - im->px, im->py, s); + find.id = id; + return (RB_FIND(images, &images, &find)); } -static void -image_free(struct image *im) -{ - image_log(im, __func__, NULL); - - TAILQ_REMOVE(&all_images, im, all_entry); - all_images_count--; - - TAILQ_REMOVE(im->list, im, entry); - sixel_free(im->data); - free(im->fallback); - free(im); -} - -int -image_free_all(struct screen *s) -{ - struct image *im, *im1; - int redraw = !TAILQ_EMPTY(&s->images); - - if (redraw) - log_debug ("%s", __func__); - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) - image_free(im); - return (redraw); -} - -/* Create text placeholder for an image. */ -static void -image_fallback(char **ret, u_int sx, u_int sy) -{ - char *buf, *label; - u_int py, size, lsize; - - /* Allocate first line. */ - lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1; - if (sx < lsize - 3) - size = lsize - 1; - else - size = sx + 2; - - /* Remaining lines. Every placeholder line has \r\n at the end. */ - size += (sx + 2) * (sy - 1) + 1; - *ret = buf = xmalloc(size); - - /* Render first line. */ - if (sx < lsize - 3) { - memcpy(buf, label, lsize); - buf += lsize - 1; - } else { - memcpy(buf, label, lsize - 3); - buf += lsize - 3; - memset(buf, '+', sx - lsize + 3); - buf += sx - lsize + 3; - snprintf(buf, 3, "\r\n"); - buf += 2; - } - - /* Remaining lines. */ - for (py = 1; py < sy; py++) { - memset(buf, '+', sx); - buf += sx; - snprintf(buf, 3, "\r\n"); - buf += 2; - } - - free(label); -} - -struct image* -image_store(struct screen *s, struct sixel_image *si) +struct image * +image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) { struct image *im; + if (width == 0 || height == 0 || sx == 0 || sy == 0 || pixels == NULL) + return (NULL); + if ((uint64_t)width * height * 4 > SIZE_MAX) + return (NULL); + if ((uint64_t)sx * sy > SIZE_MAX) + return (NULL); + im = xcalloc(1, sizeof *im); - im->s = s; - im->data = si; + do { + if (++image_next_id == 0) + image_next_id++; + im->id = image_next_id; + } while (image_find(im->id) != NULL); - im->px = s->cx; - im->py = s->cy; - sixel_size_in_cells(si, &im->sx, &im->sy); - - image_fallback(&im->fallback, im->sx, im->sy); - - image_log(im, __func__, NULL); - im->list = &s->images; - TAILQ_INSERT_TAIL(im->list, im, entry); - - TAILQ_INSERT_TAIL(&all_images, im, all_entry); - if (++all_images_count == MAX_IMAGE_COUNT) - image_free(TAILQ_FIRST(&all_images)); + im->references = 1; + im->width = width; + im->height = height; + im->sx = sx; + im->sy = sy; + im->stride = (size_t)width * 4; + im->size = im->stride * height; + im->pixels = pixels; + image_make_ascii(im); + RB_INSERT(images, &images, im); + log_debug("%s: image %u is %ux%u pixels, %ux%u cells", __func__, + im->id, width, height, sx, sy); return (im); } -int -image_check_line(struct screen *s, u_int py, u_int ny) +void +image_ref(u_int id) { - struct image *im, *im1; - int redraw = 0, in; + struct image *im = image_find(id); - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - in = (py + ny > im->py && py < im->py + im->sy); - image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); - if (in) { - image_free(im); - redraw = 1; + if (im == NULL) + fatalx("reference to missing image %u", id); + if (im->references == UINT_MAX) + fatalx("too many references to image %u", id); + im->references++; +} + +void +image_free(u_int id) +{ + struct image *im = image_find(id); + + if (im == NULL) + fatalx("free of missing image %u", id); + if (--im->references != 0) + return; + + log_debug("%s: freeing image %u", __func__, id); + RB_REMOVE(images, &images, im); + free(im->pixels); + free(im->ascii); + free(im); +} + +void +image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->flags |= GRID_FLAG_IMAGE; + gc->image_id = im->id; + gc->image_x = x; + gc->image_y = y; +} + +void +image_clear(struct screen *s, u_int id) +{ + struct grid *gd = s->grid; + struct grid_cell gc; + u_int x, y; + + for (y = 0; y < gd->hsize + gd->sy; y++) { + for (x = 0; x < gd->sx; x++) { + grid_get_cell(gd, x, y, &gc); + if ((gc.flags & GRID_FLAG_IMAGE) && + (id == 0 || gc.image_id == id)) + grid_set_cell(gd, x, y, &grid_default_cell); } } - return (redraw); } int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { - struct image *im, *im1; - int redraw = 0, in; + struct grid_cell gc; + u_int x, y, ex, ey; - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - in = (py < im->py + im->sy && - py + ny > im->py && - px < im->px + im->sx && - px + nx > im->px); - image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); - if (in) { - image_free(im); - redraw = 1; + ex = px + nx; + if (ex > screen_size_x(s)) + ex = screen_size_x(s); + ey = py + ny; + if (ey > screen_size_y(s)) + ey = screen_size_y(s); + for (y = py; y < ey; y++) { + for (x = px; x < ex; x++) { + grid_view_get_cell(s->grid, x, y, &gc); + if (gc.flags & GRID_FLAG_IMAGE) + return (1); } } - return (redraw); + return (0); } int -image_scroll_up(struct screen *s, u_int lines) +image_check_line(struct screen *s, u_int py, u_int ny) { - struct image *im, *im1; - int redraw = 0; - u_int sx, sy; - struct sixel_image *new; - - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (im->py >= lines) { - image_log(im, __func__, "1, lines=%u", lines); - im->py -= lines; - redraw = 1; - continue; - } - if (im->py + im->sy <= lines) { - image_log(im, __func__, "2, lines=%u", lines); - image_free(im); - redraw = 1; - continue; - } - sx = im->sx; - sy = (im->py + im->sy) - lines; - image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy); - - new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); - sixel_free(im->data); - im->data = new; - - im->py = 0; - sixel_size_in_cells(im->data, &im->sx, &im->sy); - - free(im->fallback); - image_fallback(&im->fallback, im->sx, im->sy); - redraw = 1; - } - return (redraw); + return (image_check_area(s, 0, py, screen_size_x(s), ny)); +} + +int +image_free_all(struct screen *s) +{ + return (image_check_area(s, 0, 0, screen_size_x(s), + screen_size_y(s))); +} + +int +image_scroll_up(struct screen *s, __unused u_int lines) +{ + return (image_free_all(s)); +} + +/* + * Put image marker cells at the cursor. The pixel object is immutable; only + * the marker rectangle is clipped to the available pane cells. + */ +void +image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) +{ + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct grid_cell gc; + u_int cx = s->cx, cy = s->cy; + u_int x, y, sx, sy, lines; + + sx = im->sx; + if (sx > screen_size_x(s) - cx) + sx = screen_size_x(s) - cx; + sy = im->sy; + if (sy > screen_size_y(s) - 1) + sy = screen_size_y(s) - 1; + if (sx == 0 || sy == 0) + return; + + if (screen_size_y(s) - cy <= sy) { + lines = sy - (screen_size_y(s) - cy) + 1; + screen_write_scrollup(ctx, lines, bg); + if (lines > cy) + screen_write_cursormove(ctx, -1, 0, 0); + else + screen_write_cursormove(ctx, -1, cy - lines, 0); + cy = s->cy; + } + + for (y = 0; y < sy; y++) { + for (x = 0; x < sx; x++) { + image_set_cell(&gc, im, x, y); + gc.bg = bg; + grid_view_set_cell(gd, cx + x, cy + y, &gc); + } + } + if (ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; + screen_write_cursormove(ctx, 0, cy + sy, 0); } diff --git a/input.c b/input.c index 0aac9bfbb..e97cd5e39 100644 --- a/input.c +++ b/input.c @@ -145,6 +145,9 @@ struct input_ctx { */ struct evbuffer *since_ground; struct event ground_timer; +#ifdef ENABLE_IMAGES + void *kitty_state; +#endif }; /* Helper functions. */ @@ -917,6 +920,9 @@ input_free(struct input_ctx *ictx) event_del(&ictx->request_timer); free(ictx->input_buf); +#ifdef ENABLE_IMAGES + kitty_free_state(ictx->kitty_state); +#endif evbuffer_free(ictx->since_ground); event_del(&ictx->ground_timer); @@ -2795,11 +2801,54 @@ input_exit_apc(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; +#ifdef ENABLE_IMAGES + struct image *im; + u_int image_id = 0, quiet = 0; + char action = '\0'; + int status; +#endif if (ictx->flags & INPUT_DISCARD) return; log_debug("%s: \"%s\"", __func__, ictx->input_buf); +#ifdef ENABLE_IMAGES + if (wp != NULL && ictx->input_len > 1 && ictx->input_buf[0] == 'G') { + im = kitty_parse_image(&ictx->kitty_state, ictx->input_buf + 1, + ictx->input_len - 1, wp->window->xpixel, + wp->window->ypixel, &image_id, &quiet, &action, &status); + if (status == KITTY_PARSE_MORE) + return; + if (status != KITTY_PARSE_OK) { + if (quiet < 2 && action != '\0') { + if (status == KITTY_PARSE_MISSING) + input_reply(ictx, 0, + "\033_Gi=%u;ENOENT\033\\", + image_id); + else + input_reply(ictx, 0, + "\033_Gi=%u;EINVAL\033\\", + image_id); + } + return; + } + if (im != NULL) { + if (action == 'd') + image_clear(sctx->s, im->id); + else + image_write(sctx, im, ictx->cell.cell.bg); + image_free(im->id); + if (quiet == 0 && image_id != 0) + input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", + image_id); + } else if (action == 'd' && image_id == 0) { + image_clear(sctx->s, 0); + } else if ((action == 't' || action == 'q') && quiet == 0) + input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id); + return; + } +#endif + if (wp != NULL && options_get_number(wp->options, "allow-set-title") && screen_set_title(sctx->s, ictx->input_buf, 1)) { diff --git a/options-table.c b/options-table.c index 4ab041ac6..9b07566a0 100644 --- a/options-table.c +++ b/options-table.c @@ -541,6 +541,7 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "xterm*:clipboard:ccolour:cstyle:focus:title," + "xterm-kitty*:kitty," "screen*:title," "rxvt*:ignorefkeys", .separator = ",", diff --git a/screen-redraw.c b/screen-redraw.c index 45ed68509..5233daed8 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1122,6 +1122,13 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx, px = span->data.p.px + (x - span->x); py = span->data.p.py; tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); +#ifdef ENABLE_IMAGES + if (tty->term->flags & TERM_KITTY) + kitty_draw_line(tty, s, px, py, n, x, y); + else if ((tty->term->flags & TERM_SIXEL) && + tty->xpixel != 0 && tty->ypixel != 0) + sixel_draw_line(tty, s, px, py, n, x, y); +#endif } /* Get default border style for spans without a pane. */ @@ -1806,15 +1813,6 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) tty_reset(tty); tty_sync_end(tty); -#ifdef ENABLE_SIXEL - if (wp != NULL) - tty_draw_images(c, wp); - else { - TAILQ_FOREACH(loop, &scene->w->panes, entry) - tty_draw_images(c, loop); - } -#endif - log_debug("%s: finished @%u redraw", c->name, scene->w->id); } diff --git a/screen-write.c b/screen-write.c index a311296e7..04c1dc49c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -3043,57 +3043,14 @@ void screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; - struct sixel_image *new; + struct image *im; - if (screen_size_y(s) == 1) + im = sixel_to_image(si); + sixel_free(si); + if (im == NULL) return; - - sixel_size_in_cells(si, &x, &y); - if (x > screen_size_x(s) || y > screen_size_y(s) - 1) { - if (x > screen_size_x(s) - cx) - sx = screen_size_x(s) - cx; - else - sx = x; - if (y > screen_size_y(s) - 1) - sy = screen_size_y(s) - 1; - else - sy = y; - new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); - sixel_free(si); - if (new == NULL) - return; - sixel_size_in_cells(new, &x, &y); - si = new; - } - - sy = screen_size_y(s) - cy; - if (sy <= y) { - lines = y - sy + 1; - if (image_scroll_up(s, lines) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; - for (i = 0; i < lines; i++) { - grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1, - bg); - screen_write_collect_scroll(ctx, bg); - } - ctx->scrolled += lines; - if (lines > cy) - screen_write_cursormove(ctx, -1, 0, 0); - else - screen_write_cursormove(ctx, -1, cy - lines, 0); - } - screen_write_collect_flush(ctx, 0, __func__); - - screen_write_initctx(ctx, &ttyctx, 0, 0); - ttyctx.image = image_store(s, si); - - tty_write(tty_cmd_sixelimage, &ttyctx); - - screen_write_cursormove(ctx, 0, cy + y, 0); + image_write(ctx, im, bg); + image_free(im->id); } #endif diff --git a/screen.c b/screen.c index 6b539b382..6f986495b 100644 --- a/screen.c +++ b/screen.c @@ -91,11 +91,6 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->tabs = NULL; s->sel = NULL; -#ifdef ENABLE_SIXEL - TAILQ_INIT(&s->images); - TAILQ_INIT(&s->saved_images); -#endif - s->write_list = NULL; s->hyperlinks = NULL; @@ -152,10 +147,6 @@ screen_reset_hyperlinks(struct screen *s) void screen_free(struct screen *s) { -#ifdef ENABLE_SIXEL - struct image *im; -#endif - free(s->sel); free(s->tabs); free(s->path); @@ -172,17 +163,6 @@ screen_free(struct screen *s) hyperlinks_free(s->hyperlinks); screen_free_titles(s); -#ifdef ENABLE_SIXEL - /* - * Images saved when entering the alternate screen stay linked in the - * global list; move them back so they are freed and unlinked here, or - * a later eviction would write through this freed screen. - */ - TAILQ_CONCAT(&s->images, &s->saved_images, entry); - TAILQ_FOREACH(im, &s->images, entry) - im->list = &s->images; - image_free_all(s); -#endif } /* Reset tabs to default, eight spaces apart. */ @@ -375,10 +355,6 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, if (sy != screen_size_y(s)) screen_resize_y(s, sy, eat_empty, &cy); -#ifdef ENABLE_SIXEL - image_free_all(s); -#endif - if (reflow) screen_reflow(s, sx, &cx, &cy, cursor); @@ -690,10 +666,6 @@ int screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx, sy; -#ifdef ENABLE_SIXEL - struct image *im; -#endif - if (SCREEN_IS_ALTERNATE(s)) return 0; sx = screen_size_x(s); @@ -707,12 +679,6 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) } memcpy(&s->saved_cell, gc, sizeof s->saved_cell); -#ifdef ENABLE_SIXEL - TAILQ_CONCAT(&s->saved_images, &s->images, entry); - TAILQ_FOREACH(im, &s->saved_images, entry) - im->list = &s->saved_images; -#endif - grid_view_clear(s->grid, 0, 0, sx, sy, 8); s->saved_flags = s->grid->flags; @@ -726,10 +692,6 @@ int screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx = screen_size_x(s), sy = screen_size_y(s); -#ifdef ENABLE_SIXEL - struct image *im; -#endif - /* * If the current size is different, temporarily resize to the old size * before copying back. @@ -772,13 +734,6 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) grid_destroy(s->saved_grid); s->saved_grid = NULL; -#ifdef ENABLE_SIXEL - image_free_all(s); - TAILQ_CONCAT(&s->images, &s->saved_images, entry); - TAILQ_FOREACH(im, &s->images, entry) - im->list = &s->images; -#endif - if (s->cx > screen_size_x(s) - 1) s->cx = screen_size_x(s) - 1; if (s->cy > screen_size_y(s) - 1) diff --git a/style.c b/style.c index 55d08fe75..ed79f99c5 100644 --- a/style.c +++ b/style.c @@ -30,7 +30,11 @@ /* Default style. */ static struct style style_default = { - { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }, + { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif + }, 0, 0, diff --git a/tmux.h b/tmux.h index c504b4757..c0577fa8d 100644 --- a/tmux.h +++ b/tmux.h @@ -75,6 +75,10 @@ struct screen_write_cline; struct screen_write_ctx; struct session; +#ifdef ENABLE_IMAGES +struct image; +struct tty_image_cache; +#endif #ifdef ENABLE_SIXEL struct sixel_image; #endif @@ -803,6 +807,7 @@ struct colour_palette { #define GRID_FLAG_NOPALETTE 0x20 #define GRID_FLAG_CLEARED 0x40 #define GRID_FLAG_TAB 0x80 +#define GRID_FLAG_IMAGE 0x100 /* Grid line flags. */ #define GRID_LINE_WRAPPED 0x1 @@ -847,22 +852,32 @@ struct colour_palette { struct grid_cell { struct utf8_data data; u_short attr; - u_char flags; + u_short flags; int fg; int bg; int us; u_int link; +#ifdef ENABLE_IMAGES + u_int image_id; + u_int image_x; + u_int image_y; +#endif }; /* Grid extended cell entry. */ struct grid_extd_entry { utf8_char data; u_short attr; - u_char flags; + u_short flags; int fg; int bg; int us; u_int link; +#ifdef ENABLE_IMAGES + u_int image_id; + u_int image_x; + u_int image_y; +#endif } __packed; /* Grid cell entry. */ @@ -876,7 +891,7 @@ struct grid_cell_entry { u_char data; } data; }; - u_char flags; + u_short flags; } __packed; /* Grid line. */ @@ -1000,24 +1015,23 @@ struct style { u_int link; }; -#ifdef ENABLE_SIXEL -/* Image. */ +#ifdef ENABLE_IMAGES +/* Immutable protocol-neutral image placement. */ struct image { - struct screen *s; - struct sixel_image *data; - char *fallback; - - u_int px; - u_int py; + u_int id; + u_int references; + u_int width; + u_int height; u_int sx; u_int sy; + size_t stride; + size_t size; + u_char *pixels; + char *ascii; - struct images *list; - TAILQ_ENTRY (image) entry; - - TAILQ_ENTRY (image) all_entry; + RB_ENTRY(image) entry; }; -TAILQ_HEAD(images, image); +RB_HEAD(images, image); #endif /* Cursor style. */ @@ -1076,11 +1090,6 @@ struct screen { bitstr_t *tabs; struct screen_sel *sel; -#ifdef ENABLE_SIXEL - struct images images; - struct images saved_images; -#endif - struct screen_write_cline *write_list; struct hyperlinks *hyperlinks; @@ -1706,6 +1715,7 @@ struct tty_term { #define TERM_RGBCOLOURS 0x10 #define TERM_VT100LIKE 0x20 #define TERM_SIXEL 0x40 +#define TERM_KITTY 0x80 int flags; LIST_ENTRY(tty_term) entry; @@ -1807,6 +1817,10 @@ struct tty { struct event key_timer; struct tty_key *key_tree; +#ifdef ENABLE_IMAGES + struct tty_image_cache *images; + u_int image_next_id; +#endif }; /* Terminal command context. */ @@ -2951,10 +2965,6 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); -#ifdef ENABLE_SIXEL -void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); -void tty_draw_images(struct client *, struct window_pane *); -#endif void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *); @@ -4173,14 +4183,37 @@ void spawn_editor_finish(struct window_pane *); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* image.c */ +struct image *image_create(u_int, u_int, u_int, u_int, u_char *); +struct image *image_find(u_int); +void image_ref(u_int); +void image_free(u_int); int image_free_all(struct screen *); -struct image *image_store(struct screen *, struct sixel_image *); int image_check_line(struct screen *, u_int, u_int); int image_check_area(struct screen *, u_int, u_int, u_int, u_int); int image_scroll_up(struct screen *, u_int); +void image_set_cell(struct grid_cell *, struct image *, u_int, + u_int); +void image_write(struct screen_write_ctx *, struct image *, u_int); +void image_make_ascii(struct image *); +const char *image_get_ascii(struct image *, u_int, u_int); +void image_clear(struct screen *, u_int); +#define KITTY_PARSE_ERROR -1 +#define KITTY_PARSE_OK 0 +#define KITTY_PARSE_MORE 1 +#define KITTY_PARSE_MISSING 2 +struct image *kitty_parse_image(void **, const u_char *, size_t, u_int, + u_int, u_int *, u_int *, char *, int *); +void kitty_free_state(void *); +void kitty_draw_line(struct tty *, struct screen *, u_int, u_int, + u_int, u_int, u_int); +void kitty_images_free(struct tty *, int); +void sixel_draw_line(struct tty *, struct screen *, u_int, u_int, + u_int, u_int, u_int); +#endif +#ifdef ENABLE_SIXEL /* image-sixel.c */ #define SIXEL_COLOUR_REGISTERS 1024 struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int); @@ -4192,6 +4225,7 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int, char *sixel_print(struct sixel_image *, struct sixel_image *, size_t *); struct screen *sixel_to_screen(struct sixel_image *); +struct image *sixel_to_image(struct sixel_image *); #endif /* server-acl.c */ diff --git a/tty-draw.c b/tty-draw.c index d47be66bc..534a7247e 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -121,6 +121,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct grid *gd = s->grid; const struct grid_cell *gcp; struct grid_cell gc, ngc, last; +#ifdef ENABLE_IMAGES + struct grid_cell image_gc; + struct image *im; + const char *ascii; +#endif struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; @@ -251,14 +256,36 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); +#ifdef ENABLE_IMAGES + /* Text terminals render image blocks as ASCII. */ + if (gc.flags & GRID_FLAG_IMAGE) { + memcpy(&image_gc, &gc, sizeof image_gc); + im = image_find(gc.image_id); + if ((tty->term->flags & TERM_KITTY) || + ((tty->term->flags & TERM_SIXEL) && + tty->xpixel != 0 && + tty->ypixel != 0)) + ascii = " "; + else + ascii = image_get_ascii(im, + gc.image_x, gc.image_y); + utf8_set(&image_gc.data, *ascii); + image_gc.flags &= ~GRID_FLAG_IMAGE; + gcp = &image_gc; + } else + gcp = &gc; +#else + gcp = &gc; +#endif + /* Work out empty cells. */ - empty = tty_draw_line_get_empty(&gc, &last, + empty = tty_draw_line_get_empty(gcp, &last, nx - i); if (empty != 0) - gcp = &gc; + ; else { /* Update for codeset if needed. */ - gcp = tty_check_codeset(tty, &gc); + gcp = tty_check_codeset(tty, gcp); /* And for selection. */ if (gcp->flags & GRID_FLAG_SELECTED) { diff --git a/tty-features.c b/tty-features.c index 656100ba9..1c6a7eb3e 100644 --- a/tty-features.c +++ b/tty-features.c @@ -357,6 +357,17 @@ static const struct tty_feature tty_feature_sixel = { TERM_SIXEL }; +/* Terminal has Kitty graphics protocol capability. */ +static const char *const tty_feature_kitty_capabilities[] = { + "Kty", + NULL +}; +static const struct tty_feature tty_feature_kitty = { + "kitty", + tty_feature_kitty_capabilities, + TERM_KITTY +}; + /* Terminal supports the OSC 9;4 progress bar. */ static const char *const tty_feature_progressbar_capabilities[] = { "Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\", @@ -379,6 +390,7 @@ static const struct tty_feature *const tty_features[] = { &tty_feature_extkeys, &tty_feature_focus, &tty_feature_ignorefkeys, + &tty_feature_kitty, &tty_feature_margins, &tty_feature_mouse, &tty_feature_osc7, @@ -594,7 +606,20 @@ tty_default_features(int *feat, const char *name, u_int version) "osc7," "sync," "usstyle," - "progressbar" + "progressbar," + "kitty" + }, + { .name = "kitty", + .features = TTY_FEATURES_BASE_MODERN_XTERM "," + "ccolour," + "cstyle," + "extkeys," + "focus," + "hyperlinks," + "osc7," + "sync," + "usstyle," + "kitty" }, { .name = "XTerm", /* diff --git a/tty.c b/tty.c index c5b443cee..cabd0551e 100644 --- a/tty.c +++ b/tty.c @@ -68,11 +68,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -#ifdef ENABLE_SIXEL -static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), - struct client *, struct tty_ctx *); -#endif - #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) #define tty_full_width(tty, ctx) \ @@ -114,6 +109,9 @@ tty_init(struct tty *tty, struct client *c) tty->ccolour = -1; tty->fg = tty->bg = -1; tty->mouse_last_pane = -1; +#ifdef ENABLE_IMAGES + tty->image_next_id = arc4random_uniform(0xffff00) + 1; +#endif if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); @@ -511,6 +509,9 @@ tty_close(struct tty *tty) tty_stop_tty(tty); if (tty->flags & TTY_OPENED) { +#ifdef ENABLE_IMAGES + kitty_images_free(tty, 1); +#endif evbuffer_free(tty->in); event_del(&tty->event_in); evbuffer_free(tty->out); @@ -527,6 +528,9 @@ void tty_free(struct tty *tty) { tty_close(tty); +#ifdef ENABLE_IMAGES + kitty_images_free(tty, 0); +#endif free(tty->r.ranges); } @@ -1522,60 +1526,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) return (c->overlay_check(c, c->overlay_data, px, py, nx)); } -#ifdef ENABLE_SIXEL -/* Update context for client. */ -static int -tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) -{ - struct window_pane *wp = ttyctx->arg; - - if (c->session->curw->window != wp->window) - return (0); - if (wp->layout_cell == NULL) - return (0); - - if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, - &ttyctx->wsy)) - ttyctx->flags |= TTY_CTX_WINDOW_BIGGER; - else - ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER; - - ttyctx->yoff = ttyctx->ryoff = wp->yoff; - if (status_at_line(c) == 0) - ttyctx->yoff += status_line_size(c); - - return (1); -} - -void -tty_draw_images(struct client *c, struct window_pane *wp) -{ - struct image *im; - struct tty_ctx ttyctx; - - TAILQ_FOREACH(im, &wp->screen->images, entry) { - memset(&ttyctx, 0, sizeof ttyctx); - - /* Set the client independent properties. */ - ttyctx.ocx = im->px; - ttyctx.ocy = im->py; - - ttyctx.orlower = wp->screen->rlower; - ttyctx.orupper = wp->screen->rupper; - - ttyctx.xoff = ttyctx.rxoff = wp->xoff; - ttyctx.sx = wp->sx; - ttyctx.sy = wp->sy; - - ttyctx.image = im; - ttyctx.arg = wp; - ttyctx.set_client_cb = tty_set_client_cb; - ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; - tty_write_one(tty_cmd_sixelimage, c, &ttyctx); - } -} -#endif - void tty_sync_start(struct tty *tty) { @@ -1649,19 +1599,6 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), } } -#ifdef ENABLE_SIXEL -/* Only write to the incoming tty instead of every client. */ -static void -tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *), - struct client *c, struct tty_ctx *ctx) -{ - if (ctx->set_client_cb == NULL) - return; - if ((ctx->set_client_cb(ctx, c)) == 1) - cmdfn(&c->tty, ctx); -} -#endif - void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { @@ -2155,58 +2092,6 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); } -#ifdef ENABLE_SIXEL -void -tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) -{ - struct image *im = ctx->image; - struct sixel_image *si = im->data; - struct sixel_image *new; - char *data; - size_t size; - u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy; - u_int i, j, x, y, rx, ry; - int fallback = 0; - - if ((~tty->term->flags & TERM_SIXEL) && - !tty_term_has(tty->term, TTYC_SXL)) - fallback = 1; - if (tty->xpixel == 0 || tty->ypixel == 0) - fallback = 1; - - sixel_size_in_cells(si, &sx, &sy); - log_debug("%s: image is %ux%u", __func__, sx, sy); - if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry)) - return; - log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry); - - if (fallback == 1) { - data = xstrdup(im->fallback); - size = strlen(data); - } else { - new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0); - if (new == NULL) - return; - - data = sixel_print(new, si, &size); - } - if (data != NULL) { - log_debug("%s: %zu bytes: %s", __func__, size, data); - tty_region_off(tty); - tty_margin_off(tty); - tty_cursor(tty, x, y); - - tty->flags |= TTY_NOBLOCK; - tty_add(tty, data, size); - tty_invalidate(tty); - free(data); - } - - if (fallback == 0) - sixel_free(new); -} -#endif - void tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) { diff --git a/window-copy.c b/window-copy.c index a6909a8d4..3963e8b27 100644 --- a/window-copy.c +++ b/window-copy.c @@ -6121,7 +6121,9 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, grid_get_cell(gd, i, sy, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; - if (gc.flags & GRID_FLAG_TAB) + if (gc.flags & GRID_FLAG_IMAGE) + utf8_set(&ud, ' '); + else if (gc.flags & GRID_FLAG_TAB) utf8_set(&ud, '\t'); else utf8_copy(&ud, &gc.data);