Make sixel images grid resident

Replace screen-coordinate image placements with immutable pixel images referenced by ordinary grid cells. Draw visible marker runs through the redraw scene using per-client sixel or text backends so images follow history, copy mode, clipping and overwrites.
This commit is contained in:
Michael Grant
2026-08-03 11:56:25 +01:00
parent 31dccb6bc9
commit c4bdd8eca1
17 changed files with 1082 additions and 507 deletions

View File

@@ -246,9 +246,9 @@ 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-ascii.c
endif
# Add bits for fuzzing if enabled.

View File

@@ -530,15 +530,25 @@ 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 SIXEL and 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
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)
@@ -1137,10 +1147,10 @@ if test "x$enable_asan" = xyes; then
else
AC_MSG_NOTICE([ASAN: off])
fi
if test "x$enable_sixel" = xyes; then
AC_MSG_NOTICE([SIXEL: on])
if test "x$with_image_support" = xyes; then
AC_MSG_NOTICE([images: on])
else
AC_MSG_NOTICE([SIXEL: off])
AC_MSG_NOTICE([images: off])
fi
if test "x$enable_debug" = xyes; then
AC_MSG_NOTICE([debug: on])

View File

@@ -2964,6 +2964,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)
@@ -3627,6 +3638,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
},

139
grid.c
View File

@@ -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);
}
@@ -268,6 +302,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) {
@@ -314,6 +354,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);
}
@@ -346,6 +393,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);
@@ -353,6 +404,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);
@@ -624,6 +683,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);
@@ -644,6 +708,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. */
@@ -663,6 +732,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;
@@ -674,6 +746,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
@@ -708,6 +791,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]);
@@ -827,6 +924,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;
@@ -837,6 +937,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)
@@ -1209,6 +1319,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,
@@ -1276,6 +1393,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;
@@ -1304,6 +1424,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++;
}
@@ -1439,8 +1566,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]);
}
@@ -1496,6 +1622,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)

40
image-ascii.c Normal file
View File

@@ -0,0 +1,40 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#include "tmux.h"
void
image_get_text_cell(__unused struct tty *tty, struct image *im, u_int x,
u_int y, const struct grid_cell *gc, struct grid_cell *out,
__unused const struct tty_style_ctx *style_ctx)
{
static const char ramp[] = " .:-=+*#%@";
const struct image_cell *cell;
u_int level = 0;
memcpy(out, gc, sizeof *out);
cell = image_get_cell(im, x, y);
if (cell != NULL)
level = cell->whole.brightness * (sizeof ramp - 2) / 255;
utf8_set(&out->data, ramp[level]);
out->flags &= ~GRID_FLAG_IMAGE;
}

View File

