Merge master into 4902-image-support

This commit is contained in:
Nicholas Marriott
2026-10-13 21:18:26 +01:00
25 changed files with 2471 additions and 2117 deletions

View File

@@ -211,6 +211,7 @@ dist_tmux_SOURCES = \
style.c \
tmux-protocol.h \
tmux.c \
tmux-image.h \
tmux.h \
tty-acs.c \
tty-draw.c \
@@ -252,7 +253,12 @@ endif
# Enable image support.
if ENABLE_IMAGES
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-fallback.c
dist_tmux_SOURCES += \
image.c \
image-grid.c \
image-sixel.c \
image-kitty.c \
image-fallback.c
endif
# Add bits for fuzzing if enabled.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.427 2026/10/12 12:22:42 nicm Exp $ */
/* $OpenBSD: format.c,v 1.428 2026/10/13 10:21:58 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2354,6 +2354,17 @@ format_cb_pane_last_prompt_time(struct format_tree *ft)
return (NULL);
}
/* Callback for pane_command_line. */
static void *
format_cb_pane_command_line(struct format_tree *ft)
{
struct window_pane *wp = ft->wp;
if (wp != NULL && wp->cmd_line != NULL)
return (xstrdup(wp->cmd_line));
return (NULL);
}
/* Callback for pane_command_start_time. */
static void *
format_cb_pane_command_start_time(struct format_tree *ft)
@@ -3843,6 +3854,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_command_end_time", FORMAT_TABLE_TIME,
format_cb_pane_command_end_time
},
{ "pane_command_line", FORMAT_TABLE_STRING,
format_cb_pane_command_line
},
{ "pane_command_running", FORMAT_TABLE_STRING,
format_cb_pane_command_running
},

View File

