Files
tmux/image-kitty.c
2026-03-06 18:59:26 +00:00

326 lines
7.5 KiB
C

/* $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"
/*
* 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;
};
/*
* 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);
}
/*
* Get the size in cells of a kitty image. If cols/rows are 0 (auto),
* calculate from pixel dimensions. Returns size via sx/sy pointers.
*/
void
kitty_size_in_cells(struct kitty_image *ki, u_int *sx, u_int *sy)
{
*sx = ki->cols;
*sy = ki->rows;
/*
* If cols/rows are 0, they mean "auto" - calculate from
* pixel dimensions.
*/
if (*sx == 0 && ki->pixel_w > 0 && ki->xpixel > 0) {
*sx = (ki->pixel_w + ki->xpixel - 1) / ki->xpixel;
}
if (*sy == 0 && ki->pixel_h > 0 && ki->ypixel > 0) {
*sy = (ki->pixel_h + ki->ypixel - 1) / ki->ypixel;
}
/* If still 0, use a reasonable default */
if (*sx == 0)
*sx = 10;
if (*sy == 0)
*sy = 10;
}
char
kitty_get_action(struct kitty_image *ki)
{
return (ki->action);
}
u_int
kitty_get_image_id(struct kitty_image *ki)
{
return (ki->image_id);
}
u_int
kitty_get_rows(struct kitty_image *ki)
{
return (ki->rows);
}
/*
* Serialize a kitty_image back into an APC escape sequence for transmission
* to the terminal. This recreates the original command that was parsed.
*/
char *
kitty_print(struct kitty_image *ki, size_t *outlen)
{
char *out;
size_t total, pos;
if (ki == NULL || ki->ctrl == NULL)
return (NULL);
/* Calculate total length: ESC _ G + ctrl + ; + encoded + ESC \ */
total = 3 + ki->ctrllen; /* \033_G + ctrl */
if (ki->encoded != NULL && ki->encodedlen > 0) {
total += 1 + ki->encodedlen; /* ; + encoded */
}
total += 2; /* \033\\ */
out = xmalloc(total + 1);
*outlen = total;
/* Build the sequence */
pos = 0;
memcpy(out + pos, "\033_G", 3);
pos += 3;
memcpy(out + pos, ki->ctrl, ki->ctrllen);
pos += ki->ctrllen;
if (ki->encoded != NULL && ki->encodedlen > 0) {
out[pos++] = ';';
memcpy(out + pos, ki->encoded, ki->encodedlen);
pos += ki->encodedlen;
}
memcpy(out + pos, "\033\\", 2);
pos += 2;
out[pos] = '\0';
return (out);
}
char *
kitty_delete_all(size_t *outlen)
{
char *out;
out = xstrdup("\033_Ga=d,d=a\033\\");
*outlen = strlen(out);
return (out);
}