@@ -1,7 +1,7 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
@@ -397,16 +398,99 @@ sixel_log(struct sixel_image *si)
void
sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
{
if ((si->x % si->xpixel) == 0)
*x = (si->x / si->xpixel);
else
*x = 1 + (si->x / si->xpixel);
if ((si->y % si->ypixel) == 0)
*y = (si->y / si->ypixel);
else
*y = 1 + (si->y / si->ypixel);
if (si->xpixel == 0)
si->xpixel = 8;
if (si->ypixel == 0)
si->ypixel = 16;
image_size_in_cells(si->x, si->y, si->xpixel, si->ypixel, x, y);
}
#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 +733,95 @@ 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_x,
u_int cells_y, u_int xpixel, u_int ypixel)
{
struct sixel_image *si;
const u_char *pixel;
u_int x, y, sx, sy, sourcex, sourcey;
u_int sourcex0, sourcey0, sourcewidth, sourceheight;
u_int r, g, b, colour, i;
if ((uint64_t)cells_x * xpixel > UINT_MAX ||
(uint64_t)cells_y * ypixel > UINT_MAX)
return (NULL);
sx = cells_x * xpixel;
sy = cells_y * ypixel;
if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT ||
sy > SIXEL_HEIGHT_LIMIT)
return (NULL);
image_get_pixel_rectangle(im, ox, oy, cells_x, cells_y, &sourcex0,
&sourcey0, &sourcewidth, &sourceheight);
if (sourcewidth == 0 || sourceheight == 0)
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++) {
sourcey = sourcey0 + (uint64_t)y * sourceheight / sy;
if (sourcey >= im->height)
sourcey = im->height - 1;
for (x = 0; x < sx; x++) {
sourcex = sourcex0 + (uint64_t)x * sourcewidth / 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_rectangle(struct tty *tty, const struct image_rectangle *rectangle,
__unused const struct tty_style_ctx *style_ctx)
{
struct sixel_image *si;
char *data;
size_t size;
si = sixel_from_image(rectangle->image, rectangle->source_x,
rectangle->source_y, rectangle->width, rectangle->height,
tty->xpixel, tty->ypixel);
if (si == NULL)
return;
data = sixel_print(si, NULL, &size);
sixel_free(si);
if (data == NULL)
return;
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, rectangle->destination_x, rectangle->destination_y);
tty->flags |= TTY_NOBLOCK;
tty_putn(tty, data, size, 0);
tty_invalidate(tty);
free(data);
}
struct screen *
sixel_to_screen(struct sixel_image *si)
{

542
image.c
View File

@@ -1,7 +1,8 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,205 +19,434 @@
#include <sys/types.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#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, ...)
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
struct image_backend {
const char *name;
int flags;
void (*draw_rectangle)(struct tty *,
const struct image_rectangle *, const struct tty_style_ctx *);
};
static const struct image_backend image_backend_ascii = {
"ascii", IMAGE_BACKEND_SCROLLS, NULL
};
static const struct image_backend image_backend_sixel = {
"sixel", IMAGE_BACKEND_GRAPHICAL, sixel_draw_rectangle
};
static const struct image_backend *
image_tty_find_backend(struct tty *tty)
{
va_list ap;
char s[128];
if (log_get_level() == 0)
return;
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);
if (tty->term != NULL && tty->term->flags & TERM_SIXEL &&
tty->xpixel != 0 && tty->ypixel != 0)
return (&image_backend_sixel);
return (&image_backend_ascii);
}
static void
image_free(struct image *im)
void
image_tty_update(struct tty *tty)
{
image_log(im, __func__, NULL);
const struct image_backend *backend;
TAILQ_REMOVE(&all_images, im, all_entry);
all_images_count--;
backend = image_tty_find_backend(tty);
if (tty->image_backend == backend)
return;
TAILQ_REMOVE(im->list, im, entry);
sixel_free(im->data);
free(im->fallback);
free(im);
tty->image_backend = backend;
log_debug("%s: %s image backend is %s", __func__,
tty->client->name, backend->name);
}
int
image_free_all(struct screen *s)
image_tty_is_graphical(struct tty *tty)
{
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);
image_tty_update(tty);
return (!!(tty->image_backend->flags & IMAGE_BACKEND_GRAPHICAL));
}
/* Create text placeholder for an image. */
int
image_tty_scrolls(struct tty *tty)
{
image_tty_update(tty);
return (!!(tty->image_backend->flags & IMAGE_BACKEND_SCROLLS));
}
void
image_tty_geometry_changed(struct tty *tty)
{
image_tty_update(tty);
}
static int
image_cmp(struct image *a, struct image *b)
{
if (a->id < b->id)
return (-1);
if (a->id > b->id)
return (1);
return (0);
}
RB_GENERATE_STATIC(images, image, entry, image_cmp);
static void
image_fallback(char **ret, u_int sx, u_int sy)
image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
uint64_t sample_columns, uint64_t sample_rows, struct image_sample *sample)
{
char *buf, *label;
u_int py, size, lsize;
const u_char *pixel;
uint64_t red = 0, green = 0, blue = 0, alpha = 0;
uint64_t brightness = 0, count = 0;
u_int x, y, x0, x1, y0, y1;
/* 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;
x0 = sample_x * im->width / sample_columns;
x1 = (sample_x + 1) * im->width / sample_columns;
y0 = sample_y * im->height / sample_rows;
y1 = (sample_y + 1) * im->height / sample_rows;
if (x1 <= x0)
x1 = x0 + 1;
if (y1 <= y0)
y1 = y0 + 1;
if (x1 > im->width)
x1 = im->width;
if (y1 > im->height)
y1 = im->height;
/* 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;
for (y = y0; y < y1; y++) {
for (x = x0; x < x1; x++) {
pixel = im->pixels + y * im->stride + x * 4;
red += pixel[0] * pixel[3] / 255;
green += pixel[1] * pixel[3] / 255;
blue += pixel[2] * pixel[3] / 255;
alpha += pixel[3];
brightness += ((2126ULL * pixel[0] +
7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) *
pixel[3] / 255;
count++;
}
}
/* Remaining lines. */
for (py = 1; py < sy; py++) {
memset(buf, '+', sx);
buf += sx;
snprintf(buf, 3, "\r\n");
buf += 2;
}
free(label);
if (count == 0)
return;
sample->red = red / count;
sample->green = green / count;
sample->blue = blue / count;
sample->alpha = alpha / count;
sample->brightness = brightness / count;
}
struct image*
image_store(struct screen *s, struct sixel_image *si)
static void
image_make_cells(struct image *im)
{
struct image_cell *cell;
uint64_t columns, rows;
u_int x, y, sample_x, sample_y;
columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS;
rows = (uint64_t)im->sy * IMAGE_SAMPLE_ROWS;
im->cells = xcalloc((size_t)im->sx * im->sy, sizeof *im->cells);
for (y = 0; y < im->sy; y++) {
for (x = 0; x < im->sx; x++) {
cell = &im->cells[(size_t)y * im->sx + x];
image_sample(im, x, y, im->sx, im->sy, &cell->whole);
for (sample_y = 0; sample_y < IMAGE_SAMPLE_ROWS;
sample_y++) {
for (sample_x = 0;
sample_x < IMAGE_SAMPLE_COLUMNS; sample_x++) {
image_sample(im,
(uint64_t)x * IMAGE_SAMPLE_COLUMNS + sample_x,
(uint64_t)y * IMAGE_SAMPLE_ROWS + sample_y,
columns, rows,
&cell->samples[sample_y][sample_x]);
}
}
}
}
}
struct image *
image_find(u_int id)
{
struct image find;
find.id = id;
return (RB_FIND(images, &images, &find));
}
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 / sizeof *im->cells)
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;
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;
}
}
return (redraw);
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++;
}
int
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->cells);
free(im);
}
const struct image_cell *
image_get_cell(struct image *im, u_int x, u_int y)
{
if (im == NULL || x >= im->sx || y >= im->sy)
return (NULL);
if (im->cells == NULL)
image_make_cells(im);
return (&im->cells[(size_t)y * im->sx + x]);
}
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;
}
/* Convert a cell-aligned image rectangle into source pixel coordinates. */
void
image_get_pixel_rectangle(const struct image *im, u_int x, u_int y,
u_int width, u_int height, u_int *px, u_int *py, u_int *pwidth,
u_int *pheight)
{
u_int x1, y1;
*px = *py = *pwidth = *pheight = 0;
if (im == NULL || x >= im->sx || y >= im->sy || width == 0 ||
height == 0)
return;
if (width > im->sx - x)
width = im->sx - x;
if (height > im->sy - y)
height = im->sy - y;
*px = (uint64_t)x * im->width / im->sx;
*py = (uint64_t)y * im->height / im->sy;
x1 = (uint64_t)(x + width) * im->width / im->sx;
y1 = (uint64_t)(y + height) * im->height / im->sy;
if (*px >= im->width)
*px = im->width - 1;
if (*py >= im->height)
*py = im->height - 1;
if (x1 <= *px)
x1 = *px + 1;
if (y1 <= *py)
y1 = *py + 1;
if (x1 > im->width)
x1 = im->width;
if (y1 > im->height)
y1 = im->height;
*pwidth = x1 - *px;
*pheight = y1 - *py;
}
void
image_size_in_cells(u_int width, u_int height, u_int xpixel, u_int ypixel,
u_int *sx, u_int *sy)
{
if (xpixel == 0)
xpixel = 8;
if (ypixel == 0)
ypixel = 16;
*sx = ((uint64_t)width + xpixel - 1) / xpixel;
*sy = ((uint64_t)height + ypixel - 1) / ypixel;
}
static 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;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
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;
if (px >= sx || py >= sy || nx == 0 || ny == 0)
return (0);
if (nx > sx - px)
ex = sx;
else
ex = px + nx;
if (ny > sy - py)
ey = sy;
else
ey = py + ny;
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)
void
image_damage_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx,
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);
if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny))
ctx->wp->flags |= PANE_REDRAW;
}
void
image_damage_all(struct screen_write_ctx *ctx)
{
image_damage_area(ctx, 0, 0, screen_size_x(ctx->s),
screen_size_y(ctx->s));
}
void
image_damage_scroll(struct screen_write_ctx *ctx, __unused u_int lines)
{
image_damage_all(ctx);
}
/* Draw the graphical image marker runs in one visible scene span. */
void
image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
u_int nx, u_int atx, u_int aty, const struct tty_style_ctx *style_ctx)
{
const struct image_backend *backend;
struct image_rectangle rectangle;
struct grid_cell gc, next;
struct image *im;
u_int i, run;
image_tty_update(tty);
backend = tty->image_backend;
if (~backend->flags & IMAGE_BACKEND_GRAPHICAL)
return;
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;
}
rectangle.image = im;
memcpy(&rectangle.cell, &gc, sizeof rectangle.cell);
rectangle.source_x = gc.image_x;
rectangle.source_y = gc.image_y;
rectangle.width = run;
rectangle.height = 1;
rectangle.destination_x = atx + i;
rectangle.destination_y = aty;
backend->draw_rectangle(tty, &rectangle, style_ctx);
}
}
/*
* 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);
}
}
image_damage_area(ctx, cx, cy, sx, sy);
screen_write_cursormove(ctx, 0, cy + sy, 0);
}

68
regress/image-support.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/sh
# Grid-resident SIXEL image markers: history, copy, overwrite and fallback.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
$TMUX new-session -d -x 20 -y 8 "
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
i=0
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
sleep 10"
sleep 1
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
# Images scroll as grid cells, while capture output contains ordinary spaces.
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
$TMUX capture-pane -pS- >$TMP || exit 1
grep 'Pq' $TMP >/dev/null && exit 1
# Copy mode duplicates the backing grid, including image marker references.
$TMUX copy-mode || exit 1
$TMUX send-keys -X history-top || exit 1
$TMUX capture-pane -p >$TMP || exit 1
$TMUX send-keys -X cancel || exit 1
# Ordinary text overwrites marker cells through the normal grid write path.
$TMUX new-window -d "
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
printf '\033[HXY'
sleep 10"
sleep 1
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
# A client without an image feature gets the brightness-based text backend.
$TMUX kill-server 2>/dev/null
$TMUX2 new-session -d -x 10 -y 4 "
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
sleep 10" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX new-session -d -x 10 -y 4 || exit 1
$TMUX set -g status off || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
grep -q '[.:-=+*#%@]' $TMP || exit 1
# Selection redraws must leave text image cells visible.
$TMUX2 copy-mode || exit 1
$TMUX2 send-keys -X history-top || exit 1
$TMUX2 send-keys -X start-of-line || exit 1
$TMUX2 send-keys -X begin-selection || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
grep -q '[.:-=+*#%@]' $TMP || exit 1
exit 0

View File

@@ -1125,6 +1125,9 @@ 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
image_draw_line(tty, s, px, py, n, x, y, &style_ctx);
#endif
}
/* Get default border style for spans without a pane. */
@@ -1817,15 +1820,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);
}