@@ -74,6 +74,10 @@ grid_view_clear_history(struct grid *gd, u_int bg)
gl = grid_get_line(gd, grid_view_y(gd, yy));
if (gl->cellused != 0)
last = yy + 1;
#ifdef ENABLE_IMAGES
else if (image_grid_line_has_images(gl))
last = yy + 1;
#endif
}
if (last == 0) {
grid_view_clear(gd, 0, 0, gd->sx, gd->sy, bg);

13
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.160 2026/10/12 08:08:37 nicm Exp $ */
/* $OpenBSD: grid.c,v 1.161 2026/10/12 15:40:17 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -701,6 +701,9 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
return;
if (grid_check_y(gd, __func__, py + ny - 1) != 0)
return;
#ifdef ENABLE_IMAGES
image_grid_remove(gd, px, py, nx, ny);
#endif
for (yy = py; yy < py + ny; yy++) {
gl = &gd->linedata[yy];
@@ -1262,17 +1265,13 @@ 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 original_dy, original_sy;
u_int ody = dy, osy = sy;
#endif
if (dy + ny > dst->hsize + dst->sy)
ny = dst->hsize + dst->sy - dy;
if (sy + ny > src->hsize + src->sy)
ny = src->hsize + src->sy - sy;
#ifdef ENABLE_IMAGES
original_dy = dy;
original_sy = sy;
#endif
grid_free_lines(dst, dy, ny);
for (yy = 0; yy < ny; yy++) {
@@ -1303,7 +1302,7 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
dy++;
}
#ifdef ENABLE_IMAGES
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
image_grid_duplicate_lines(dst, ody, src, osy, ny);
#endif
}

View File

@@ -252,27 +252,31 @@ fallback_block_key(enum image_glyph_detail detail, u_int mask)
static u_char *
fallback_make_shades(struct image *im, u_int levels)
{
struct image_glyph_data *data = im->fallback_data;
struct image_glyph_data *data = image_get_fallback_data(im);
const struct image_cell *cell;
int *values, value, represented, error;
size_t cells, index;
u_int x, y, scan, level, minimum = 255, maximum = 0;
u_int sx, sy;
int reverse;
u_char *result;
if (data == NULL)
data = im->fallback_data = xcalloc(1, sizeof *data);
if (data == NULL) {
data = xcalloc(1, sizeof *data);
image_set_fallback_data(im, data);
}
image_get_size_in_cells(im, &sx, &sy);
result = (levels == 5 ? data->shade5 : data->shade8);
if (result != NULL)
return (result);
/* Collect cell brightness and the range used by this image. */
cells = (size_t)im->sx * im->sy;
cells = (size_t)sx * sy;
values = xcalloc(cells, sizeof *values);
result = xcalloc(cells, 1);
for (y = 0; y < im->sy; y++) {
for (x = 0; x < im->sx; x++) {
index = (size_t)y * im->sx + x;
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
index = (size_t)y * sx + x;
cell = image_get_cell(im, x, y);
value = cell->whole.brightness;
if ((u_int)value < minimum)
@@ -291,11 +295,11 @@ fallback_make_shades(struct image *im, u_int levels)
}
/* Dither each row, alternating direction to distribute the error. */
for (y = 0; y < im->sy; y++) {
for (y = 0; y < sy; y++) {
reverse = (y & 1);
for (scan = 0; scan < im->sx; scan++) {
x = (reverse ? im->sx - scan - 1 : scan);
index = (size_t)y * im->sx + x;
for (scan = 0; scan < sx; scan++) {
x = (reverse ? sx - scan - 1 : scan);
index = (size_t)y * sx + x;
value = values[index];
if (value < 0)
value = 0;
@@ -305,21 +309,21 @@ fallback_make_shades(struct image *im, u_int levels)
result[index] = level;
represented = level * 255 / (levels - 1);
error = value - represented;
if (!reverse && x + 1 < im->sx)
if (!reverse && x + 1 < sx)
values[index + 1] += error * 7 / 16;
if (reverse && x != 0)
values[index - 1] += error * 7 / 16;
if (y + 1 == im->sy)
if (y + 1 == sy)
continue;
if (!reverse && x != 0)
values[index + im->sx - 1] += error * 3 / 16;
if (reverse && x + 1 < im->sx)
values[index + im->sx + 1] += error * 3 / 16;
values[index + im->sx] += error * 5 / 16;
if (!reverse && x + 1 < im->sx)
values[index + im->sx + 1] += error / 16;
values[index + sx - 1] += error * 3 / 16;
if (reverse && x + 1 < sx)
values[index + sx + 1] += error * 3 / 16;
values[index + sx] += error * 5 / 16;
if (!reverse && x + 1 < sx)
values[index + sx + 1] += error / 16;
if (reverse && x != 0)
values[index + im->sx - 1] += error / 16;
values[index + sx - 1] += error / 16;
}
}
free(values);
@@ -447,7 +451,7 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
enum image_glyph_palette palette;
enum image_glyph_detail detail;
u_char *levels, key;
u_int level = 0, nlevels;
u_int level = 0, nlevels, sx, sy;
memcpy(out, gc, sizeof *out);
cell = image_get_cell(im, x, y);
@@ -469,7 +473,8 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
if (detail == IMAGE_GLYPH_SHADE5)
nlevels = 5;
levels = fallback_make_shades(im, nlevels);
level = levels[(size_t)y * im->sx + x];
image_get_size_in_cells(im, &sx, &sy);
level = levels[(size_t)y * sx + x];
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
bold_shades[level]);
if (key == 0)
@@ -492,7 +497,7 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
void
fallback_free(struct image *im)
{
struct image_glyph_data *data = im->fallback_data;
struct image_glyph_data *data = image_get_fallback_data(im);
if (data == NULL)
return;

1491
image-grid.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -595,7 +595,7 @@ kitty_upload(struct tty *tty, struct image *im)
} while (id == 0);
entry->server_id = image_get_id(im);
entry->kitty_id = id;
entry->order_id = im->order_id;
entry->order_id = image_get_order_id(im);
entry->xpixel = tty->xpixel;
entry->ypixel = tty->ypixel;
entry->next_placement = 0;
@@ -843,7 +843,7 @@ kitty_placements_free(struct kitty_source *source)
for (placement = source->placements; placement != NULL;
placement = next) {
next = placement->next;
image_free(placement->server_id);
image_free(image_find(placement->server_id));
free(placement);
}
source->placements = NULL;
@@ -854,7 +854,7 @@ static void
kitty_source_free(struct kitty_source *source)
{
kitty_placements_free(source);
image_free(source->server_id);
image_free(image_find(source->server_id));
free(source);
}
@@ -908,11 +908,11 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
old_id = source->server_id;
old = image_find(old_id);
if (old != NULL)
im->order_id = old->order_id;
image_set_order_id(im, image_get_order_id(old));
kitty_placements_free(source);
image_free(source->server_id);
image_free(image_find(source->server_id));
}
image_ref(image_get_id(im));
image_ref(im);
source->server_id = image_get_id(im);
return (old_id);
}
@@ -945,10 +945,10 @@ kitty_placement_set(struct kitty_context *kc, const struct kitty_state *ks,
old_id = placement->server_id;
placement->z = ks->z;
placement->virtual = ks->virtual;
image_ref(image_get_id(im));
image_ref(im);
placement->server_id = image_get_id(im);
if (old_id != 0)
image_free(old_id);
image_free(image_find(old_id));
return (old_id);
}
@@ -964,7 +964,7 @@ kitty_source_get(struct kitty_context *kc, u_int id)
return (NULL);
im = image_find(source->server_id);
if (im != NULL)
image_ref(image_get_id(im));
image_ref(im);
return (im);
}
@@ -981,7 +981,7 @@ kitty_prune_placements(struct kitty_source *source, struct grid *gd)
if (image_grid_has_image(gd, placement->server_id))
goto keep;
*pp = placement->next;
image_free(placement->server_id);
image_free(image_find(placement->server_id));
free(placement);
removed++;
continue;
@@ -1040,7 +1040,7 @@ kitty_delete_images(void *state, struct screen_write_ctx *ctx,
goto keep_placement;
}
*pp = placement->next;
image_free(placement->server_id);
image_free(image_find(placement->server_id));
free(placement);
removed++;
continue;
@@ -1272,20 +1272,20 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
view.scaled_height = floor(view.height * height_scale * units);
if (view.scaled_height < view.height)
view.scaled_height = view.height;
view.x_offset = floor(x_offset * units);
view.y_offset = floor(y_offset * units);
if (view.x_offset > view.canvas_width ||
view.scaled_width > view.canvas_width - view.x_offset ||
view.y_offset > view.canvas_height ||
view.scaled_height > view.canvas_height - view.y_offset) {
view.canvas_xoff = floor(x_offset * units);
view.canvas_yoff = floor(y_offset * units);
if (view.canvas_xoff > view.canvas_width ||
view.scaled_width > view.canvas_width - view.canvas_xoff ||
view.canvas_yoff > view.canvas_height ||
view.scaled_height > view.canvas_height - view.canvas_yoff) {
return (NULL);
}
/* Centre Unicode-placeholder images within their rectangle. */
if (ks->virtual && ks->columns != 0 && ks->rows != 0) {
view.x_offset += (view.canvas_width - view.x_offset -
view.canvas_xoff += (view.canvas_width - view.canvas_xoff -
view.scaled_width) / 2;
view.y_offset += (view.canvas_height - view.y_offset -
view.canvas_yoff += (view.canvas_height - view.canvas_yoff -
view.scaled_height) / 2;
}
/* Let the output terminal scale unpadded rectangular placements. */
@@ -1297,8 +1297,8 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
view.scaled_width = view.width;
view.canvas_height = view.height;
view.scaled_height = view.height;
view.x_offset = 0;
view.y_offset = 0;
view.canvas_xoff = 0;
view.canvas_yoff = 0;
}
im = image_create_view(source, &view);
if (im != NULL && ks->no_cursor)
@@ -1401,17 +1401,17 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
im = NULL;
} else if (ks->virtual) {
im = kitty_place_image(source, ks, xpixel, ypixel);
image_free(image_get_id(source));
image_free(source);
if (im != NULL) {
(void)kitty_placement_set(kc, ks, im);
image_free(image_get_id(im));
image_free(im);
im = NULL;
result->action = 'u';
result->status = KITTY_PARSE_OK;
}
} else {
im = kitty_place_image(source, ks, xpixel, ypixel);
image_free(image_get_id(source));
image_free(source);
if (im != NULL) {
result->replace_id =
kitty_placement_set(kc, ks, im);
@@ -1524,14 +1524,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
result->status = KITTY_PARSE_ERROR;
else {
(void)kitty_placement_set(kc, ks, im);
image_free(image_get_id(im));
image_free(im);
im = NULL;
result->action = 'u';
}
} else {
im = NULL;
}
image_free(image_get_id(source));
image_free(source);
}
kitty_state_free(ks);
return (im);

View File

@@ -1695,3 +1695,18 @@ sixel_redraw_start(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
tty_repeat_space(tty, sx);
}
}
/* Write a SIXEL image and release the parsed data. */
void
sixel_write(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg)
{
struct image *im;
im = sixel_to_image(si);
if (im == NULL) {
sixel_free(si);
return;
}
image_write_sixel(ctx, im, bg);
image_free(im);
}

1839
image.c

File diff suppressed because it is too large Load Diff

