mirror of
https://github.com/tmux/tmux.git
synced 2026-10-16 16:44:30 +00:00
782 lines
18 KiB
C
782 lines
18 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
|
* THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/* State attached to an input parser. */
|
|
struct kitty_state {
|
|
char action;
|
|
u_int format;
|
|
char medium;
|
|
char compression;
|
|
char delete;
|
|
u_int width;
|
|
u_int height;
|
|
u_int source_x;
|
|
u_int source_y;
|
|
u_int source_width;
|
|
u_int source_height;
|
|
u_int x_offset;
|
|
u_int y_offset;
|
|
u_int columns;
|
|
u_int rows;
|
|
u_int image_id;
|
|
u_int image_number;
|
|
u_int placement_id;
|
|
u_int quiet;
|
|
u_int data_size;
|
|
int no_cursor;
|
|
int more;
|
|
|
|
u_char *data;
|
|
size_t size;
|
|
size_t allocated;
|
|
|
|
TAILQ_HEAD(, kitty_image_map) images;
|
|
TAILQ_ENTRY(kitty_state) entry;
|
|
};
|
|
TAILQ_HEAD(kitty_states, kitty_state);
|
|
|
|
/* Kitty input states holding uploaded images. */
|
|
static struct kitty_states kitty_states = TAILQ_HEAD_INITIALIZER(kitty_states);
|
|
|
|
/* Map a Kitty placement ID to an image ID. */
|
|
struct kitty_placement_map {
|
|
u_int kitty_id;
|
|
u_int image_id;
|
|
|
|
TAILQ_ENTRY(kitty_placement_map) entry;
|
|
};
|
|
|
|
/* Map Kitty image ID to image ID and placement views. */
|
|
struct kitty_image_map {
|
|
u_int kitty_id;
|
|
u_int image_id;
|
|
TAILQ_HEAD(, kitty_placement_map) placements;
|
|
|
|
TAILQ_ENTRY(kitty_image_map) entry;
|
|
};
|
|
|
|
/* Select Kitty output. */
|
|
int
|
|
kitty_select(__unused struct tty *tty)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/* Parse Kitty graphics control data. */
|
|
static int
|
|
kitty_parse_control_data(struct kitty_state *ks, const u_char *data,
|
|
size_t size, const char **m)
|
|
{
|
|
char *copy, *ptr, *next, *value;
|
|
const char *errstr;
|
|
u_int number;
|
|
char key;
|
|
int z;
|
|
|
|
*m = NULL;
|
|
|
|
copy = ptr = xstrndup(data, size);
|
|
while ((next = strsep(&ptr, ",")) != NULL) {
|
|
if (*next == '\0') {
|
|
if (ptr == NULL)
|
|
break;
|
|
*m = (*m == NULL ? "EINVAL" : *m);
|
|
continue;
|
|
}
|
|
key = next[0];
|
|
if (next[1] != '=' || next[2] == '\0') {
|
|
*m = (*m == NULL ? "EINVAL" : *m);
|
|
continue;
|
|
}
|
|
value = next + 2;
|
|
|
|
switch (key) {
|
|
case 'a':
|
|
case 't':
|
|
case 'o':
|
|
case 'd':
|
|
if (value[1] != '\0') {
|
|
*m = (*m == NULL ? "EINVAL" : *m);
|
|
break;
|
|
}
|
|
switch (key) {
|
|
case 'a':
|
|
ks->action = value[0];
|
|
break;
|
|
case 't':
|
|
ks->medium = value[0];
|
|
break;
|
|
case 'o':
|
|
ks->compression = value[0];
|
|
break;
|
|
case 'd':
|
|
ks->delete = value[0];
|
|
break;
|
|
}
|
|
break;
|
|
case 'z':
|
|
z = strtonum(value, INT_MIN, INT_MAX, &errstr);
|
|
if (errstr != NULL)
|
|
*m = (*m == NULL ? "EINVAL" : *m);
|
|
else if (z != 0)
|
|
*m = (*m == NULL ? "ENOTSUP" : *m);
|
|
break;
|
|
case 'U':
|
|
case 'P':
|
|
case 'Q':
|
|
case 'H':
|
|
case 'V':
|
|
if (strtonum(value, INT_MIN, UINT_MAX, &errstr) != 0)
|
|
*m = (*m == NULL ? "ENOTSUP" : *m);
|
|
else if (errstr != NULL)
|
|
*m = (*m == NULL ? "ENOTSUP" : *m);
|
|
break;
|
|
case 'f':
|
|
case 's':
|
|
case 'v':
|
|
case 'i':
|
|
case 'I':
|
|
case 'p':
|
|
case 'q':
|
|
case 'm':
|
|
case 'S':
|
|
case 'x':
|
|
case 'y':
|
|
case 'w':
|
|
case 'h':
|
|
case 'X':
|
|
case 'Y':
|
|
case 'c':
|
|
case 'r':
|
|
case 'C':
|
|
number = strtonum(value, 0, UINT_MAX, &errstr);
|
|
if (errstr != NULL) {
|
|
*m = (*m == NULL ? "EINVAL" : *m);
|
|
break;
|
|
}
|
|
switch (key) {
|
|
case 'f':
|
|
ks->format = number;
|
|
break;
|
|
case 's':
|
|
ks->width = number;
|
|
break;
|
|
case 'v':
|
|
ks->height = number;
|
|
break;
|
|
case 'i':
|
|
ks->image_id = number;
|
|
break;
|
|
case 'I':
|
|
ks->image_number = number;
|
|
break;
|
|
case 'p':
|
|
ks->placement_id = number;
|
|
break;
|
|
case 'q':
|
|
ks->quiet = number;
|
|
break;
|
|
case 'm':
|
|
ks->more = (number != 0);
|
|
break;
|
|
case 'S':
|
|
ks->data_size = number;
|
|
break;
|
|
case 'x':
|
|
ks->source_x = number;
|
|
break;
|
|
case 'y':
|
|
ks->source_y = number;
|
|
break;
|
|
case 'w':
|
|
ks->source_width = number;
|
|
break;
|
|
case 'h':
|
|
ks->source_height = number;
|
|
break;
|
|
case 'X':
|
|
ks->x_offset = number;
|
|
break;
|
|
case 'Y':
|
|
ks->y_offset = number;
|
|
break;
|
|
case 'c':
|
|
ks->columns = number;
|
|
break;
|
|
case 'r':
|
|
ks->rows = number;
|
|
break;
|
|
case 'C':
|
|
ks->no_cursor = (number != 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free(copy);
|
|
if (*m != NULL)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
/* Free Kitty graphics parser state. */
|
|
void
|
|
kitty_free_state(void *state)
|
|
{
|
|
struct kitty_state *ks = state;
|
|
struct kitty_image_map *map;
|
|
struct kitty_placement_map *kpl;
|
|
|
|
if (ks == NULL)
|
|
return;
|
|
TAILQ_REMOVE(&kitty_states, ks, entry);
|
|
while ((map = TAILQ_FIRST(&ks->images)) != NULL) {
|
|
TAILQ_REMOVE(&ks->images, map, entry);
|
|
while ((kpl = TAILQ_FIRST(&map->placements)) != NULL) {
|
|
TAILQ_REMOVE(&map->placements, kpl, entry);
|
|
free(kpl);
|
|
}
|
|
image_free(image_find(map->image_id));
|
|
free(map);
|
|
}
|
|
free(ks->data);
|
|
free(ks);
|
|
}
|
|
|
|
/* Remove a Kitty image. */
|
|
void
|
|
kitty_remove_image(struct image *im)
|
|
{
|
|
struct kitty_state *ks;
|
|
struct kitty_image_map *map, *next;
|
|
struct kitty_placement_map *kpl, *kpl1;
|
|
u_int id = image_get_id(im);
|
|
|
|
TAILQ_FOREACH(ks, &kitty_states, entry) {
|
|
TAILQ_FOREACH_SAFE(map, &ks->images, entry, next) {
|
|
TAILQ_FOREACH_SAFE(kpl, &map->placements, entry, kpl1) {
|
|
if (map->image_id != id && kpl->image_id != id)
|
|
continue;
|
|
TAILQ_REMOVE(&map->placements, kpl, entry);
|
|
free(kpl);
|
|
}
|
|
if (map->image_id != id)
|
|
continue;
|
|
TAILQ_REMOVE(&ks->images, map, entry);
|
|
image_free(im);
|
|
free(map);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Find an image mapping by Kitty image ID. */
|
|
static struct kitty_image_map *
|
|
kitty_image_map_find(struct kitty_state *ks, u_int id)
|
|
{
|
|
struct kitty_image_map *map;
|
|
|
|
TAILQ_FOREACH(map, &ks->images, entry) {
|
|
if (map->kitty_id == id)
|
|
return (map);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/* Replace the image associated with a Kitty image ID. */
|
|
static struct kitty_image_map *
|
|
kitty_image_map_set(struct kitty_state *ks, struct screen_write_ctx *ctx,
|
|
u_int id, struct image *im)
|
|
{
|
|
struct kitty_image_map *map;
|
|
struct kitty_placement_map *kpl;
|
|
struct image *old;
|
|
|
|
if (id == 0)
|
|
return (NULL);
|
|
map = kitty_image_map_find(ks, id);
|
|
if (map != NULL) {
|
|
old = image_find(map->image_id);
|
|
image_set_order_id(im, image_get_order_id(old));
|
|
while ((kpl = TAILQ_FIRST(&map->placements)) != NULL) {
|
|
TAILQ_REMOVE(&map->placements, kpl, entry);
|
|
image_clear(ctx, kpl->image_id);
|
|
free(kpl);
|
|
}
|
|
image_free(image_find(map->image_id));
|
|
} else {
|
|
map = xcalloc(1, sizeof *map);
|
|
map->kitty_id = id;
|
|
TAILQ_INIT(&map->placements);
|
|
TAILQ_INSERT_HEAD(&ks->images, map, entry);
|
|
}
|
|
image_ref(im);
|
|
map->image_id = image_get_id(im);
|
|
return (map);
|
|
}
|
|
|
|
/* Replace a named placement, or add a new anonymous placement. */
|
|
static void
|
|
kitty_placement_map_set(struct screen_write_ctx *ctx,
|
|
struct kitty_image_map *map, u_int id, struct image *im)
|
|
{
|
|
struct kitty_placement_map *kpl, *kpl1;
|
|
|
|
if (map == NULL)
|
|
return;
|
|
TAILQ_FOREACH_SAFE(kpl, &map->placements, entry, kpl1) {
|
|
if (image_find(kpl->image_id) == NULL) {
|
|
TAILQ_REMOVE(&map->placements, kpl, entry);
|
|
free(kpl);
|
|
continue;
|
|
}
|
|
if (id != 0 && kpl->kitty_id == id)
|
|
break;
|
|
}
|
|
if (kpl == NULL) {
|
|
kpl = xcalloc(1, sizeof *kpl);
|
|
kpl->kitty_id = id;
|
|
TAILQ_INSERT_TAIL(&map->placements, kpl, entry);
|
|
} else
|
|
image_clear(ctx, kpl->image_id);
|
|
kpl->image_id = image_get_id(im);
|
|
}
|
|
|
|
/* Remove mappings for absent placements. */
|
|
static u_int
|
|
kitty_prune_placements(struct kitty_image_map *map, struct screen *s)
|
|
{
|
|
struct kitty_placement_map *kpl, *kpl1;
|
|
u_int removed = 0;
|
|
struct grid *sgd = s->saved_grid;
|
|
|
|
TAILQ_FOREACH_SAFE(kpl, &map->placements, entry, kpl1) {
|
|
if (image_grid_has_image(s->grid, kpl->image_id))
|
|
continue;
|
|
if (sgd != NULL && image_grid_has_image(sgd, kpl->image_id))
|
|
continue;
|
|
TAILQ_REMOVE(&map->placements, kpl, entry);
|
|
free(kpl);
|
|
removed++;
|
|
}
|
|
return (removed);
|
|
}
|
|
|
|
/* Delete selected Kitty placements. */
|
|
static void
|
|
kitty_delete_images(struct kitty_state *ks, struct screen_write_ctx *ctx)
|
|
{
|
|
struct screen *s = ctx->s;
|
|
struct grid *gd = s->grid;
|
|
struct kitty_image_map *map, *map1;
|
|
struct kitty_placement_map *kpl;
|
|
u_int removed;
|
|
char how = ks->delete;
|
|
int release;
|
|
|
|
release = (how >= 'A' && how <= 'Z');
|
|
if (release)
|
|
how += 'a' - 'A';
|
|
TAILQ_FOREACH(map, &ks->images, entry)
|
|
kitty_prune_placements(map, s);
|
|
if (how == 'a')
|
|
image_grid_clear_visible(gd, IMAGE_TYPE_KITTY);
|
|
|
|
TAILQ_FOREACH_SAFE(map, &ks->images, entry, map1) {
|
|
removed = 0;
|
|
if (how == 'i') {
|
|
if (map->kitty_id != ks->image_id)
|
|
continue;
|
|
TAILQ_FOREACH(kpl, &map->placements, entry) {
|
|
if (ks->placement_id != 0 &&
|
|
kpl->kitty_id != ks->placement_id)
|
|
continue;
|
|
removed += image_grid_clear(gd, kpl->image_id);
|
|
}
|
|
if (ks->placement_id == 0)
|
|
removed++;
|
|
}
|
|
removed += kitty_prune_placements(map, s);
|
|
if (!release || removed == 0 || !TAILQ_EMPTY(&map->placements))
|
|
continue;
|
|
TAILQ_REMOVE(&ks->images, map, entry);
|
|
image_free(image_find(map->image_id));
|
|
free(map);
|
|
}
|
|
if (ctx->wp != NULL)
|
|
ctx->wp->flags |= PANE_REDRAW;
|
|
}
|
|
|
|
/* Decode and append one base64-encoded Kitty payload chunk. */
|
|
static int
|
|
kitty_append_data(struct kitty_state *ks, const u_char *data, size_t size)
|
|
{
|
|
u_char *d;
|
|
size_t dsize, allocated;
|
|
|
|
if (size > IMAGE_SIZE_LIMIT)
|
|
return (-1);
|
|
|
|
d = image_base64_decode(data, size, IMAGE_SIZE_LIMIT, &dsize);
|
|
if (d == NULL)
|
|
return (-1);
|
|
if (dsize == 0) {
|
|
free(d);
|
|
return (0);
|
|
}
|
|
if (dsize > IMAGE_SIZE_LIMIT - ks->size) {
|
|
free(d);
|
|
return (-1);
|
|
}
|
|
|
|
if (ks->size + dsize > ks->allocated) {
|
|
allocated = ks->allocated;
|
|
if (allocated == 0)
|
|
allocated = 4096;
|
|
while (allocated < ks->size + dsize)
|
|
allocated *= 2;
|
|
ks->data = xrealloc(ks->data, allocated);
|
|
ks->allocated = allocated;
|
|
}
|
|
memcpy(ks->data + ks->size, d, dsize);
|
|
ks->size += dsize;
|
|
|
|
free(d);
|
|
return (0);
|
|
}
|
|
|
|
/* Reply to a Kitty graphics sequence. */
|
|
static void
|
|
kitty_send_reply(struct input_ctx *ictx, struct kitty_state *ks, const char *m)
|
|
{
|
|
char i[96] = "";
|
|
char I[96] = "";
|
|
char p[96] = "";
|
|
|
|
if (ks->quiet >= 2)
|
|
return;
|
|
if (ks->action == 'd' && strcmp(m, "OK") == 0)
|
|
return;
|
|
if (ks->quiet == 1 && strcmp(m, "OK") == 0)
|
|
return;
|
|
if (ks->image_id == 0 && ks->image_number == 0 && ks->action != 'q')
|
|
return;
|
|
|
|
if (ks->image_id != 0 || ks->image_number == 0)
|
|
xsnprintf(i, sizeof i, "i=%u", ks->image_id);
|
|
if (ks->image_number != 0) {
|
|
if (*i != '\0')
|
|
strlcat(i, ",", sizeof i);
|
|
xsnprintf(I, sizeof I, "I=%u", ks->image_number);
|
|
}
|
|
if (ks->placement_id != 0)
|
|
xsnprintf(p, sizeof p, ",p=%u", ks->placement_id);
|
|
input_reply(ictx, 0, "\033_G%s%s%s;%s\033\\", i, I, p, m);
|
|
}
|
|
|
|
/* Create a Kitty placement with shared source pixels and stored geometry. */
|
|
static struct image *
|
|
kitty_place_image(struct image *src, struct kitty_state *ks, u_int xpixel,
|
|
u_int ypixel)
|
|
{
|
|
struct image_geom *sg = image_get_geometry(src);
|
|
enum image_type type = image_get_type(src);
|
|
struct image_geom ig = { 0 };
|
|
u_char *pixels;
|
|
size_t stride;
|
|
uint64_t cw, ch;
|
|
double size;
|
|
|
|
ig.src_x = ks->source_x;
|
|
ig.src_y = ks->source_y;
|
|
|
|
/* Clip the source rectangle. */
|
|
if (ig.src_x >= sg->src_w || ig.src_y >= sg->src_h)
|
|
return (NULL);
|
|
ig.src_w = sg->src_w - ig.src_x;
|
|
if (ks->source_width != 0 && ig.src_w > ks->source_width)
|
|
ig.src_w = ks->source_width;
|
|
ig.src_h = sg->src_h - ig.src_y;
|
|
if (ks->source_height != 0 && ig.src_h > ks->source_height)
|
|
ig.src_h = ks->source_height;
|
|
|
|
/* Clamp offsets to the starting cell. */
|
|
if (xpixel == 0)
|
|
xpixel = 8;
|
|
if (ypixel == 0)
|
|
ypixel = 16;
|
|
ig.canvas_xoff = ks->x_offset;
|
|
if (ig.canvas_xoff >= xpixel)
|
|
ig.canvas_xoff = xpixel - 1;
|
|
ig.canvas_yoff = ks->y_offset;
|
|
if (ig.canvas_yoff >= ypixel)
|
|
ig.canvas_yoff = ypixel - 1;
|
|
|
|
/* Work out the size. */
|
|
ig.sx = ks->columns;
|
|
ig.sy = ks->rows;
|
|
ig.scaled_width = ig.src_w;
|
|
ig.scaled_height = ig.src_h;
|
|
if (ig.sx == 0 && ig.sy == 0) {
|
|
if (ig.canvas_xoff > UINT_MAX - ig.src_w ||
|
|
ig.canvas_yoff > UINT_MAX - ig.src_h)
|
|
return (NULL);
|
|
image_size_in_cells(ig.src_w + ig.canvas_xoff,
|
|
ig.src_h + ig.canvas_yoff, xpixel, ypixel, &ig.sx, &ig.sy);
|
|
} else if (ig.sy == 0) {
|
|
ig.scaled_width = (double)ig.sx * xpixel - ig.canvas_xoff;
|
|
ig.scaled_height = ig.src_h * ig.scaled_width / ig.src_w;
|
|
|
|
size = ceil((ig.scaled_height + ig.canvas_yoff) / ypixel);
|
|
if (size > USHRT_MAX)
|
|
return (NULL);
|
|
ig.sy = size;
|
|
} else if (ig.sx == 0) {
|
|
ig.scaled_height = (double)ig.sy * ypixel - ig.canvas_yoff;
|
|
ig.scaled_width = ig.src_w * ig.scaled_height / ig.src_h;
|
|
|
|
size = ceil((ig.scaled_width + ig.canvas_xoff) / xpixel);
|
|
if (size > USHRT_MAX)
|
|
return (NULL);
|
|
ig.sx = size;
|
|
} else {
|
|
ig.scaled_width = (double)ig.sx * xpixel - ig.canvas_xoff;
|
|
ig.scaled_height = (double)ig.sy * ypixel - ig.canvas_yoff;
|
|
}
|
|
if (ig.sx > USHRT_MAX ||
|
|
ig.sy > USHRT_MAX ||
|
|
ig.sx * ig.sy > IMAGE_SIZE_LIMIT)
|
|
return (NULL);
|
|
|
|
/* Record geometry in the input terminal's pixel coordinates. */
|
|
cw = (uint64_t)ig.sx * xpixel;
|
|
ch = (uint64_t)ig.sy * ypixel;
|
|
if (cw > UINT_MAX || ch > UINT_MAX)
|
|
return (NULL);
|
|
ig.canvas_width = cw;
|
|
ig.canvas_height = ch;
|
|
pixels = image_get_pixels(src, &stride, NULL);
|
|
return (image_create_with_parent(type, src, &ig, stride, pixels));
|
|
}
|
|
|
|
/* Parse and reply to a Kitty graphics sequence. */
|
|
void
|
|
kitty_parse_input(struct input_ctx *ictx, struct window_pane *wp,
|
|
struct screen_write_ctx *ctx, u_int bg, void **state, const u_char *buf,
|
|
size_t len)
|
|
{
|
|
struct window *w = wp->window;
|
|
struct kitty_state *ks = *state;
|
|
struct kitty_state cks = { .action = 't' };
|
|
struct image_geom ig = { 0 };
|
|
struct kitty_image_map *map = NULL;
|
|
const u_char *found;
|
|
const char *m;
|
|
u_char *p = NULL;
|
|
size_t csize, psize;
|
|
u_int sx, sy;
|
|
uint64_t cw, ch;
|
|
struct image *src = NULL, *im = NULL;
|
|
int error;
|
|
|
|
buf++;
|
|
len--;
|
|
|
|
if (ks == NULL) {
|
|
ks = xcalloc(1, sizeof *ks);
|
|
TAILQ_INIT(&ks->images);
|
|
TAILQ_INSERT_TAIL(&kitty_states, ks, entry);
|
|
*state = ks;
|
|
}
|
|
|
|
found = memchr(buf, ';', len);
|
|
if (found == NULL) {
|
|
csize = len;
|
|
psize = 0;
|
|
} else {
|
|
csize = found - buf;
|
|
psize = len - csize - 1;
|
|
}
|
|
|
|
if (ks->more) {
|
|
kitty_parse_control_data(&cks, buf, csize, &m);
|
|
if (cks.action == 'd') {
|
|
free(ks->data);
|
|
ks->data = NULL;
|
|
ks->size = 0;
|
|
ks->allocated = 0;
|
|
ks->more = 0;
|
|
}
|
|
}
|
|
|
|
if (!ks->more) {
|
|
ks->action = 't';
|
|
ks->format = 32;
|
|
ks->medium = 'd';
|
|
ks->compression = 'o';
|
|
ks->delete = 'a';
|
|
ks->width = 0;
|
|
ks->height = 0;
|
|
ks->source_x = 0;
|
|
ks->source_y = 0;
|
|
ks->source_width = 0;
|
|
ks->source_height = 0;
|
|
ks->x_offset = 0;
|
|
ks->y_offset = 0;
|
|
ks->columns = 0;
|
|
ks->rows = 0;
|
|
ks->image_id = 0;
|
|
ks->image_number = 0;
|
|
ks->placement_id = 0;
|
|
ks->quiet = 0;
|
|
ks->data_size = 0;
|
|
ks->no_cursor = 0;
|
|
}
|
|
ks->more = 0;
|
|
|
|
error = kitty_parse_control_data(ks, buf, csize, &m);
|
|
if (error != 0)
|
|
goto out;
|
|
m = "EINVAL";
|
|
if (ks->image_id == 0 && ks->image_number == 0)
|
|
ks->placement_id = 0;
|
|
if (ks->image_number != 0) {
|
|
if (ks->image_id != 0)
|
|
goto out;
|
|
m = "ENOTSUP";
|
|
goto out;
|
|
}
|
|
if (ks->medium != 'd') {
|
|
m = "ENOTSUP";
|
|
goto out;
|
|
}
|
|
if (ks->action != 't' && ks->action != 'T' &&
|
|
ks->action != 'q' && ks->action != 'p' && ks->action != 'd') {
|
|
m = "ENOTSUP";
|
|
goto out;
|
|
}
|
|
if (ks->action == 'd') {
|
|
switch (ks->delete) {
|
|
case 'a':
|
|
case 'A':
|
|
case 'i':
|
|
case 'I':
|
|
break;
|
|
default:
|
|
m = "ENOTSUP";
|
|
goto out;
|
|
}
|
|
kitty_delete_images(ks, ctx);
|
|
m = "OK";
|
|
goto out;
|
|
}
|
|
if (psize != 0 && kitty_append_data(ks, found + 1, psize) != 0)
|
|
goto out;
|
|
if (ks->more)
|
|
return;
|
|
|
|
if (ks->action == 'p') {
|
|
map = kitty_image_map_find(ks, ks->image_id);
|
|
if (map == NULL) {
|
|
m = "ENOENT";
|
|
goto out;
|
|
}
|
|
src = image_find(map->image_id);
|
|
image_ref(src);
|
|
|
|
im = kitty_place_image(src, ks, w->xpixel, w->ypixel);
|
|
if (im == NULL)
|
|
goto out;
|
|
goto place;
|
|
}
|
|
|
|
if (ks->compression != 'o' && ks->compression != 'z')
|
|
goto out;
|
|
switch (ks->format) {
|
|
case 24:
|
|
case 32:
|
|
p = image_raw_decode(ks->data, ks->size, IMAGE_SIZE_LIMIT,
|
|
ks->format, ks->width, ks->height, ks->compression == 'z');
|
|
break;
|
|
case 100:
|
|
p = image_png_decode(ks->data, ks->size, IMAGE_SIZE_LIMIT,
|
|
ks->compression == 'z', ks->data_size, &ks->width,
|
|
&ks->height);
|
|
break;
|
|
}
|
|
if (p == NULL)
|
|
goto out;
|
|
|
|
cw = (w->xpixel == 0 ? 8 : w->xpixel);
|
|
ch = (w->ypixel == 0 ? 16 : w->ypixel);
|
|
image_size_in_cells(ks->width, ks->height, cw, ch, &sx, &sy);
|
|
cw *= sx;
|
|
ch *= sy;
|
|
if (cw > UINT_MAX || ch > UINT_MAX) {
|
|
free(p);
|
|
goto out;
|
|
}
|
|
ig.src_w = ig.scaled_width = ks->width;
|
|
ig.src_h = ig.scaled_height = ks->height;
|
|
ig.canvas_width = cw;
|
|
ig.canvas_height = ch;
|
|
ig.sx = sx;
|
|
ig.sy = sy;
|
|
src = image_create(IMAGE_TYPE_KITTY, &ig, p);
|
|
if (src == NULL) {
|
|
free(p);
|
|
goto out;
|
|
}
|
|
|
|
if (ks->action == 'T') {
|
|
im = kitty_place_image(src, ks, w->xpixel, w->ypixel);
|
|
if (im == NULL)
|
|
goto out;
|
|
}
|
|
if (ks->action != 'q')
|
|
map = kitty_image_map_set(ks, ctx, ks->image_id, src);
|
|
|
|
place:
|
|
if (im != NULL) {
|
|
image_set_order_id(im, image_get_order_id(src));
|
|
kitty_placement_map_set(ctx, map, ks->placement_id, im);
|
|
image_write(ctx, im, bg, ks->no_cursor);
|
|
}
|
|
m = "OK";
|
|
|
|
out:
|
|
kitty_send_reply(ictx, ks, m);
|
|
if (im != NULL)
|
|
image_free(im);
|
|
if (src != NULL)
|
|
image_free(src);
|
|
|
|
free(ks->data);
|
|
ks->data = NULL;
|
|
ks->size = 0;
|
|
ks->allocated = 0;
|
|
ks->more = 0;
|
|
}
|