View File

@@ -1308,9 +1308,8 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
memcpy(&gc, &grid_default_cell, sizeof gc);
utf8_set(&gc.data, 'E');
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -1355,9 +1354,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1396,9 +1394,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1437,9 +1434,8 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1472,9 +1468,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
@@ -1540,9 +1535,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
@@ -1613,9 +1607,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
@@ -1652,9 +1645,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
@@ -1679,9 +1671,8 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
if (s->cx > sx - 1)
@@ -1733,9 +1724,8 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
@@ -1785,9 +1775,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct grid_line *gl;
#ifdef ENABLE_SIXEL
int redraw = 0;
#endif
u_int rupper = s->rupper, rlower = s->rlower;
gl = grid_get_line(gd, gd->hsize + s->cy);
@@ -1808,13 +1795,12 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
if (rlower == screen_size_y(s) - 1)
redraw = image_scroll_up(s, 1);
image_damage_scroll(ctx, 1);
else
redraw = image_check_line(s, rupper, rlower - rupper);
if (redraw && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
image_damage_area(ctx, 0, rupper, screen_size_x(s),
rlower - rupper);
#endif
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
@@ -1840,9 +1826,8 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
ctx->bg = bg;
}
#ifdef ENABLE_SIXEL
if (image_scroll_up(s, lines) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_scroll(ctx, lines);
#endif
for (i = 0; i < lines; i++) {
@@ -1869,9 +1854,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
else if (lines > s->rlower - s->rupper + 1)
lines = s->rlower - s->rupper + 1;
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
for (i = 0; i < lines; i++)
@@ -1910,9 +1894,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -1991,9 +1974,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2064,9 +2046,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2531,9 +2512,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
}
#ifdef ENABLE_SIXEL
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, s->cx, s->cy, ci->used, 1);
#endif
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
@@ -3046,57 +3026,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