65
input.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.273 2026/10/08 07:50:05 nicm Exp $ */
/* $OpenBSD: input.c,v 1.276 2026/10/13 10:21:58 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2032,6 +2032,7 @@ static void
input_csi_dispatch_sm_private(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct grid_cell *gc = &ictx->cell.cell;
u_int i;
@@ -2093,13 +2094,13 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
break;
case 2031:
screen_write_mode_set(sctx, MODE_THEME_UPDATES);
if (ictx->wp != NULL) {
ictx->wp->last_theme = window_pane_get_theme(ictx->wp);
ictx->wp->flags &= ~PANE_THEMECHANGED;
if (wp != NULL) {
wp->last_theme = window_pane_get_theme(wp);
wp->flags &= ~PANE_THEMECHANGED;
}
break;
case 2026:
screen_write_start_sync(ictx->wp);
screen_write_start_sync(wp);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
@@ -2666,7 +2667,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
p2 = 0;
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
if (si != NULL)
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
sixel_write(sctx, si, ictx->cell.cell.bg);
}
#endif
@@ -2867,7 +2868,7 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
} else if (im != NULL) {
image_write_kitty(sctx, im, ictx->cell.cell.bg,
result.image_id, result.placement_id, result.z);
image_free(image_get_id(im));
image_free(im);
}
if (result.action != 'd')
input_reply_kitty(ictx, &result, "OK");
@@ -3336,6 +3337,52 @@ input_osc_133_exit_status(const char *p)
return (status);
}
/* Parse and clean the URL-encoded command line from OSC 133 C. */
static char *
input_osc_133_command_line(const char *p)
{
const char *end;
char line[16385], h[3] = { 0 };
size_t size = 0;
u_char ch;
if (p[1] != ';')
return (NULL);
p += 2;
for (;;) {
end = strchr(p, ';');
if (end == NULL)
end = p + strlen(p);
if (end - p >= 12 && strncmp(p, "cmdline_url=", 12) == 0)
break;
if (*end == '\0')
return (NULL);
p = end + 1;
}
p += 12;
while (p < end) {
if (size == sizeof line - 1)
return (NULL);
ch = *p++;
if (ch == '%') {
if (end - p < 2 ||
!isxdigit((u_char)p[0]) ||
!isxdigit((u_char)p[1]))
return (NULL);
h[0] = p[0];
h[1] = p[1];
ch = strtoul(h, NULL, 16);
if (ch == '\0')
return (NULL);
p += 2;
}
line[size++] = ch;
}
line[size] = '\0';
return (clean_name(line, 1));
}
/* Fire an OSC 133 command event. */
static void
input_fire_command_event(struct window_pane *wp, const char *name)
@@ -3355,6 +3402,8 @@ input_fire_command_event(struct window_pane *wp, const char *name)
event_payload_set_window(ep, "window", wp->window);
event_payload_set_pane(ep, "pane", wp);
if (wp->cmd_line != NULL)
event_payload_set_string(ep, "command_line", "%s", wp->cmd_line);
if (wp->cmd_status != -1)
event_payload_set_int(ep, "command_status", wp->cmd_status);
if (tstart != 0)
@@ -3426,6 +3475,8 @@ input_osc_133(struct input_ctx *ictx, const char *p)
gl->osc133_data.out_start_col = s->cx;
}
if (wp != NULL) {
free(wp->cmd_line);
wp->cmd_line = input_osc_133_command_line(p);
wp->cmd_start_time = time(NULL);
wp->cmd_end_time = 0;
wp->flags |= PANE_CMDRUNNING;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: mode-tree.c,v 1.103 2026/09/24 18:59:00 nicm Exp $ */
/* $OpenBSD: mode-tree.c,v 1.104 2026/10/12 18:23:25 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -701,8 +701,17 @@ mode_tree_build(struct mode_tree_data *mtd)
static void
mode_tree_remove_ref(struct mode_tree_data *mtd)
{
if (--mtd->references == 0)
free(mtd);
if (--mtd->references != 0)
return;
mode_tree_clear_prompt(mtd);
mode_tree_free_items(&mtd->children);
mode_tree_clear_lines(mtd);
screen_free(&mtd->screen);
free(mtd->search);
free(mtd->filter);
free(mtd);
}
void
@@ -713,14 +722,6 @@ mode_tree_free(struct mode_tree_data *mtd)
if (mtd->zoomed == 0)
server_unzoom_window(wp->window);
mode_tree_clear_prompt(mtd);
mode_tree_free_items(&mtd->children);
mode_tree_clear_lines(mtd);
screen_free(&mtd->screen);
free(mtd->search);
free(mtd->filter);
mtd->dead = 1;
mode_tree_remove_ref(mtd);
}
@@ -1095,7 +1096,7 @@ mode_tree_prompt_accept(struct cmdq_item *item, void *data)
struct client *c = cmdq_get_client(item);
key_code key = 'y';
if (mtd->prompt != NULL && c != NULL)
if (!mtd->dead && mtd->prompt != NULL && c != NULL)
mode_tree_key(mtd, c, &key, NULL, NULL, NULL);
mode_tree_remove_ref(mtd);
@@ -1122,7 +1123,6 @@ mode_tree_prompt_free_callback(void *data)
mtp->mtd->prompt_data = NULL;
if (mtp->freecb != NULL)
mtp->freecb(mtp->data);
mode_tree_remove_ref(mtp->mtd);
free(mtp);
}
@@ -1152,8 +1152,6 @@ mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c,
mtp->inputcb = inputcb;
mtp->freecb = freecb;
mtp->data = data;
mtd->references++;
mtd->prompt_top = options_get_number(oo, "status-position") == 0;
memset(&pd, 0, sizeof pd);
@@ -1524,6 +1522,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
if (mtd->prompt != NULL) {
redraw = 0;
prompt = mtd->prompt;
mtd->references++;
mtp = mtd->prompt_data;
if (mtp != NULL)
@@ -1548,6 +1547,11 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
}
} else
result = prompt_key(prompt, *key, &redraw);
if (mtd->dead) {
mode_tree_remove_ref(mtd);
*key = KEYC_NONE;
return (1);
}
if (mtd->prompt_data == mtp && mtp != NULL)
mtp->c = NULL;
@@ -1563,6 +1567,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW;
}
mode_tree_remove_ref(mtd);
if (result != PROMPT_KEY_NOT_HANDLED) {
*key = KEYC_NONE;
return (0);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
/* $OpenBSD: prompt.c,v 1.8 2026/10/13 07:56:00 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1555,14 +1555,25 @@ prompt_complete_add(char ***list, u_int *size, const char *s)
/* Build completion list. */
static char **
prompt_complete_commands(u_int *size, const char *s)
prompt_complete_commands(u_int *size, const char *s, int at_start)
{
char **list = NULL, *tmp;
const char *value, *cp;
const struct cmd_entry **cmdent;
size_t slen = strlen(s), valuelen;
struct options_entry *o;
struct options_array_item *a;
char **list = NULL, *tmp;
const char **layout, *value, *cp;
const struct cmd_entry **cmdent;
const struct options_table_entry *oe;
size_t slen = strlen(s), valuelen;
struct options_entry *o;
struct options_array_item *a;
const char *layouts[] = {
"even-horizontal",
"even-vertical",
"main-horizontal",
"main-horizontal-mirrored",
"main-vertical",
"main-vertical-mirrored",
"tiled",
NULL
};
*size = 0;
for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
@@ -1588,6 +1599,16 @@ prompt_complete_commands(u_int *size, const char *s)
a = options_array_next(a);
}
}
if (at_start)
return (list);
for (oe = options_table; oe->name != NULL; oe++) {
if (strncmp(oe->name, s, slen) == 0)
prompt_complete_add(&list, size, oe->name);
}
for (layout = layouts; *layout != NULL; layout++) {
if (strncmp(*layout, s, slen) == 0)
prompt_complete_add(&list, size, *layout);
}
return (list);
}
@@ -1671,11 +1692,10 @@ prompt_complete(struct prompt *pr, const char *word, u_int offset)
char **list = NULL, *out = NULL;
u_int size = 0, i;
if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 ||
*word == '\0')
if (pr->type != PROMPT_TYPE_COMMAND || *word == '\0')
return (NULL);
list = prompt_complete_commands(&size, word);
list = prompt_complete_commands(&size, word, offset == 0);
if (size == 0) {
free(list);
return (NULL);

View File

@@ -378,4 +378,23 @@ done
[ "$i" -lt 50 ] || fail "tagged windows were not killed"
exit_mode q
# --- killing the mode's own session -----------------------------------------
# Killing the attached session also destroys the pane hosting the tree. The
# confirmation callback must finish without redrawing the destroyed mode.
$TMUX set-option -g detach-on-destroy off || exit 1
$TMUX choose-tree -s -t aaa:0 -F 'G7' -O name || exit 1
wait_count ': G7' 2
$TMUX send-keys -t aaa:0 g x || fail "send-keys session kill failed"
wait_for 'Kill session aaa'
$TMUX send-keys -t aaa:0 y || fail "session kill confirmation failed"
i=0
while [ "$i" -lt 50 ]; do
sessions=$($TMUX list-sessions -F '#{session_name}' 2>/dev/null)
client_session=$($TMUX list-clients -F '#{client_session}' 2>/dev/null)
[ "$sessions" = 'zzz' ] && [ "$client_session" = 'zzz' ] && break
sleep 0.2
i=$((i + 1))
done
[ "$i" -lt 50 ] || fail "killing the tree session killed the server"
exit 0

View File

@@ -59,7 +59,13 @@ os.kill(client, signal.SIGTERM)
size(master, 23, 61, 0, 0)
os.kill(client, signal.SIGWINCH)
os.kill(client, signal.SIGCONT)
# macOS drains the controlling terminal when its session leader exits.
# Keep reading output so the client can finish exiting.
for i in range(200):
try:
os.read(master, 65536)
except OSError:
pass
if os.waitpid(client, os.WNOHANG)[0] != 0:
break
time.sleep(0.05)

View File

@@ -109,6 +109,7 @@ pane_bg
pane_bottom
pane_command_duration
pane_command_end_time
pane_command_line
pane_command_running
pane_command_start_time
pane_command_status

View File

@@ -1,88 +0,0 @@
#!/bin/sh
. ./input-common.inc
# Exercise expansion by VS16, a skin tone modifier and a regional indicator.
for kind in variation modifier flag; do
case "$kind" in
variation)
combine_seq='\342\234\224\357\270\217'
text='✔️'
;;
modifier)
combine_seq='\342\230\235\360\237\217\273'
text='☝🏻'
;;
flag)
combine_seq='\360\237\207\254\360\237\207\247'
text='🇬🇧'
;;
esac
start_pane "$kind-overwrite" 10 3 "A\347\225\214BC\r${combine_seq}X"
check_capture "$kind-overwrite" "${text}XBC"
check_cursor "$kind-overwrite" '3,0'
check_raw_matches "$kind-overwrite" \
"C 0,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 0,1 .*flags=PADDING\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]'
start_pane "$kind-edge" 5 3 "abcd${combine_seq}X"
check_capture "$kind-edge" "abcd
${text}X"
check_cursor "$kind-edge" '3,1'
check_raw_matches "$kind-edge" \
'L 0 \(0\) flags=.*WRAPPED' \
'C 0,4 data=\(1,1, \)' \
"C 1,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 1,1 .*flags=PADDING\[[0-9a-f]+\]' \
'C 1,2 data=\(1,1,X\) flags=NONE\[0\]'
start_pane "$kind-fit" 5 3 "abc${combine_seq}X"
check_capture "$kind-fit" "abc${text}
X"
check_cursor "$kind-fit" '1,1'
start_pane "$kind-scroll" 5 2 "11111\r\nabcd${combine_seq}X"
check_capture "$kind-scroll" "abcd
${text}X"
check_cursor "$kind-scroll" '3,1'
start_pane "$kind-region" 5 4 \
"top\033[2;1Hold\033[4;1Hend\033[2;3r\033[3;1Habcd${combine_seq}X"
check_capture "$kind-region" "top
abcd
${text}X
end"
check_cursor "$kind-region" '3,2'
start_pane "$kind-insert" 10 3 "ABCDE\r\033[4h${combine_seq}X"
check_capture "$kind-insert" "${text}XABCDE"
check_cursor "$kind-insert" '3,0'
start_pane "$kind-insert-edge" 5 3 \
"\033[2;1HABCDE\033[1;1Habcd\033[4h${combine_seq}X"
check_capture "$kind-insert-edge" "abcd
${text}XAB"
check_cursor "$kind-insert-edge" '3,1'
start_pane "$kind-nowrap" 5 3 "\033[?7labc${combine_seq}"
check_capture "$kind-nowrap" "abc${text}"
check_cursor "$kind-nowrap" '4,0'
check_raw_matches "$kind-nowrap" \
"C 0,3 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 0,4 .*flags=PADDING\[[0-9a-f]+\]'
done
# A two-column cluster cannot fit in a one-column pane. Keep its narrow base.
start_pane one-column 1 3 '\342\234\224\357\270\217'
check_capture one-column '✔'
check_cursor one-column '1,0'
check_raw_no_matches one-column 'data=\(2,'
# Without wrapping, even a wide cell filling the pane leaves the cursor inside.
start_pane two-columns-nowrap 2 3 '\033[?7l\342\234\224\357\270\217'
check_capture two-columns-nowrap '✔️'
check_cursor two-columns-nowrap '1,0'
exit $exit_status

