/* $OpenBSD$ */ /* * Copyright (c) 2026 Thomas Adam * * 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 #include #include #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); } /* * 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; *outlen = 3 + 7 + 2; out = xmalloc(*outlen + 1); memcpy(out, "\033_Ga=d,d=a\033\\", *outlen); out[*outlen] = '\0'; return (out); }