View File

@@ -93,11 +93,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;
@@ -131,11 +126,6 @@ screen_reinit(struct screen *s, int check)
screen_clear_selection(s);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
image_free_all(s);
#endif
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
screen_reset_hyperlinks(s);
}
@@ -154,10 +144,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);
@@ -174,17 +160,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. */
@@ -377,10 +352,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);
@@ -692,10 +663,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);
@@ -709,12 +676,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;
@@ -728,10 +689,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.
@@ -774,13 +731,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)

View File

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

127
tmux.h
View File

@@ -75,6 +75,11 @@ struct screen_write_cline;
struct screen_write_ctx;
struct session;
#ifdef ENABLE_IMAGES
struct image;
struct image_backend;
struct image_rectangle;
#endif
#ifdef ENABLE_SIXEL
struct sixel_image;
#endif
@@ -803,6 +808,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
@@ -858,22 +864,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. */
@@ -887,7 +903,7 @@ struct grid_cell_entry {
u_char data;
} data;
};
u_char flags;
u_short flags;
} __packed;
/* OSC 133 data for a grid line. */
@@ -1021,24 +1037,54 @@ struct style {
u_int link;
};
#ifdef ENABLE_SIXEL
/* Image. */
struct image {
struct screen *s;
struct sixel_image *data;
char *fallback;
#ifdef ENABLE_IMAGES
/* A protocol-neutral average of part of an image cell. RGB is premultiplied. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
u_int px;
u_int py;
/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
/* Immutable protocol-neutral image placement. */
struct image {
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;
struct image_cell *cells; /* lazily generated text samples */
struct images *list;
TAILQ_ENTRY (image) entry;
TAILQ_ENTRY (image) all_entry;
RB_ENTRY(image) entry;
};
TAILQ_HEAD(images, image);
RB_HEAD(images, image);
/* A cell-aligned part of an image to draw at a terminal position. */
struct image_rectangle {
struct image *image;
struct grid_cell cell;
u_int source_x;
u_int source_y;
u_int width;
u_int height;
u_int destination_x;
u_int destination_y;
};
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
#endif
/* Cursor style. */
@@ -1097,11 +1143,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;
@@ -1832,6 +1873,9 @@ struct tty {
struct event key_timer;
struct tty_key *key_tree;
#ifdef ENABLE_IMAGES
const struct image_backend *image_backend;
#endif
};
/* Terminal command context. */
@@ -2984,10 +3028,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 *);
@@ -4211,14 +4251,38 @@ 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 */
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);
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);
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_get_pixel_rectangle(const struct image *, u_int, u_int,
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
u_int *);
void image_damage_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_damage_all(struct screen_write_ctx *);
void image_damage_scroll(struct screen_write_ctx *, u_int);
int image_tty_is_graphical(struct tty *);
int image_tty_scrolls(struct tty *);
void image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, const struct tty_style_ctx *);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
void image_get_text_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
void sixel_draw_rectangle(struct tty *,
const struct image_rectangle *, const struct tty_style_ctx *);
#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);
@@ -4230,6 +4294,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 */

