mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-20 07:24:41 +02:00
Graphics protocol: Add support for having the terminal emulator assign image ids
Useful when multiple non co-operating programs want to share the screen. Fixes #3163
This commit is contained in:
117
kitty/graphics.c
117
kitty/graphics.c
@@ -92,6 +92,16 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline Image*
|
||||
img_by_client_number(GraphicsManager *self, uint32_t number) {
|
||||
// get the newest image with the specified number
|
||||
for (size_t i = self->image_count; i-- > 0; ) {
|
||||
if (self->images[i].client_number == number) return self->images + i;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
remove_image(GraphicsManager *self, size_t idx) {
|
||||
free_image(self, self->images + idx);
|
||||
@@ -306,6 +316,33 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_client_ids(const void* a, const void* b) {
|
||||
const uint32_t *x = a, *y = b;
|
||||
return *x - *y;
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
get_free_client_id(const GraphicsManager *self) {
|
||||
if (!self->image_count) return 1;
|
||||
uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count);
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < self->image_count; i++) {
|
||||
Image *q = self->images + i;
|
||||
if (q->client_id) client_ids[count++] = q->client_id;
|
||||
}
|
||||
if (!count) { free(client_ids); return 1; }
|
||||
qsort(client_ids, count, sizeof(uint32_t), cmp_client_ids);
|
||||
uint32_t prev_id = 0, ans = 1;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (client_ids[i] == prev_id) continue;
|
||||
prev_id = client_ids[i];
|
||||
if (client_ids[i] != ans) break;
|
||||
ans = client_ids[i] + 1;
|
||||
}
|
||||
free(client_ids);
|
||||
return ans;
|
||||
}
|
||||
|
||||
static Image*
|
||||
handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
|
||||
@@ -333,6 +370,11 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
} else {
|
||||
img->internal_id = internal_id_counter++;
|
||||
img->client_id = iid;
|
||||
img->client_number = g->image_number;
|
||||
if (!img->client_id && img->client_number) {
|
||||
img->client_id = get_free_client_id(self);
|
||||
self->last_init_graphics_command.id = img->client_id;
|
||||
}
|
||||
}
|
||||
img->atime = monotonic(); img->used_storage = 0;
|
||||
img->width = g->data_width; img->height = g->data_height;
|
||||
@@ -472,7 +514,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid, uint32_t placement_id) {
|
||||
finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid, uint32_t placement_id, uint32_t image_number) {
|
||||
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
|
||||
bool is_ok_response = !command_response[0];
|
||||
if (g->quiet) {
|
||||
@@ -483,9 +525,14 @@ finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid
|
||||
if (!data_loaded) return NULL;
|
||||
snprintf(command_response, 10, "OK");
|
||||
}
|
||||
if (placement_id) snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u,p=%u;%s", iid, placement_id, command_response);
|
||||
else snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, command_response);
|
||||
size_t pos = 0;
|
||||
#define print(fmt, ...) pos += snprintf(rbuf + pos, arraysz(rbuf) - 1 - pos, fmt, __VA_ARGS__)
|
||||
print("Gi=%u", iid);
|
||||
if (image_number) print(",I=%u", image_number);
|
||||
if (placement_id) print(",p=%u", placement_id);
|
||||
print(";%s", command_response);
|
||||
return rbuf;
|
||||
#undef print
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -521,11 +568,14 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelS
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
static uint32_t
|
||||
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
|
||||
if (img == NULL) img = img_by_client_id(self, g->id);
|
||||
if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u", g->id); return; }
|
||||
if (!img->data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return; }
|
||||
if (img == NULL) {
|
||||
if (g->id) img = img_by_client_id(self, g->id);
|
||||
else if (g->image_number) img = img_by_client_number(self, g->image_number);
|
||||
if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return 0; }
|
||||
}
|
||||
if (!img->data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return 0; }
|
||||
ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
|
||||
*is_dirty = true;
|
||||
self->layers_dirty = true;
|
||||
@@ -556,6 +606,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
|
||||
update_dest_rect(ref, g->num_cells, g->num_lines, cell);
|
||||
// Move the cursor, the screen will take care of ensuring it is in bounds
|
||||
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
||||
return img->client_id;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -649,7 +700,8 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
||||
// Image lifetime/scrolling {{{
|
||||
|
||||
static inline void
|
||||
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
||||
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) {
|
||||
bool matched = false;
|
||||
for (size_t i = self->image_count; i-- > 0;) {
|
||||
Image *img = self->images + i;
|
||||
for (size_t j = img->refcnt; j-- > 0;) {
|
||||
@@ -657,12 +709,15 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi
|
||||
if (filter_func(ref, img, data, cell)) {
|
||||
remove_i_from_array(img->refs, j, img->refcnt);
|
||||
self->layers_dirty = true;
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
||||
if (only_first_image && matched) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
modify_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
||||
for (size_t i = self->image_count; i-- > 0;) {
|
||||
@@ -743,7 +798,7 @@ clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void
|
||||
|
||||
void
|
||||
grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
|
||||
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell);
|
||||
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -753,6 +808,14 @@ id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
const GraphicsCommand *g = data;
|
||||
if (img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
const GraphicsCommand *g = data;
|
||||
@@ -786,8 +849,9 @@ point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixel
|
||||
static void
|
||||
handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
||||
static GraphicsCommand d;
|
||||
bool only_first_image = false;
|
||||
switch (g->delete_action) {
|
||||
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell); *is_dirty = true; break
|
||||
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell, only_first_image); *is_dirty = true; break
|
||||
#define D(l, u, data, func) case l: case u: I(u, data, func)
|
||||
#define G(l, u, func) D(l, u, g, func)
|
||||
case 0:
|
||||
@@ -802,6 +866,10 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c
|
||||
case 'C':
|
||||
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
|
||||
I('C', &d, point_filter_func);
|
||||
case 'n':
|
||||
case 'N':
|
||||
only_first_image = true;
|
||||
I('N', &g, number_filter_func);
|
||||
default:
|
||||
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
|
||||
break;
|
||||
@@ -839,6 +907,11 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
const char *ret = NULL;
|
||||
command_response[0] = 0;
|
||||
|
||||
if (g->id && g->image_number) {
|
||||
set_command_failed_response("EINVAL", "Must not specify both image id and image number");
|
||||
return finish_command_response(g, false, g->id, g->placement_id, g->image_number);
|
||||
}
|
||||
|
||||
switch(g->action) {
|
||||
case 0:
|
||||
case 't':
|
||||
@@ -848,8 +921,8 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
bool is_query = g->action == 'q';
|
||||
if (is_query) { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } }
|
||||
Image *image = handle_add_command(self, g, payload, is_dirty, iid);
|
||||
if (is_query) ret = finish_command_response(g, image != NULL, q_iid, 0);
|
||||
else ret = finish_command_response(g, image != NULL, self->last_init_graphics_command.id, self->last_init_graphics_command.placement_id);
|
||||
if (is_query) ret = finish_command_response(g, image != NULL, q_iid, 0, 0);
|
||||
else ret = finish_command_response(g, image != NULL, self->last_init_graphics_command.id, self->last_init_graphics_command.placement_id, self->last_init_graphics_command.image_number);
|
||||
if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image, cell);
|
||||
id_type added_image_id = image ? image->internal_id : 0;
|
||||
if (g->action == 'q') remove_images(self, add_trim_predicate, 0);
|
||||
@@ -857,12 +930,12 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
if (!g->id) {
|
||||
REPORT_ERROR("Put graphics command without image id");
|
||||
if (!g->id && !g->image_number) {
|
||||
REPORT_ERROR("Put graphics command without image id or number");
|
||||
break;
|
||||
}
|
||||
handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||
ret = finish_command_response(g, true, g->id, g->placement_id);
|
||||
uint32_t image_id = handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||
ret = finish_command_response(g, true, image_id, g->placement_id, g->image_number);
|
||||
break;
|
||||
case 'd':
|
||||
handle_delete_command(self, g, c, is_dirty, cell);
|
||||
@@ -886,8 +959,8 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
||||
static inline PyObject*
|
||||
image_as_dict(Image *img) {
|
||||
#define U(x) #x, img->x
|
||||
return Py_BuildValue("{sI sI sI sI sK sI sO sO sN}",
|
||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt),
|
||||
return Py_BuildValue("{sI sI sI sI sK sI sI sO sO sN}",
|
||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(client_number),
|
||||
"data_loaded", img->data_loaded ? Py_True : Py_False,
|
||||
"is_4byte_aligned", img->load_data.is_4byte_aligned ? Py_True : Py_False,
|
||||
"data", Py_BuildValue("y#", img->load_data.data, img->load_data.data_sz)
|
||||
@@ -907,6 +980,13 @@ W(image_for_client_id) {
|
||||
return image_as_dict(img);
|
||||
}
|
||||
|
||||
W(image_for_client_number) {
|
||||
unsigned long num = PyLong_AsUnsignedLong(args);
|
||||
Image *img = img_by_client_number(self, num);
|
||||
if (!img) Py_RETURN_NONE;
|
||||
return image_as_dict(img);
|
||||
}
|
||||
|
||||
W(shm_write) {
|
||||
const char *name, *data;
|
||||
Py_ssize_t sz;
|
||||
@@ -957,6 +1037,7 @@ W(update_layers) {
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
M(image_for_client_id, METH_O),
|
||||
M(image_for_client_number, METH_O),
|
||||
M(update_layers, METH_VARARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
typedef struct {
|
||||
unsigned char action, transmission_type, compressed, delete_action;
|
||||
uint32_t format, more, id, data_sz, data_offset, placement_id, quiet;
|
||||
uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet;
|
||||
uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset;
|
||||
int32_t z_index;
|
||||
size_t payload_sz;
|
||||
@@ -44,7 +44,7 @@ typedef struct {
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t texture_id, client_id, width, height;
|
||||
uint32_t texture_id, client_id, client_number, width, height;
|
||||
id_type internal_id;
|
||||
|
||||
bool data_loaded;
|
||||
|
||||
34
kitty/parse-graphics-command.h
generated
34
kitty/parse-graphics-command.h
generated
@@ -24,6 +24,7 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
format = 'f',
|
||||
more = 'm',
|
||||
id = 'i',
|
||||
image_number = 'I',
|
||||
placement_id = 'p',
|
||||
quiet = 'q',
|
||||
width = 'w',
|
||||
@@ -72,6 +73,9 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
case id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case image_number:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case placement_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
@@ -140,8 +144,8 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case action: {
|
||||
g.action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.action != 'q' && g.action != 'd' && g.action != 'p' &&
|
||||
g.action != 'T' && g.action != 't') {
|
||||
if (g.action != 'T' && g.action != 'd' && g.action != 'p' &&
|
||||
g.action != 't' && g.action != 'q') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for action: 0x%x",
|
||||
g.action);
|
||||
@@ -151,14 +155,14 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case delete_action: {
|
||||
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.delete_action != 'y' && g.delete_action != 'q' &&
|
||||
g.delete_action != 'z' && g.delete_action != 'Z' &&
|
||||
g.delete_action != 'a' && g.delete_action != 'A' &&
|
||||
g.delete_action != 'Q' && g.delete_action != 'X' &&
|
||||
g.delete_action != 'i' && g.delete_action != 'p' &&
|
||||
g.delete_action != 'Y' && g.delete_action != 'P' &&
|
||||
g.delete_action != 'c' && g.delete_action != 'x' &&
|
||||
g.delete_action != 'C' && g.delete_action != 'I') {
|
||||
if (g.delete_action != 'y' && g.delete_action != 'c' &&
|
||||
g.delete_action != 'x' && g.delete_action != 'p' &&
|
||||
g.delete_action != 'P' && g.delete_action != 'Q' &&
|
||||
g.delete_action != 'C' && g.delete_action != 'Y' &&
|
||||
g.delete_action != 'i' && g.delete_action != 'z' &&
|
||||
g.delete_action != 'A' && g.delete_action != 'a' &&
|
||||
g.delete_action != 'q' && g.delete_action != 'Z' &&
|
||||
g.delete_action != 'X' && g.delete_action != 'I') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for delete_action: 0x%x",
|
||||
g.delete_action);
|
||||
@@ -168,8 +172,8 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case transmission_type: {
|
||||
g.transmission_type = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.transmission_type != 'f' && g.transmission_type != 's' &&
|
||||
g.transmission_type != 't' && g.transmission_type != 'd') {
|
||||
if (g.transmission_type != 's' && g.transmission_type != 'd' &&
|
||||
g.transmission_type != 't' && g.transmission_type != 'f') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for transmission_type: 0x%x",
|
||||
g.transmission_type);
|
||||
@@ -242,6 +246,7 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
U(format);
|
||||
U(more);
|
||||
U(id);
|
||||
U(image_number);
|
||||
U(placement_id);
|
||||
U(quiet);
|
||||
U(width);
|
||||
@@ -314,12 +319,13 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
}
|
||||
|
||||
REPORT_VA_COMMAND(
|
||||
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si "
|
||||
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si "
|
||||
"sI} y#",
|
||||
"graphics_command", "action", g.action, "delete_action", g.delete_action,
|
||||
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
||||
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
|
||||
(unsigned int)g.id, "placement_id", (unsigned int)g.placement_id, "quiet",
|
||||
(unsigned int)g.id, "image_number", (unsigned int)g.image_number,
|
||||
"placement_id", (unsigned int)g.placement_id, "quiet",
|
||||
(unsigned int)g.quiet, "width", (unsigned int)g.width, "height",
|
||||
(unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset",
|
||||
(unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height,
|
||||
|
||||
Reference in New Issue
Block a user