142
regress/input-osc133.sh Normal file
View File

@@ -0,0 +1,142 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
check()
{
format=$1
expected=$2
actual=$($TMUX -u display-message -p -t "$pane" "$format") || exit 1
if [ "$actual" != "$expected" ]; then
printf 'format: %s\nexpected: %s\nactual: %s\n' \
"$format" "$expected" "$actual"
exit 1
fi
}
emit()
{
printf '\033]133;%s\033\\' "$1" |
$TMUX display-message -I -t "$pane" || exit 1
}
check_command()
{
emit "C;$1"
check '#{pane_command_line}' "$2"
check '#{@started}' "$2"
check '#{pane_command_running}:#{pane_command_status}' '1:'
emit 'D;7'
check '#{pane_command_line}' "$2"
check '#{@finished}' "$2"
check '#{pane_command_running}:#{pane_command_status}' '0:7'
}
$TMUX new-session -d -s test 'sleep 1000' || exit 1
pane=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1
other=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1
$TMUX set-hook -g pane-command-started \
'set -gF @started "#{hook_command_line}"' || exit 1
$TMUX set-hook -g pane-command-finished \
'set -gF @finished "#{hook_command_line}"' || exit 1
check '#{pane_command_line}:#{pane_command_running}' ':0'
check_command 'cmdline_url=sleep%201' 'sleep 1'
# Prompt markers leave the most recent command available.
emit A
emit B
check '#{pane_command_line}' 'sleep 1'
$TMUX display-message -p -t "$other" '#{pane_command_line}' |
grep -qx '' || exit 1
# Unknown fields are ignored and decoded semicolons belong to the value.
check_command 'other=1;cmdline_url=echo%20one%3B%20echo%20two;other=2' \
'echo one; echo two'
check_command 'cmdline_url=echo%20a+b%2Bc%25%3d%3D' 'echo a+b+c%=='
check_command 'cmdline_url=echo%20%C3%A9%E4%B8%AD' 'echo é中'
check_command 'cmdline_url=echo%20%2520' 'echo %20'
# Use the same cleanup as titles for control characters and format jobs.
check_command 'cmdline_url=echo%20%23%28date%29' 'echo _(date)'
check_command 'cmdline_url=echo%20%5C' 'echo \\'
# Multiline commands retain tabs and newlines as visible escapes, including
# in the event payloads used by hooks.
check_command 'cmdline_url=sleep%201%0Aecho%20done' 'sleep 1\necho done'
check_command 'cmdline_url=echo%09hello%0Aworld' 'echo\thello\nworld'
# The name validator also permits these bytes before clean_name escapes them.
name=$(printf 'tab\tnewline\nname')
$TMUX rename-window -t "$other" "$name" || exit 1
actual=$($TMUX -u display-message -p -t "$other" '#{window_name}') || exit 1
[ "$actual" = 'tab\tnewline\nname' ] || exit 1
$TMUX select-pane -t "$pane" -T "$name" || exit 1
check '#{pane_title}' 'tab\tnewline\nname'
# Every new start clears old metadata if it is missing or invalid.
emit C
check '#{pane_command_line}:#{@started}' ':'
for metadata in \
'cmdline_url=' \
'other=cmdline_url=stale' \
'xcmdline_url=stale' \
'cmdline_url=bad%' \
'cmdline_url=bad%2' \
'cmdline_url=bad%GG' \
'cmdline_url=bad%00tail' \
'cmdline_url=bad%0Dtail' \
'cmdline_url=bad%1Btail' \
'cmdline_url=bad%07tail' \
'cmdline_url=bad%08tail' \
'cmdline_url=bad%0Btail' \
'cmdline_url=bad%0Ctail' \
'cmdline_url=bad%7Ftail' \
'cmdline_url=bad%FF' \
'cmdline_url=bad%C3' \
'cmdline_url=bad%C0%AF'
do
check_command 'cmdline_url=previous' previous
check_command "$metadata" ''
done
# The limit applies to decoded bytes, not the size of the OSC string.
decoded=$(printf '%5000s' '' | tr ' ' x)
check_command "cmdline_url=$decoded" "$decoded"
encoded=$(awk 'BEGIN { for (i = 0; i < 16384; i++) printf "%%78" }')
decoded=$(printf '%16384s' '' | tr ' ' x)
check_command "cmdline_url=$encoded" "$decoded"
check_command "cmdline_url=${encoded}%78" ''
# Complete UTF-8 characters may reach the limit, but overlong lines are
# rejected in full rather than truncated, even through a character.
check_boundary()
{
prefix=$(printf "%${1}s" '' | tr ' ' x)
check_command "cmdline_url=$prefix$2" "$prefix$3"
check_command "cmdline_url=${prefix}x$2" ''
}
check_boundary 16382 '%C3%A9' 'é'
check_boundary 16381 '%E4%B8%AD' '中'
check_boundary 16380 '%F0%9F%98%80' '😀'
# Each hook gets a snapshot, even if multiple commands arrive in one read.
$TMUX set -g @log '' || exit 1
$TMUX set-hook -g pane-command-started \
'set -gF @log "#{@log}|start:#{hook_command_line}"' || exit 1
$TMUX set-hook -g pane-command-finished \
'set -gF @log "#{@log}|end:#{hook_command_line}:#{hook_command_status}"' || exit 1
printf '\033]133;C;cmdline_url=first\007\033]133;D;0\007\033]133;C;cmdline_url=second\007\033]133;D;1\007\033]133;C\007\033]133;D;2\007' |
$TMUX display-message -I -t "$pane" || exit 1
check '#{@log}' '|start:first|end:first:0|start:second|end:second:1|start:|end::2'
check '#{pane_command_line}' ''
exit 0

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.299 2026/10/05 08:41:23 nicm Exp $ */
/* $OpenBSD: screen-write.c,v 1.300 2026/10/12 18:47:52 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,10 +34,6 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
static void screen_write_collect_flush(struct screen_write_ctx *, int,
const char *);
#ifdef ENABLE_IMAGES
static void screen_write_image_damage(struct screen_write_ctx *, u_int,
u_int, u_int, u_int);
#endif
static int screen_write_overwrite(struct screen_write_ctx *,
struct grid_cell *, u_int);
static int screen_write_combine(struct screen_write_ctx *,
@@ -396,20 +392,6 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
ctx->bg = 8;
}
#ifdef ENABLE_IMAGES
/* Damage temporal images when application input writes text cells. */
static void
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
u_int sx, u_int sy)
{
image_redraw_area(ctx, x, y, sx, sy);
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(ctx->s->grid, x,
ctx->s->grid->hsize + y, sx, sy);
}
}
#endif
/* Initialize writing with a pane. */
void
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
@@ -1491,11 +1473,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
utf8_set(&gc.data, 'E');
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(s->grid, 0, s->grid->hsize,
screen_size_x(s), screen_size_y(s));
}
image_damage_screen(ctx, 0, 0, screen_size_x(s), screen_size_y(s));
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -1540,13 +1518,13 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg);
screen_write_collect_flush(ctx, 0, __func__);
@@ -1580,13 +1558,13 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg);
screen_write_collect_flush(ctx, 0, __func__);
@@ -1620,13 +1598,13 @@ 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_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, s->cx, s->cy, nx, 1);
#endif
grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg);
screen_write_collect_flush(ctx, 0, __func__);
@@ -1654,10 +1632,6 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy)
ny = sy - s->cy;
@@ -1667,6 +1641,10 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
grid_view_insert_lines(gd, s->cy, ny, bg);
screen_write_collect_flush(ctx, 0, __func__);
@@ -1691,6 +1669,11 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s),
s->rlower + 1 - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower)
grid_view_insert_lines(gd, s->cy, ny, bg);
else
@@ -1721,10 +1704,6 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy)
ny = sy - s->cy;
@@ -1734,6 +1713,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
grid_view_delete_lines(gd, s->cy, ny, bg);
screen_write_collect_flush(ctx, 0, __func__);
@@ -1760,6 +1743,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), ry);
#endif
if (s->cy < s->rupper || s->cy > s->rlower)
grid_view_delete_lines(gd, s->cy, ny, bg);
else
@@ -1790,11 +1777,17 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
u_int flags;
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) {
#ifdef ENABLE_IMAGES
if (!image_grid_line_has_images(gl))
return;
#else
return;
#endif
}
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
image_redraw_area(ctx, 0, s->cy, sx, 1);
#endif
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
@@ -1828,11 +1821,20 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
}
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
if (s->cx > sx - 1)
return;
if (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)) {
#ifdef ENABLE_IMAGES
if (!image_grid_area_has_images(s->grid, s->cx,
s->grid->hsize + s->cy, sx - s->cx, 1))
return;
#else
return;
#endif
}
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
image_redraw_area(ctx, s->cx, s->cy, sx - s->cx, 1);
#endif
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
@@ -1858,7 +1860,7 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
}
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
image_redraw_area(ctx, 0, s->cy, s->cx + 1, 1);
#endif
if (s->cx > sx - 1)
@@ -1982,11 +1984,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
}
#ifdef ENABLE_IMAGES
if (rlower == screen_size_y(s) - 1)
image_redraw_scroll(ctx);
else
image_redraw_area(ctx, 0, rupper, screen_size_x(s),
rlower - rupper);
image_redraw_scroll(ctx);
#endif
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
@@ -2080,13 +2078,15 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
if (s->cx < sx)
image_redraw_area(ctx, s->cx, s->cy, sx - s->cx, 1);
image_redraw_area(ctx, 0, s->cy + 1, sx, sy - (s->cy + 1));
#endif
/* Scroll into history if it is enabled and clearing entire screen. */
if (s->cx == 0 &&
s->cy == 0 &&
@@ -2160,13 +2160,14 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, 0, sx, s->cy);
image_redraw_area(ctx, 0, s->cy, s->cx + 1, 1);
#endif
if (s->cy > 0)
grid_view_clear(s->grid, 0, 0, sx, s->cy, bg);
if (s->cx > sx - 1)
@@ -2232,13 +2233,13 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
/* Scroll into history if it is enabled. */
if ((s->grid->flags & GRID_HISTORY) &&
ctx->wp != NULL &&
@@ -2662,14 +2663,19 @@ screen_write_collect_insert_clear(struct screen_write_ctx *ctx, u_int px,
* background so the columns it covered do not lose their colour.
*/
static void
screen_write_clear_cell(struct grid *gd, u_int px, u_int py)
screen_write_clear_cell(struct screen_write_ctx *ctx, u_int px, u_int py)
{
struct grid *gd = ctx->s->grid;
struct grid_cell gc;
int bg;
grid_view_get_cell(gd, px, py, &gc);
bg = gc.bg;
#ifdef ENABLE_IMAGES
image_damage_screen(ctx, px, py, 1, 1);
#endif
memcpy(&gc, &grid_default_cell, sizeof gc);
gc.bg = bg;
grid_view_set_cell(gd, px, py, &gc);
@@ -2728,7 +2734,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, xx, s->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
screen_write_clear_cell(s->grid, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (before) at %u (cx %u)",
__func__, xx, s->cx);
}
@@ -2737,7 +2743,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, 0, s->cy, &gc);
if (gc.data.width > 1 ||
(gc.flags & GRID_FLAG_PADDING)) {
screen_write_clear_cell(s->grid, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (before) at %u "
"(cx %u)", __func__, xx, s->cx);
}
@@ -2747,7 +2753,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
#ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1);
image_damage_screen(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,
@@ -2760,7 +2766,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, xx, s->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
screen_write_clear_cell(s->grid, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (after) at %u (cx %u)", __func__,
xx, s->cx);
}
@@ -2883,8 +2889,8 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
screen_write_initctx(ctx, &ttyctx, 0, 0);
#ifdef ENABLE_IMAGES
/* Input semantics decide which image layers this text damages. */
screen_write_image_damage(ctx, s->cx, s->cy, width, 1);
/* Mark images as damaged. */
image_damage_screen(ctx, s->cx, s->cy, width, 1);
#endif
/* Handle overwriting of UTF-8 characters. */
@@ -3118,10 +3124,12 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
} else
force_wide = 0;
/* Set the new cell. */
#ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, cx - n, cy, n, 1);
/* Mark images as damaged. */
image_damage_screen(ctx, cx - n, cy, n, 1);
#endif
/* Set the new cell. */
grid_view_set_cell(gd, cx - n, cy, &last);
if (force_wide)
grid_view_set_padding(gd, cx - 1, cy, last.bg);
@@ -3195,12 +3203,12 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc,
if (~tmp_gc.flags & GRID_FLAG_PADDING)
break;
log_debug("%s: padding at %u,%u", __func__, xx, s->cy);
screen_write_clear_cell(gd, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
}
/* Overwrite the character at the start of this padding. */
log_debug("%s: character at %u,%u", __func__, xx, s->cy);
screen_write_clear_cell(gd, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
done = 1;
}
@@ -3218,7 +3226,7 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc,
break;
log_debug("%s: overwrite at %u,%u", __func__, xx,
s->cy);
screen_write_clear_cell(gd, xx, s->cy);
screen_write_clear_cell(ctx, xx, s->cy);
done = 1;
}
}
@@ -3257,24 +3265,6 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
tty_write(tty_cmd_rawstring, &ttyctx);
}
#ifdef ENABLE_IMAGES
/* Write a SIXEL image. */
void
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
u_int bg)
{
struct image *im;
im = sixel_to_image(si);
if (im == NULL) {
sixel_free(si);
return;
}
image_write_sixel(ctx, im, bg);
image_free(image_get_id(im));
}
#endif
/* Turn alternate screen on. */
void
screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,