View File

@@ -29,6 +29,7 @@ enum tty_draw_line_state {
TTY_DRAW_LINE_NEW1,
TTY_DRAW_LINE_NEW2,
TTY_DRAW_LINE_EMPTY,
TTY_DRAW_LINE_IMAGE,
TTY_DRAW_LINE_SAME,
TTY_DRAW_LINE_DONE
};
@@ -38,6 +39,7 @@ static const char* tty_draw_line_states[] = {
"NEW1",
"NEW2",
"EMPTY",
"IMAGE",
"SAME",
"DONE"
};
@@ -123,10 +125,14 @@ 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;
#endif
struct grid_line *gl;
u_int i, j, last_i, cx, ex, width;
u_int cellsize, bg;
int flags, empty, wrapped = 0;
int flags, empty, skip, wrapped = 0;
char buf[1000];
size_t len;
enum tty_draw_line_state current_state, next_state;
@@ -231,6 +237,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
width = 0;
current_state = TTY_DRAW_LINE_FIRST;
for (;;) {
skip = 0;
/* Work out the next state. */
if (i == nx) {
/*
@@ -253,14 +261,43 @@ 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);
/* Work out empty cells. */
empty = tty_draw_line_get_empty(&gc, &last,
nx - i);
if (empty != 0)
#ifdef ENABLE_IMAGES
/* Text terminals render image blocks as ASCII. */
if (gc.flags & GRID_FLAG_IMAGE) {
im = image_find(gc.image_id);
if (image_tty_is_graphical(tty)) {
memcpy(&image_gc, &gc,
sizeof image_gc);
utf8_set(&image_gc.data, ' ');
image_gc.flags &=
~GRID_FLAG_IMAGE;
skip = 1;
} else {
image_get_text_cell(tty, im,
gc.image_x, gc.image_y, &gc,
&image_gc, style_ctx);
}
if (skip)
image_gc.flags &=
~GRID_FLAG_SELECTED;
gcp = &image_gc;
} else
gcp = &gc;
#else
gcp = &gc;
#endif
/* Work out empty cells. */
if (skip)
empty = 0;
else
empty = tty_draw_line_get_empty(gcp,
&last, nx - i);
if (empty != 0 || skip)
;
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) {
@@ -273,7 +310,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
}
/* Work out the next state. */
if (empty != 0)
if (skip)
next_state = TTY_DRAW_LINE_IMAGE;
else if (empty != 0)
next_state = TTY_DRAW_LINE_EMPTY;
else if (current_state == TTY_DRAW_LINE_FIRST)
next_state = TTY_DRAW_LINE_SAME;
@@ -320,7 +359,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
}
/* Append the cell if it is not empty and not padding. */
if (next_state != TTY_DRAW_LINE_EMPTY) {
if (next_state != TTY_DRAW_LINE_EMPTY &&
next_state != TTY_DRAW_LINE_IMAGE) {
memcpy(buf + len, gcp->data.data, gcp->data.size);
len += gcp->data.size;
width += gcp->data.width;

View File

@@ -224,9 +224,11 @@ static const struct tty_feature tty_feature_strikethrough = {
0
};
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
/* Terminal supports synchronized updates. */
static const char *const tty_feature_sync_capabilities[] = {
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
TTY_FEATURE_SYNC,
NULL
};
static const struct tty_feature tty_feature_sync = {
@@ -349,6 +351,7 @@ static const struct tty_feature tty_feature_ignorefkeys = {
/* Terminal has sixel capability. */
static const char *const tty_feature_sixel_capabilities[] = {
"Sxl",
TTY_FEATURE_SYNC,
NULL
};
static const struct tty_feature tty_feature_sixel = {
@@ -581,6 +584,7 @@ tty_default_features(int *feat, const char *name, u_int version)
"extkeys,"
"focus,"
"hyperlinks,"
"sixel,"
"usstyle"
},
{ .name = "ghostty",

159
tty.c
View File

@@ -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) \
@@ -163,10 +158,21 @@ tty_resize(struct tty *tty)
void
tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
{
#ifdef ENABLE_IMAGES
int geometry_changed;
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
#endif
tty->sx = sx;
tty->sy = sy;
tty->xpixel = xpixel;
tty->ypixel = ypixel;
#ifdef ENABLE_IMAGES
if (geometry_changed) {
image_tty_geometry_changed(tty);
server_redraw_client(tty->client);
}
#endif
}
static void
@@ -527,7 +533,6 @@ void
tty_free(struct tty *tty)
{
tty_close(tty);
free(tty->r.ranges);
}
@@ -538,6 +543,9 @@ tty_update_features(struct tty *tty)
if (tty_apply_features(tty->term, c->term_features))
tty_term_apply_overrides(tty->term);
#ifdef ENABLE_IMAGES
image_tty_update(tty);
#endif
if (tty_use_margin(tty))
tty_putcode(tty, TTYC_ENMG);
@@ -1522,60 +1530,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 +1603,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 +2096,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)
{
@@ -2234,6 +2123,10 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
const struct tty_style_ctx *style_ctx)
{
const struct grid_cell *gcp;
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
struct image *im;
#endif
/* Skip last character if terminal is stupid. */
if ((tty->term->flags & TERM_NOAM) &&
@@ -2249,6 +2142,22 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
if (!tty_check_overlay(tty, tty->cx, tty->cy))
return;
#ifdef ENABLE_IMAGES
/*
* Graphical image cells are drawn by the redraw scene. In particular,
* do not erase one with a selected space while copy mode is moving the
* selection; the unchanged image would then need to be drawn again.
*/
if (gc->flags & GRID_FLAG_IMAGE) {
if (image_tty_is_graphical(tty))
return;
im = image_find(gc->image_id);
image_get_text_cell(tty, im, gc->image_x, gc->image_y, gc,
&image_gc, style_ctx);
gc = &image_gc;
}
#endif
/* Check the output codeset and apply attributes. */
gcp = tty_check_codeset(tty, gc);
tty_attributes(tty, gcp, style_ctx);

View File

@@ -6151,7 +6151,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);