mirror of
https://github.com/tmux/tmux.git
synced 2026-03-06 15:55:33 +00:00
Add support for the kitty graphics protocol
Kitty-capable terminals (kitty, ghostty, and others) can display inline images via APC escape sequences. Kitty's image support uses a passthrough model: APC sequences from programs running inside panes are relayed verbatim to the outer terminal with cursor positioning adjusted for the pane offset. The outer terminal handles all image rendering and therefore itself must be kitty-aware.
This commit is contained in:
@@ -236,6 +236,11 @@ if ENABLE_SIXEL
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
endif
|
||||
|
||||
# Enable kitty graphics protocol support.
|
||||
if ENABLE_KITTY
|
||||
dist_tmux_SOURCES += image-kitty.c
|
||||
endif
|
||||
|
||||
if NEED_FUZZING
|
||||
check_PROGRAMS = fuzz/input-fuzzer
|
||||
fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
||||
|
||||
10
configure.ac
10
configure.ac
@@ -471,6 +471,16 @@ if test "x$enable_sixel" = xyes; then
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
|
||||
# Enable kitty graphics protocol support.
|
||||
AC_ARG_ENABLE(
|
||||
kitty,
|
||||
AS_HELP_STRING(--enable-kitty, enable kitty terminal graphics protocol)
|
||||
)
|
||||
if test "x$enable_kitty" = xyes; then
|
||||
AC_DEFINE(ENABLE_KITTY)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_KITTY, [test "x$enable_kitty" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM(
|
||||
|
||||
14
format.c
14
format.c
@@ -2571,6 +2571,17 @@ format_cb_sixel_support(__unused struct format_tree *ft)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for kitty_support. */
|
||||
static void *
|
||||
format_cb_kitty_support(__unused struct format_tree *ft)
|
||||
{
|
||||
#ifdef ENABLE_KITTY
|
||||
return (xstrdup("1"));
|
||||
#else
|
||||
return (xstrdup("0"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for active_window_index. */
|
||||
static void *
|
||||
format_cb_active_window_index(struct format_tree *ft)
|
||||
@@ -3469,6 +3480,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "sixel_support", FORMAT_TABLE_STRING,
|
||||
format_cb_sixel_support
|
||||
},
|
||||
{ "kitty_support", FORMAT_TABLE_STRING,
|
||||
format_cb_kitty_support
|
||||
},
|
||||
{ "socket_path", FORMAT_TABLE_STRING,
|
||||
format_cb_socket_path
|
||||
},
|
||||
|
||||
203
image-kitty.c
Normal file
203
image-kitty.c
Normal file
@@ -0,0 +1,203 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Thomas Adam <thomas@xteddy.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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
* Parse control-data key=value pairs from a kitty APC sequence.
|
||||
* Format: key=value,key=value,...
|
||||
*/
|
||||
static int
|
||||
kitty_parse_control(const char *ctrl, size_t ctrllen, struct kitty_image *ki)
|
||||
{
|
||||
const char *p = ctrl, *end = ctrl + ctrllen, *errstr;
|
||||
char key[4], val[32];
|
||||
size_t klen, vlen;
|
||||
|
||||
while (p < end) {
|
||||
klen = 0;
|
||||
while (p < end && *p != '=' && klen < sizeof(key) - 1)
|
||||
key[klen++] = *p++;
|
||||
key[klen] = '\0';
|
||||
if (p >= end || *p != '=')
|
||||
return (-1);
|
||||
p++;
|
||||
|
||||
vlen = 0;
|
||||
while (p < end && *p != ',' && vlen < sizeof(val) - 1)
|
||||
val[vlen++] = *p++;
|
||||
val[vlen] = '\0';
|
||||
if (p < end && *p == ',')
|
||||
p++;
|
||||
|
||||
if (klen != 1)
|
||||
continue;
|
||||
|
||||
switch (key[0]) {
|
||||
case 'a':
|
||||
ki->action = val[0];
|
||||
break;
|
||||
case 'f':
|
||||
ki->format = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 't':
|
||||
ki->medium = val[0];
|
||||
break;
|
||||
case 's':
|
||||
ki->pixel_w = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'v':
|
||||
ki->pixel_h = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'c':
|
||||
ki->cols = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'r':
|
||||
ki->rows = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'i':
|
||||
ki->image_id = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'I':
|
||||
ki->image_num = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'p':
|
||||
ki->placement_id = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'm':
|
||||
ki->more = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'q':
|
||||
ki->quiet = strtonum(val, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'z':
|
||||
ki->z_index = strtonum(val, INT_MIN, INT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
break;
|
||||
case 'o':
|
||||
ki->compression = val[0];
|
||||
break;
|
||||
case 'd':
|
||||
ki->delete_what = val[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a kitty APC body (after the leading 'G').
|
||||
* Stores the original control string and base64 payload verbatim for
|
||||
* pass-through re-emission to the outer terminal.
|
||||
*/
|
||||
struct kitty_image *
|
||||
kitty_parse(const u_char *buf, size_t len, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
struct kitty_image *ki;
|
||||
const u_char *semi;
|
||||
const char *ctrl;
|
||||
size_t ctrllen, paylen;
|
||||
|
||||
if (len == 0)
|
||||
return (NULL);
|
||||
|
||||
semi = memchr(buf, ';', len);
|
||||
if (semi != NULL) {
|
||||
ctrl = (const char *)buf;
|
||||
ctrllen = semi - buf;
|
||||
paylen = len - ctrllen - 1;
|
||||
} else {
|
||||
ctrl = (const char *)buf;
|
||||
ctrllen = len;
|
||||
paylen = 0;
|
||||
}
|
||||
|
||||
ki = xcalloc(1, sizeof *ki);
|
||||
ki->xpixel = xpixel;
|
||||
ki->ypixel = ypixel;
|
||||
ki->action = 'T';
|
||||
ki->format = 32;
|
||||
ki->medium = 'd';
|
||||
|
||||
if (kitty_parse_control(ctrl, ctrllen, ki) != 0) {
|
||||
free(ki);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (paylen > 0) {
|
||||
ki->encoded = xmalloc(paylen + 1);
|
||||
memcpy(ki->encoded, semi + 1, paylen);
|
||||
ki->encoded[paylen] = '\0';
|
||||
ki->encodedlen = paylen;
|
||||
}
|
||||
|
||||
ki->ctrl = xmalloc(ctrllen + 1);
|
||||
memcpy(ki->ctrl, ctrl, ctrllen);
|
||||
ki->ctrl[ctrllen] = '\0';
|
||||
ki->ctrllen = ctrllen;
|
||||
|
||||
return (ki);
|
||||
}
|
||||
|
||||
void
|
||||
kitty_free(struct kitty_image *ki)
|
||||
{
|
||||
if (ki == NULL)
|
||||
return;
|
||||
free(ki->encoded);
|
||||
free(ki->ctrl);
|
||||
free(ki);
|
||||
}
|
||||
|
||||
char *
|
||||
kitty_delete_all(size_t *outlen)
|
||||
{
|
||||
char *out;
|
||||
|
||||
*outlen = 3 + 7 + 2;
|
||||
out = xmalloc(*outlen + 1);
|
||||
memcpy(out, "\033_Ga=d,d=a\033\\", *outlen);
|
||||
out[*outlen] = '\0';
|
||||
return (out);
|
||||
}
|
||||
59
input.c
59
input.c
@@ -145,6 +145,7 @@ struct input_ctx {
|
||||
*/
|
||||
struct evbuffer *since_ground;
|
||||
struct event ground_timer;
|
||||
|
||||
};
|
||||
|
||||
/* Helper functions. */
|
||||
@@ -1381,6 +1382,10 @@ input_esc_dispatch(struct input_ctx *ictx)
|
||||
input_reset_cell(ictx);
|
||||
screen_write_reset(sctx);
|
||||
screen_write_fullredraw(sctx);
|
||||
#ifdef ENABLE_KITTY
|
||||
if (ictx->wp != NULL)
|
||||
tty_kitty_delete_all_pane(ictx->wp);
|
||||
#endif
|
||||
break;
|
||||
case INPUT_ESC_IND:
|
||||
screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
|
||||
@@ -2722,11 +2727,65 @@ input_exit_apc(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct window_pane *wp = ictx->wp;
|
||||
#ifdef ENABLE_KITTY
|
||||
struct kitty_image *ki;
|
||||
#endif
|
||||
|
||||
if (ictx->flags & INPUT_DISCARD)
|
||||
return;
|
||||
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
if (ictx->input_len >= 1 && ictx->input_buf[0] == 'G') {
|
||||
if (wp == NULL)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Intercept only a=q (query) to reply OK ourselves.
|
||||
* Everything else — including a=T (transmit+display),
|
||||
* a=d (delete), a=p (place), multi-chunk sequences —
|
||||
* passes through verbatim to the outer terminal.
|
||||
* The outer terminal manages all image placement and
|
||||
* scrolling; tmux must not interfere.
|
||||
*/
|
||||
ki = kitty_parse(ictx->input_buf + 1,
|
||||
ictx->input_len - 1, 0, 0);
|
||||
if (ki != NULL && ki->action == 'q') {
|
||||
if (ki->image_id != 0)
|
||||
input_reply(ictx, 0,
|
||||
"\033_Gi=%u;OK\033\\",
|
||||
ki->image_id);
|
||||
else
|
||||
input_reply(ictx, 0,
|
||||
"\033_Ga=q;OK\033\\");
|
||||
kitty_free(ki);
|
||||
return;
|
||||
}
|
||||
kitty_free(ki);
|
||||
|
||||
/* Reconstruct APC and pass through verbatim. */
|
||||
{
|
||||
char *apc;
|
||||
size_t apclen;
|
||||
|
||||
/* input_buf already starts with 'G'. */
|
||||
apclen = 2 + ictx->input_len + 2;
|
||||
apc = xmalloc(apclen + 1);
|
||||
apc[0] = '\033';
|
||||
apc[1] = '_';
|
||||
memcpy(apc + 2, ictx->input_buf,
|
||||
ictx->input_len);
|
||||
apc[2 + ictx->input_len] = '\033';
|
||||
apc[2 + ictx->input_len + 1] = '\\';
|
||||
apc[apclen] = '\0';
|
||||
tty_kitty_passthrough(wp, apc, apclen,
|
||||
sctx->s->cx, sctx->s->cy);
|
||||
free(apc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif /* ENABLE_KITTY */
|
||||
|
||||
if (wp != NULL &&
|
||||
options_get_number(wp->options, "allow-set-title") &&
|
||||
screen_set_title(sctx->s, ictx->input_buf)) {
|
||||
|
||||
@@ -667,6 +667,16 @@ screen_redraw_screen(struct client *c)
|
||||
}
|
||||
if (flags & CLIENT_REDRAWWINDOW) {
|
||||
log_debug("%s: redrawing panes", c->name);
|
||||
#ifdef ENABLE_KITTY
|
||||
/*
|
||||
* Delete all kitty image placements before redrawing panes.
|
||||
* This must happen unconditionally — even when the new window
|
||||
* has no images — so that images from the previous window
|
||||
* (or from a `reset` in the shell) are cleared from the outer
|
||||
* terminal before new content is drawn over them.
|
||||
*/
|
||||
tty_kitty_delete_all(&c->tty);
|
||||
#endif
|
||||
screen_redraw_draw_panes(&ctx);
|
||||
screen_redraw_draw_pane_scrollbars(&ctx);
|
||||
}
|
||||
@@ -697,8 +707,12 @@ screen_redraw_pane(struct client *c, struct window_pane *wp,
|
||||
tty_sync_start(&c->tty);
|
||||
tty_update_mode(&c->tty, c->tty.mode, NULL);
|
||||
|
||||
if (!redraw_scrollbar_only)
|
||||
if (!redraw_scrollbar_only) {
|
||||
#ifdef ENABLE_KITTY
|
||||
tty_kitty_delete_all(&c->tty);
|
||||
#endif
|
||||
screen_redraw_draw_pane(&ctx, wp);
|
||||
}
|
||||
|
||||
if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars))
|
||||
screen_redraw_draw_pane_scrollbar(&ctx, wp);
|
||||
|
||||
59
tmux.h
59
tmux.h
@@ -72,6 +72,10 @@ struct session;
|
||||
struct sixel_image;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
struct kitty_image;
|
||||
#endif
|
||||
|
||||
struct tty_ctx;
|
||||
struct tty_code;
|
||||
struct tty_key;
|
||||
@@ -614,6 +618,7 @@ enum tty_code_code {
|
||||
TTYC_SMULX,
|
||||
TTYC_SMXX,
|
||||
TTYC_SXL,
|
||||
TTYC_KTY,
|
||||
TTYC_SS,
|
||||
TTYC_SWD,
|
||||
TTYC_SYNC,
|
||||
@@ -943,6 +948,44 @@ struct image {
|
||||
TAILQ_HEAD(images, image);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
/*
|
||||
* kitty_image stores the raw decoded pixel data and metadata from a kitty
|
||||
* graphics protocol APC sequence. It is used to re-emit the sequence to the
|
||||
* outer terminal on redraw.
|
||||
*/
|
||||
struct kitty_image {
|
||||
/* Control-data fields parsed from the APC sequence. */
|
||||
char action; /* a=: 'T'=transmit+display, 't', 'p', 'd', ... */
|
||||
u_int format; /* f=: 32=RGBA, 24=RGB, 100=PNG */
|
||||
char medium; /* t=: 'd'=direct, 'f'=file, 't'=tmp, 's'=shm */
|
||||
u_int pixel_w; /* s=: source image pixel width */
|
||||
u_int pixel_h; /* v=: source image pixel height */
|
||||
u_int cols; /* c=: display columns (0=auto) */
|
||||
u_int rows; /* r=: display rows (0=auto) */
|
||||
u_int image_id; /* i=: image id (0=unassigned) */
|
||||
u_int image_num; /* I=: image number */
|
||||
u_int placement_id; /* p=: placement id */
|
||||
u_int more; /* m=: 1=more chunks coming, 0=last */
|
||||
u_int quiet; /* q=: suppress responses */
|
||||
int z_index; /* z=: z-index */
|
||||
char compression; /* o=: 'z'=zlib, 0=none */
|
||||
char delete_what; /* d=: delete target (used with a=d) */
|
||||
|
||||
/* Cell size at the time of parsing (from the owning window). */
|
||||
u_int xpixel;
|
||||
u_int ypixel;
|
||||
|
||||
/* Original base64-encoded payload (concatenated across all chunks). */
|
||||
char *encoded;
|
||||
size_t encodedlen;
|
||||
|
||||
char *ctrl;
|
||||
size_t ctrllen;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* Cursor style. */
|
||||
enum screen_cursor_style {
|
||||
SCREEN_CURSOR_DEFAULT,
|
||||
@@ -1566,6 +1609,7 @@ struct tty_term {
|
||||
#define TERM_RGBCOLOURS 0x10
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_KITTY 0x80
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -2658,6 +2702,13 @@ void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
void tty_kitty_delete_all(struct tty *);
|
||||
void tty_kitty_delete_all_pane(struct window_pane *);
|
||||
void tty_kitty_passthrough(struct window_pane *, const char *, size_t,
|
||||
u_int, u_int);
|
||||
#endif
|
||||
|
||||
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
|
||||
void tty_default_colours(struct grid_cell *, struct window_pane *);
|
||||
|
||||
@@ -3264,6 +3315,7 @@ void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
|
||||
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 *,
|
||||
@@ -3748,6 +3800,13 @@ char *sixel_print(struct sixel_image *, struct sixel_image *,
|
||||
struct screen *sixel_to_screen(struct sixel_image *);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
/* image-kitty.c */
|
||||
struct kitty_image *kitty_parse(const u_char *, size_t, u_int, u_int);
|
||||
void kitty_free(struct kitty_image *);
|
||||
char *kitty_delete_all(size_t *);
|
||||
#endif
|
||||
|
||||
/* server-acl.c */
|
||||
void server_acl_init(void);
|
||||
struct server_acl_user *server_acl_user_find(uid_t);
|
||||
|
||||
@@ -357,6 +357,17 @@ static const struct tty_feature tty_feature_sixel = {
|
||||
TERM_SIXEL
|
||||
};
|
||||
|
||||
/* Terminal has kitty graphics protocol capability. */
|
||||
static const char *const tty_feature_kitty_capabilities[] = {
|
||||
"Kty",
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_kitty = {
|
||||
"kitty",
|
||||
tty_feature_kitty_capabilities,
|
||||
TERM_KITTY
|
||||
};
|
||||
|
||||
/* Available terminal features. */
|
||||
static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_256,
|
||||
@@ -368,6 +379,7 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
&tty_feature_kitty,
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
@@ -500,7 +512,19 @@ tty_default_features(int *feat, const char *name, u_int version)
|
||||
*/
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM
|
||||
",ccolour,cstyle,extkeys,focus"
|
||||
}
|
||||
},
|
||||
#ifdef ENABLE_KITTY
|
||||
{ .name = "kitty",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM
|
||||
",ccolour,cstyle,extkeys,focus,sync,hyperlinks"
|
||||
",kitty"
|
||||
},
|
||||
{ .name = "ghostty",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM
|
||||
",ccolour,cstyle,extkeys,focus,sync,hyperlinks"
|
||||
",kitty"
|
||||
},
|
||||
#endif
|
||||
};
|
||||
u_int i;
|
||||
|
||||
|
||||
97
tty-keys.c
97
tty-keys.c
@@ -60,6 +60,10 @@ static int tty_keys_device_attributes2(struct tty *, const char *, size_t,
|
||||
static int tty_keys_extended_device_attributes(struct tty *, const char *,
|
||||
size_t, size_t *);
|
||||
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
|
||||
#ifdef ENABLE_KITTY
|
||||
static int tty_keys_kitty_graphics(struct tty *, const char *, size_t,
|
||||
size_t *);
|
||||
#endif
|
||||
|
||||
/* A key tree entry. */
|
||||
struct tty_key {
|
||||
@@ -759,6 +763,19 @@ tty_keys_next(struct tty *tty)
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
/* Is this a kitty graphics protocol response? ESC _ G ... ESC \ */
|
||||
switch (tty_keys_kitty_graphics(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
key = KEYC_UNKNOWN;
|
||||
goto complete_key;
|
||||
case -1: /* no, or not valid */
|
||||
break;
|
||||
case 1: /* partial */
|
||||
goto partial_key;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Is this a primary device attributes response? */
|
||||
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
@@ -1640,6 +1657,14 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
|
||||
tty_default_features(features, "mintty", 0);
|
||||
else if (strncmp(tmp, "foot(", 5) == 0)
|
||||
tty_default_features(features, "foot", 0);
|
||||
#ifdef ENABLE_KITTY
|
||||
else if (strncmp(tmp, "kitty ", 6) == 0 ||
|
||||
strcmp(tmp, "kitty") == 0)
|
||||
tty_default_features(features, "kitty", 0);
|
||||
else if (strncmp(tmp, "ghostty ", 8) == 0 ||
|
||||
strcmp(tmp, "ghostty") == 0)
|
||||
tty_default_features(features, "ghostty", 0);
|
||||
#endif
|
||||
log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf);
|
||||
|
||||
free(c->term_type);
|
||||
@@ -1795,3 +1820,75 @@ tty_keys_palette(struct tty *tty, const char *buf, size_t len, size_t *size)
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
/*
|
||||
* Handle kitty graphics protocol response from outer terminal.
|
||||
* Format: ESC _ G <key=value,...> ; <message> ESC \
|
||||
* The response to our capability probe (a=q) is:
|
||||
* ESC _ G i=31;OK ESC \ (supported)
|
||||
* ESC _ G i=31;ENOSYS:... (not supported)
|
||||
* Returns 0 for success, -1 for not a kitty response, 1 for partial.
|
||||
*/
|
||||
static int
|
||||
tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
|
||||
size_t *size)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
int *features = &c->term_features;
|
||||
size_t i;
|
||||
char tmp[256];
|
||||
|
||||
*size = 0;
|
||||
|
||||
/*
|
||||
* Kitty APC response starts with ESC _ G (3 bytes).
|
||||
* The 8-bit C1 equivalent 0x9f could also be used but is rare.
|
||||
*/
|
||||
if (buf[0] != '\033')
|
||||
return (-1);
|
||||
if (len == 1)
|
||||
return (1);
|
||||
if (buf[1] != '_')
|
||||
return (-1);
|
||||
if (len == 2)
|
||||
return (1);
|
||||
if (buf[2] != 'G')
|
||||
return (-1);
|
||||
if (len == 3)
|
||||
return (1);
|
||||
|
||||
/* Collect body up to ESC \ (ST). */
|
||||
for (i = 0; i < sizeof(tmp) - 1; i++) {
|
||||
if (3 + i + 1 >= len)
|
||||
return (1); /* partial */
|
||||
if (buf[3 + i] == '\033' && buf[3 + i + 1] == '\\') {
|
||||
tmp[i] = '\0';
|
||||
*size = 3 + i + 2;
|
||||
break;
|
||||
}
|
||||
tmp[i] = buf[3 + i];
|
||||
}
|
||||
if (i == sizeof(tmp) - 1)
|
||||
return (-1); /* too long, not a valid response */
|
||||
|
||||
log_debug("%s: kitty graphics response: %s", c->name, tmp);
|
||||
|
||||
/*
|
||||
* Check if the message (after the semicolon) starts with "OK".
|
||||
* The format is: i=31;OK or i=31;ENOSYS:...
|
||||
*/
|
||||
{
|
||||
const char *semi = strchr(tmp, ';');
|
||||
if (semi != NULL && strncmp(semi + 1, "OK", 2) == 0) {
|
||||
log_debug("%s: kitty graphics supported", c->name);
|
||||
tty_add_features(features, "kitty", ",");
|
||||
tty_update_features(tty);
|
||||
} else {
|
||||
log_debug("%s: kitty graphics not supported", c->name);
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -270,6 +270,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
|
||||
[TTYC_SETULC1] = { TTYCODE_STRING, "Setulc1" },
|
||||
[TTYC_SE] = { TTYCODE_STRING, "Se" },
|
||||
[TTYC_SXL] = { TTYCODE_FLAG, "Sxl" },
|
||||
[TTYC_KTY] = { TTYCODE_FLAG, "Kty" },
|
||||
[TTYC_SGR0] = { TTYCODE_STRING, "sgr0" },
|
||||
[TTYC_SITM] = { TTYCODE_STRING, "sitm" },
|
||||
[TTYC_SMACS] = { TTYCODE_STRING, "smacs" },
|
||||
@@ -461,6 +462,9 @@ tty_term_apply_overrides(struct tty_term *term)
|
||||
/* Log the SIXEL flag. */
|
||||
log_debug("SIXEL flag is %d", !!(term->flags & TERM_SIXEL));
|
||||
|
||||
/* Log the KITTY flag. */
|
||||
log_debug("KITTY flag is %d", !!(term->flags & TERM_KITTY));
|
||||
|
||||
/* Update the RGB flag if the terminal has RGB colours. */
|
||||
if (tty_term_has(term, TTYC_SETRGBF) &&
|
||||
tty_term_has(term, TTYC_SETRGBB))
|
||||
|
||||
117
tty.c
117
tty.c
@@ -391,6 +391,23 @@ tty_send_requests(struct tty *tty)
|
||||
return;
|
||||
|
||||
if (tty->term->flags & TERM_VT100LIKE) {
|
||||
#ifdef ENABLE_KITTY
|
||||
/*
|
||||
* Send the kitty graphics capability probe BEFORE the DA1
|
||||
* request. Per the kitty spec, a supporting terminal sends
|
||||
* its APC response before processing any subsequent requests.
|
||||
* So the reply ordering will be:
|
||||
* 1. ESC _ G i=31;OK ESC \ (kitty response, if supported)
|
||||
* 2. ESC [ ? ... c (DA1 response)
|
||||
* 3. ESC [ > ... c (DA2 response)
|
||||
* 4. ESC P > | ... ESC \ (XDA response)
|
||||
* which is exactly what our parsers expect.
|
||||
* Only probe if the kitty feature isn't already enabled.
|
||||
*/
|
||||
if (~tty->term->flags & TERM_KITTY)
|
||||
tty_puts(tty,
|
||||
"\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\");
|
||||
#endif
|
||||
if (~tty->flags & TTY_HAVEDA)
|
||||
tty_puts(tty, "\033[c");
|
||||
if (~tty->flags & TTY_HAVEDA2)
|
||||
@@ -1468,6 +1485,7 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
void
|
||||
tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
struct tty_ctx ttyctx;
|
||||
|
||||
@@ -1491,6 +1509,9 @@ tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s)
|
||||
ttyctx.allow_invisible_panes = 1;
|
||||
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
|
||||
}
|
||||
#else
|
||||
(void)c; (void)wp; (void)s;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2144,6 +2165,102 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_KITTY
|
||||
static int
|
||||
tty_has_kitty(struct tty *tty)
|
||||
{
|
||||
return ((tty->term->flags & TERM_KITTY) ||
|
||||
tty_term_has(tty->term, TTYC_KTY));
|
||||
}
|
||||
|
||||
/*
|
||||
* Pass a kitty APC sequence directly to all attached kitty-capable clients
|
||||
* showing the given pane. The outer terminal's cursor is first moved to
|
||||
* the pane-relative position (cx, cy) so that images placed at "current
|
||||
* cursor" land in the right spot. Pass cx=cy=UINT_MAX to skip cursor
|
||||
* positioning (e.g. for delete commands that don't depend on position).
|
||||
*/
|
||||
void
|
||||
tty_kitty_passthrough(struct window_pane *wp, const char *data, size_t len,
|
||||
u_int cx, u_int cy)
|
||||
{
|
||||
struct client *c;
|
||||
struct tty *tty;
|
||||
u_int x, y;
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session == NULL || c->tty.term == NULL)
|
||||
continue;
|
||||
if (c->flags & CLIENT_SUSPENDED)
|
||||
continue;
|
||||
if (c->tty.flags & TTY_FREEZE)
|
||||
continue;
|
||||
tty = &c->tty;
|
||||
if (!tty_has_kitty(tty))
|
||||
continue;
|
||||
if (c->session->curw->window != wp->window)
|
||||
continue;
|
||||
if (!window_pane_visible(wp))
|
||||
continue;
|
||||
|
||||
/* Position cursor at the correct screen location. */
|
||||
if (cx != UINT_MAX && cy != UINT_MAX) {
|
||||
x = wp->xoff + cx;
|
||||
y = wp->yoff + cy;
|
||||
if (status_at_line(c) == 0)
|
||||
y += status_line_size(c);
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, x, y);
|
||||
}
|
||||
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_add(tty, data, len);
|
||||
tty_invalidate(tty);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all kitty image placements from the outer terminal unconditionally.
|
||||
* Called directly (not via tty_write) so it fires on every full window
|
||||
* redraw regardless of whether the current window has any stored images.
|
||||
*/
|
||||
void
|
||||
tty_kitty_delete_all(struct tty *tty)
|
||||
{
|
||||
char *data;
|
||||
size_t size;
|
||||
|
||||
if (!tty_has_kitty(tty))
|
||||
return;
|
||||
|
||||
if ((data = kitty_delete_all(&size)) == NULL)
|
||||
return;
|
||||
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_add(tty, data, size);
|
||||
tty_invalidate(tty);
|
||||
free(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all kitty image placements via passthrough for a specific pane.
|
||||
* Used on terminal reset (RIS) so images are cleared from the outer terminal.
|
||||
*/
|
||||
void
|
||||
tty_kitty_delete_all_pane(struct window_pane *wp)
|
||||
{
|
||||
char *data;
|
||||
size_t size;
|
||||
|
||||
if ((data = kitty_delete_all(&size)) == NULL)
|
||||
return;
|
||||
|
||||
tty_kitty_passthrough(wp, data, size, UINT_MAX, UINT_MAX);
|
||||
free(data);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user