254
tmux-image.h Normal file
View File

@@ -0,0 +1,254 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2007 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
* 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.
*/
#ifndef TMUX_IMAGE_H
#define TMUX_IMAGE_H
#ifdef ENABLE_IMAGES
struct grid;
struct grid_line;
struct image;
struct image_line;
struct image_store;
struct screen;
struct screen_write_ctx;
struct sixel_image;
struct tty;
struct grid_cell;
struct image_rect;
struct image_backend;
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
#define IMAGE_BACKEND_CLIPPED 0x4
/* Private ACS keys used by the text image backend. */
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
#define TTY_ACS_IMAGE_BLOCK 0x83
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
/* Averaged RGBA colour and brightness used by the text image backend. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
/* Whole-cell and subcell samples generated lazily for one image cell. */
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
enum kitty_parse_status {
KITTY_PARSE_ERROR = -1,
KITTY_PARSE_OK,
KITTY_PARSE_MORE,
KITTY_PARSE_MISSING,
KITTY_PARSE_UNSUPPORTED
};
/* Reply fields and placement actions returned by one Kitty command. */
struct kitty_parse_result {
u_int image_id;
u_int image_number;
u_int replace_id;
u_int placement_id;
u_int quiet;
u_int x;
u_int y;
int32_t z;
char action;
char delete;
enum kitty_parse_status status;
};
/* Resolved source cell and application IDs of one Unicode placeholder. */
struct kitty_placeholder {
struct image *image;
u_int source_x;
u_int source_y;
u_int image_id;
u_int placement_id;
int32_t z;
};
/* View of all or part of an image. */
struct image_view {
/* Position and size in the image, in pixels. */
u_int x;
u_int y;
u_int width;
u_int height;
/* Size after scaling, in pixels. */
u_int scaled_width;
u_int scaled_height;
/* Destination canvas size and offset in pixels. */
u_int canvas_width;
u_int canvas_height;
u_int canvas_xoff;
u_int canvas_yoff;
/* Destination size in terminal cells. */
u_int sx;
u_int sy;
};
/* image.c */
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
u_char *);
struct image *image_create_view(struct image *, struct image_view *);
struct image *image_find(u_int);
u_int image_get_id(struct image *);
void image_ref(struct image *);
void image_free(struct image *);
void image_get_size(struct image *, u_int *, u_int *);
void image_get_canvas_size(struct image *, u_int *, u_int *);
void image_get_size_in_cells(struct image *, u_int *, u_int *);
const u_char *image_get_pixels(struct image *, size_t *, size_t *);
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
u_int *);
void image_set_no_cursor(struct image *);
int image_has_no_cursor(struct image *);
struct sixel_image *image_get_sixel(struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
u_int image_get_source_id(struct image *);
u_int image_get_order_id(struct image *);
void image_set_order_id(struct image *, u_int);
void *image_get_fallback_data(struct image *);
void image_set_fallback_data(struct image *, void *);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
void image_get_pixel_rect(struct image *, u_int, u_int, u_int,
u_int, u_int *, u_int *, u_int *, u_int *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
/* image-grid.c */
void image_grid_add(struct grid *, struct image *, u_int, u_int,
u_int, u_int, u_int, u_int);
void image_grid_remove(struct grid *, u_int, u_int, u_int, u_int);
void image_grid_remove_overwritten_spans(struct grid *, u_int, u_int,
u_int, u_int);
void image_grid_free_line(struct grid *, struct grid_line *);
void image_grid_free(struct grid *);
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
int image_grid_line_has_images(struct grid_line *);
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
u_int);
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_redraw_all(struct screen_write_ctx *);
void image_redraw_scroll(struct screen_write_ctx *);
void image_damage_screen(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
void image_grid_resize_width(struct grid *, u_int);
int image_grid_has_image(struct grid *, u_int);
int image_grid_get_placeholder(struct grid *, u_int, u_int,
struct kitty_placeholder *);
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
void image_write_sixel(struct screen_write_ctx *, struct image *, u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *, u_int,
u_int, u_int, int32_t);
void image_clear(struct screen_write_ctx *, u_int);
void image_clear_kitty(struct screen_write_ctx *,
const struct kitty_parse_result *);
int image_get_fallback_at(struct tty *, struct screen *, u_int, u_int,
const struct grid_cell *, struct grid_cell *);
struct image *image_rect_get_image(const struct image_rect *);
void image_rect_get_coords(const struct image_rect *, u_int *, u_int *,
u_int *, u_int *, u_int *, u_int *);
int64_t image_rect_get_z(const struct image_rect *);
void image_draw_line(struct tty *, struct screen *, u_int, u_int, u_int,
u_int, u_int, int);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void image_redraw_finish(struct tty *);
void image_draw_flush(struct tty *);
int image_backend_flags(struct tty *);
int image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
/* image-fallback.c */
void fallback_get_cell(struct tty *, struct image *, u_int, u_int,
const struct grid_cell *, struct grid_cell *);
void fallback_free(struct image *);
/* image-kitty.c */
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, struct kitty_parse_result *);
int kitty_placeholder_to_image(void *, struct grid *,
struct grid_cell *, u_int, u_int, struct kitty_placeholder *);
int kitty_cell_is_placeholder(const struct grid_cell *);
void kitty_delete_images(void *, struct screen_write_ctx *,
const struct kitty_parse_result *);
void kitty_free_state(void *);
void kitty_draw_rect(struct tty *, const struct image_rect *);
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void kitty_redraw_finish(struct tty *);
void kitty_free_output_state(struct tty *, int);
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
u_int);
void sixel_write(struct screen_write_ctx *, struct sixel_image *,
u_int);
void sixel_draw_rect(struct tty *, const struct image_rect *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
void sixel_flush_output(struct tty *);
struct image *sixel_to_image(struct sixel_image *);
void sixel_free(struct sixel_image *);
void sixel_log(struct sixel_image *);
#endif /* ENABLE_IMAGES */
#endif /* TMUX_IMAGE_H */

6
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1184 2026/10/12 12:35:03 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1185 2026/10/13 10:21:58 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: October 12 2026 $
.Dd $Mdocdate: October 13 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -7463,6 +7463,7 @@ The following variables are available, where appropriate:
.It Li "history_size" Ta "" Ta "Size of history in lines"
.It Li "hook" Ta "" Ta "Name of running hook, if any"
.It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any"
.It Li "hook_command_line" Ta "" Ta "Command line reported for an OSC 133 command hook, if any"
.It Li "hook_fire_count" Ta "" Ta "Number of times hook has fired"
.It Li "hook_fire_time" Ta "" Ta "Time hook last fired"
.It Li "hook_last" Ta "" Ta "Previous value for a monitor hook"
@@ -7531,6 +7532,7 @@ The following variables are available, where appropriate:
.It Li "pane_bottom" Ta "" Ta "Bottom of pane"
.It Li "pane_command_duration" Ta "" Ta "Current or most recent OSC 133 command duration in seconds"
.It Li "pane_command_end_time" Ta "" Ta "Time most recent OSC 133 command ended"
.It Li "pane_command_line" Ta "" Ta "Current or most recent OSC 133 command line, if reported"
.It Li "pane_command_running" Ta "" Ta "1 if an OSC 133 command is running"
.It Li "pane_command_start_time" Ta "" Ta "Time current or most recent OSC 133 command started"
.It Li "pane_command_status" Ta "" Ta "Exit status from most recent OSC 133 command"

245
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1457 2026/10/12 12:35:03 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1459 2026/10/13 10:21:58 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -19,11 +19,13 @@
#ifndef TMUX_H
#define TMUX_H
#include <sys/types.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <termios.h>
#include <wchar.h>
@@ -33,6 +35,7 @@
#endif
#include "compat.h"
#include "tmux-image.h"
#include "tmux-protocol.h"
#include "xmalloc.h"
@@ -77,15 +80,6 @@ struct screen_write_cline;
struct screen_write_ctx;
struct session;
#ifdef ENABLE_IMAGES
struct image;
struct image_backend;
struct image_rect;
struct image_line;
struct image_store;
struct sixel_image;
#endif
struct tty_ctx;
struct tty_code;
struct tty_key;
@@ -1048,123 +1042,6 @@ struct style {
u_int link;
};
#ifdef ENABLE_IMAGES
/* Private ACS keys used by the text image backend. */
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
#define TTY_ACS_IMAGE_BLOCK 0x83
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
/* Averaged RGBA colour and brightness used by the text image backend. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
/* Whole-cell and subcell samples generated lazily for one image cell. */
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
/* Immutable pixels and derived output data shared by image placements. */
struct image {
u_int id;
u_int references;
u_int flags;
/* Retained parent (zero for sources) and original source IDs. */
u_int parent_id;
u_int source_id;
u_int order_id; /* Original creation order. */
/* Pixel dimensions, including the logical transparent canvas. */
u_int width;
u_int height;
u_int canvas_width;
u_int canvas_height;
/* Grid dimensions and pixel buffer lengths in bytes. */
u_int sx;
u_int sy;
size_t stride;
size_t size;
u_char *pixels;
struct sixel_image *sixel;
struct image_cell *cells;
void *fallback_data;
RB_ENTRY(image) entry;
};
RB_HEAD(images, image);
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
/* Source crop and padded destination canvas for creating an image view. */
struct image_view {
u_int x; /* Crop origin in source pixels. */
u_int y;
u_int width; /* Crop size in source pixels. */
u_int height;
u_int scaled_width; /* Resampled crop size in pixels. */
u_int scaled_height;
u_int canvas_width; /* Padded canvas size in pixels. */
u_int canvas_height;
u_int sx; /* Placement size in cells. */
u_int sy;
u_int x_offset; /* Crop origin in canvas pixels. */
u_int y_offset;
};
enum kitty_parse_status {
KITTY_PARSE_ERROR = -1,
KITTY_PARSE_OK,
KITTY_PARSE_MORE,
KITTY_PARSE_MISSING,
KITTY_PARSE_UNSUPPORTED
};
/* Reply fields and placement actions returned by one Kitty command. */
struct kitty_parse_result {
u_int image_id;
u_int image_number;
u_int replace_id; /* Old server ID, or zero. */
u_int placement_id;
u_int quiet;
u_int x; /* Deletion coordinates. */
u_int y;
int32_t z;
char action;
char delete;
enum kitty_parse_status status;
};
/* Resolved source cell and application IDs of one Unicode placeholder. */
struct kitty_placeholder {
struct image *image;
u_int source_x; /* Origin in image cells. */
u_int source_y;
u_int image_id;
u_int placement_id;
int32_t z;
};
#endif
/* Cursor style. */
enum screen_cursor_style {
SCREEN_CURSOR_DEFAULT,
@@ -1493,6 +1370,7 @@ struct window_pane {
time_t cmd_start_time;
time_t cmd_end_time;
int cmd_status;
char *cmd_line;
int fd;
struct bufferevent *event;
@@ -3764,10 +3642,6 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
u_char *, u_int);
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
int);
#ifdef ENABLE_IMAGES
void screen_write_sixelimage(struct screen_write_ctx *,
struct sixel_image *, u_int);
#endif
void screen_write_alternateon(struct screen_write_ctx *,
struct grid_cell *, int);
void screen_write_alternateoff(struct screen_write_ctx *,
@@ -4357,115 +4231,6 @@ void spawn_editor_finish(struct window_pane *);
/* regsub.c */
char *regsub(const char *, const char *, const char *, int);
#ifdef ENABLE_IMAGES
/* image.c */
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
#define IMAGE_BACKEND_CLIPPED 0x4
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
u_char *);
struct image *image_create_view(struct image *, const struct image_view *);
struct image *image_find(u_int);
u_int image_get_id(const struct image *);
void image_get_size(const struct image *, u_int *, u_int *);
void image_get_canvas_size(const struct image *, u_int *, u_int *);
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
void image_set_no_cursor(struct image *);
struct sixel_image *image_get_sixel(const struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
void image_ref(u_int);
void image_free(u_int);
void image_write_sixel(struct screen_write_ctx *, struct image *,
u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, int32_t);
void image_get_pixel_rect(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 *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_redraw_all(struct screen_write_ctx *);
void image_redraw_scroll(struct screen_write_ctx *);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void image_redraw_finish(struct tty *);
void image_draw_flush(struct tty *);
int image_backend_flags(struct tty *);
int image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, int);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
int image_get_fallback_at(struct tty *, struct screen *, u_int,
u_int, const struct grid_cell *, struct grid_cell *);
struct image *image_rect_get_image(const struct image_rect *);
void image_rect_get_coords(const struct image_rect *,
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
int64_t image_rect_get_z(const struct image_rect *);
void image_clear(struct screen_write_ctx *, u_int);
void image_clear_kitty(struct screen_write_ctx *,
const struct kitty_parse_result *);
int image_grid_has_image(struct grid *, u_int);
void image_grid_remove_overwritten_spans(struct grid *, u_int,
u_int, u_int, u_int);
void image_grid_free_line(struct grid *, struct grid_line *);
void image_grid_free(struct grid *);
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
void image_grid_resize_width(struct grid *, u_int);
int image_grid_line_has_images(const struct grid_line *);
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
u_int);
int image_grid_get_placeholder(struct grid *, u_int, u_int,
struct kitty_placeholder *);
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
/* image-fallback.c */
void fallback_get_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *);
void fallback_free(struct image *);
/* image-kitty.c */
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, struct kitty_parse_result *);
int kitty_placeholder_to_image(void *, struct grid *,
struct grid_cell *, u_int, u_int,
struct kitty_placeholder *);
int kitty_cell_is_placeholder(const struct grid_cell *);
void kitty_delete_images(void *, struct screen_write_ctx *,
const struct kitty_parse_result *);
void kitty_free_state(void *);
void kitty_draw_rect(struct tty *,
const struct image_rect *);
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void kitty_redraw_finish(struct tty *);
void kitty_free_output_state(struct tty *, int);
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
void sixel_draw_rect(struct tty *,
const struct image_rect *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
void sixel_flush_output(struct tty *);
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
u_int);
void sixel_free(struct sixel_image *);
void sixel_log(struct sixel_image *);
struct image *sixel_to_image(struct sixel_image *);
#endif
/* server-acl.c */
#define SERVER_ACL_READONLY 0x1
#define SERVER_ACL_IS_GROUP 0x2

1
tty.c
View File

@@ -1487,7 +1487,6 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc)
return (&new);
}
void
tty_sync_start(struct tty *tty)
{

4
utf8.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: utf8.c,v 1.74 2026/10/05 08:32:24 nicm Exp $ */
/* $OpenBSD: utf8.c,v 1.75 2026/10/13 10:21:58 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -835,7 +835,7 @@ utf8_isvalid(const char *s)
continue;
return (0);
}
if (*s < 0x20 || *s > 0x7e)
if (*s != '\t' && *s != '\n' && (*s < 0x20 || *s > 0x7e))
return (0);
s++;
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-tree.c,v 1.96 2026/08/25 07:23:30 nicm Exp $ */
/* $OpenBSD: window-tree.c,v 1.97 2026/10/12 18:23:25 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1344,12 +1344,10 @@ window_tree_kill_current_callback(struct client *c, void *modedata,
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE);
data->references++;
window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE);
}
@@ -1365,12 +1363,10 @@ window_tree_kill_tagged_callback(struct client *c, void *modedata,
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE);
data->references++;
mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE);
}
@@ -1453,6 +1449,8 @@ window_tree_key(struct window_mode_entry *wme, struct client *c,
item = mode_tree_get_current(data->data);
finished = mode_tree_key(data->data, c, &key, m, &x, &y);
if (data->dead)
return;
again:
if (item != (new_item = mode_tree_get_current(data->data))) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window.c,v 1.385 2026/10/08 07:06:26 nicm Exp $ */
/* $OpenBSD: window.c,v 1.386 2026/10/13 10:21:58 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1670,6 +1670,7 @@ window_pane_free(struct window_pane *wp)
log_debug("pane %%%u freed (%d references)", wp->id, wp->references);
free(wp->searchstr);
free(wp->cmd_line);
screen_free(&wp->status_screen);
screen_free(&wp->base);
free(wp->r.ranges);