Graphics protocol: Allow suppressing responses from the terminal to graphics commands
This commit is contained in:
parent
c9828dfece
commit
23420adfa6
@ -14,7 +14,8 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
when the tab bar is hidden (:iss:`3115`)
|
||||
|
||||
- Graphics protocol: Add support for giving individual image placements their
|
||||
own ids. This is a backwards compatible protocol extension. (:iss:`3133`)
|
||||
own ids. This is a backwards compatible protocol extension, and also allow
|
||||
suppressing responses from the terminal to commands (:iss:`3133`)
|
||||
|
||||
- Distribute extra pixels among all eight-blocks rather than adding them
|
||||
all to the last block (:iss:`3097`)
|
||||
|
||||
@ -430,6 +430,18 @@ Some examples::
|
||||
<ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the placements with z-index -1, also freeing up image data
|
||||
<ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all placements that intersect the cell at (3, 4), without freeing data
|
||||
|
||||
|
||||
Suppressing responses from the terminal
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are using the graphics protocol from a limited client, such as a shell
|
||||
script, it might be useful to avoid having to process responses from the
|
||||
terminal. For this, you can use the ``q`` key. Set it to ``1`` to suppress
|
||||
``OK`` responses and to ``2`` to suppress failure responses.
|
||||
|
||||
.. note:: This feature was implemented in kitty in versions after 0.19.2
|
||||
|
||||
|
||||
Image persistence and storage quotas
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -451,6 +463,9 @@ Key Value Default Description
|
||||
======= ==================== ========= =================
|
||||
``a`` Single character. ``t`` The overall action this graphics command is performing.
|
||||
``(t, T, q, p, d)``
|
||||
|
||||
``q`` ``0, 1, 2`` ``0`` Suppress responses from the terminal to this graphics command.
|
||||
|
||||
**Keys for image transmission**
|
||||
-----------------------------------------------------------
|
||||
``f`` Positive integer. ``32`` The format in which the image data is sent.
|
||||
|
||||
@ -258,6 +258,7 @@ def graphics_parser() -> None:
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('id', 'uint'),
|
||||
'p': ('placement_id', 'uint'),
|
||||
'q': ('quiet', 'uint'),
|
||||
'w': ('width', 'uint'),
|
||||
'h': ('height', 'uint'),
|
||||
'x': ('x_offset', 'uint'),
|
||||
|
||||
@ -137,22 +137,20 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current
|
||||
if (!self->image_count) self->used_storage = 0; // sanity check
|
||||
}
|
||||
|
||||
static char add_response[512] = {0};
|
||||
static bool has_add_respose = false;
|
||||
static char command_response[512] = {0};
|
||||
|
||||
static inline void
|
||||
set_add_response(const char *code, const char *fmt, ...) {
|
||||
set_command_failed_response(const char *code, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
size_t sz = sizeof(add_response)/sizeof(add_response[0]);
|
||||
int num = snprintf(add_response, sz, "%s:", code);
|
||||
vsnprintf(add_response + num, sz - num, fmt, args);
|
||||
const size_t sz = sizeof(command_response)/sizeof(command_response[0]);
|
||||
const int num = snprintf(command_response, sz, "%s:", code);
|
||||
vsnprintf(command_response + num, sz - num, fmt, args);
|
||||
va_end(args);
|
||||
has_add_respose = true;
|
||||
}
|
||||
|
||||
// Decode formats {{{
|
||||
#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); goto err; }
|
||||
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; }
|
||||
|
||||
static inline bool
|
||||
mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t offset) {
|
||||
@ -220,7 +218,7 @@ err:
|
||||
|
||||
static void
|
||||
png_error_handler(const char *code, const char *msg) {
|
||||
set_add_response(code, "%s", msg);
|
||||
set_command_failed_response(code, "%s", msg);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@ -311,9 +309,8 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
|
||||
|
||||
static Image*
|
||||
handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
|
||||
#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); self->loading_image = 0; if (img) img->data_loaded = false; return NULL; }
|
||||
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); self->loading_image = 0; if (img) img->data_loaded = false; return NULL; }
|
||||
#define MAX_DATA_SZ (4u * 100000000u)
|
||||
has_add_respose = false;
|
||||
bool existing, init_img = true;
|
||||
Image *img = NULL;
|
||||
unsigned char tt = g->transmission_type ? g->transmission_type : 'd';
|
||||
@ -475,15 +472,19 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
create_add_response(bool data_loaded, uint32_t iid, uint32_t placement_id) {
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0]) + 128];
|
||||
if (iid) {
|
||||
if (!has_add_respose) {
|
||||
if (!data_loaded) return NULL;
|
||||
snprintf(add_response, 10, "OK");
|
||||
finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid, uint32_t placement_id) {
|
||||
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
|
||||
bool is_ok_response = !command_response[0];
|
||||
if (g->quiet) {
|
||||
if (is_ok_response || g->quiet > 1) return NULL;
|
||||
}
|
||||
if (placement_id) snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u,p=%u;%s", iid, placement_id, add_response);
|
||||
else snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, add_response);
|
||||
if (iid) {
|
||||
if (is_ok_response) {
|
||||
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);
|
||||
return rbuf;
|
||||
}
|
||||
return NULL;
|
||||
@ -522,10 +523,9 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelS
|
||||
|
||||
static void
|
||||
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
|
||||
has_add_respose = false;
|
||||
if (img == NULL) img = img_by_client_id(self, g->id);
|
||||
if (img == NULL) { set_add_response("ENOENT", "Put command refers to non-existent image with id: %u", g->id); return; }
|
||||
if (!img->data_loaded) { set_add_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return; }
|
||||
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; }
|
||||
ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
|
||||
*is_dirty = true;
|
||||
self->layers_dirty = true;
|
||||
@ -837,6 +837,7 @@ grman_rescale(GraphicsManager *self, CellPixelSize cell) {
|
||||
const char*
|
||||
grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
||||
const char *ret = NULL;
|
||||
command_response[0] = 0;
|
||||
|
||||
switch(g->action) {
|
||||
case 0:
|
||||
@ -847,8 +848,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 = create_add_response(image != NULL, q_iid, 0);
|
||||
else ret = create_add_response(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);
|
||||
else ret = finish_command_response(g, image != NULL, self->last_init_graphics_command.id, self->last_init_graphics_command.placement_id);
|
||||
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);
|
||||
@ -861,7 +862,7 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
break;
|
||||
}
|
||||
handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||
ret = create_add_response(true, g->id, g->placement_id);
|
||||
ret = finish_command_response(g, true, g->id, g->placement_id);
|
||||
break;
|
||||
case 'd':
|
||||
handle_delete_command(self, g, c, is_dirty, cell);
|
||||
|
||||
@ -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;
|
||||
uint32_t format, more, id, 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;
|
||||
|
||||
56
kitty/parse-graphics-command.h
generated
56
kitty/parse-graphics-command.h
generated
@ -1,4 +1,5 @@
|
||||
// This file is generated by ./gen-apc-parsers.py do not edit!
|
||||
// This file is generated by /home/kovid/work/kitty/./gen-apc-parsers.py do not
|
||||
// edit!
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -24,6 +25,7 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
more = 'm',
|
||||
id = 'i',
|
||||
placement_id = 'p',
|
||||
quiet = 'q',
|
||||
width = 'w',
|
||||
height = 'h',
|
||||
x_offset = 'x',
|
||||
@ -73,6 +75,9 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
case placement_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case quiet:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case width:
|
||||
value_state = UINT;
|
||||
break;
|
||||
@ -135,8 +140,8 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case action: {
|
||||
g.action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.action != 'p' && g.action != 'q' && g.action != 't' &&
|
||||
g.action != 'T' && g.action != 'd') {
|
||||
if (g.action != 'q' && g.action != 'd' && g.action != 'p' &&
|
||||
g.action != 'T' && g.action != 't') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for action: 0x%x",
|
||||
g.action);
|
||||
@ -146,14 +151,14 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case delete_action: {
|
||||
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.delete_action != 'p' && g.delete_action != 'q' &&
|
||||
g.delete_action != 'Z' && g.delete_action != 'x' &&
|
||||
g.delete_action != 'Q' && g.delete_action != 'C' &&
|
||||
g.delete_action != 'y' && g.delete_action != 'Y' &&
|
||||
g.delete_action != 'i' && g.delete_action != 'I' &&
|
||||
g.delete_action != 'a' && g.delete_action != 'z' &&
|
||||
g.delete_action != 'A' && g.delete_action != 'X' &&
|
||||
g.delete_action != 'P' && g.delete_action != 'c') {
|
||||
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') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for delete_action: 0x%x",
|
||||
g.delete_action);
|
||||
@ -163,8 +168,8 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case transmission_type: {
|
||||
g.transmission_type = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.transmission_type != 'd' && g.transmission_type != 'f' &&
|
||||
g.transmission_type != 't' && g.transmission_type != 's') {
|
||||
if (g.transmission_type != 'f' && g.transmission_type != 's' &&
|
||||
g.transmission_type != 't' && g.transmission_type != 'd') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for transmission_type: 0x%x",
|
||||
g.transmission_type);
|
||||
@ -238,6 +243,7 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
U(more);
|
||||
U(id);
|
||||
U(placement_id);
|
||||
U(quiet);
|
||||
U(width);
|
||||
U(height);
|
||||
U(x_offset);
|
||||
@ -308,21 +314,21 @@ 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} "
|
||||
"y#",
|
||||
"s {sc sc sc sc 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, "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, "data_width",
|
||||
(unsigned int)g.data_width, "data_sz", (unsigned int)g.data_sz,
|
||||
"data_offset", (unsigned int)g.data_offset, "num_cells",
|
||||
(unsigned int)g.num_cells, "num_lines", (unsigned int)g.num_lines,
|
||||
"cell_x_offset", (unsigned int)g.cell_x_offset, "cell_y_offset",
|
||||
(unsigned int)g.cell_y_offset, "z_index", (int)g.z_index, "payload_sz",
|
||||
g.payload_sz, payload, g.payload_sz);
|
||||
(unsigned int)g.id, "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,
|
||||
"data_width", (unsigned int)g.data_width, "data_sz",
|
||||
(unsigned int)g.data_sz, "data_offset", (unsigned int)g.data_offset,
|
||||
"num_cells", (unsigned int)g.num_cells, "num_lines",
|
||||
(unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset,
|
||||
"cell_y_offset", (unsigned int)g.cell_y_offset, "z_index", (int)g.z_index,
|
||||
"payload_sz", g.payload_sz, payload, g.payload_sz);
|
||||
|
||||
screen_handle_graphics_command(screen, &g, payload);
|
||||
}
|
||||
|
||||
@ -135,6 +135,7 @@ class TestGraphics(BaseTest):
|
||||
|
||||
# Test load query
|
||||
self.ae(l('abcd', s=1, v=1, a='q'), 'OK')
|
||||
self.assertIsNone(l('abcd', s=1, v=1, a='q', q=1))
|
||||
self.ae(g.image_count, 0)
|
||||
|
||||
# Test simple load
|
||||
@ -150,6 +151,8 @@ class TestGraphics(BaseTest):
|
||||
self.ae(l('mnop', m=0), 'OK')
|
||||
img = g.image_for_client_id(1)
|
||||
self.ae(img['data'], b'abcdefghijklmnop')
|
||||
self.ae(l('abcd', s=10, v=10, q=1), 'ENODATA:Insufficient image data: 4 < 400')
|
||||
self.ae(l('abcd', s=10, v=10, q=2), None)
|
||||
|
||||
# Test compression
|
||||
random_data = byte_block(3 * 1024)
|
||||
|
||||
@ -379,7 +379,7 @@ class TestParser(BaseTest):
|
||||
for f in 'action delete_action transmission_type compressed'.split():
|
||||
k.setdefault(f, b'\0')
|
||||
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width'
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id').split():
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id quiet').split():
|
||||
k.setdefault(f, 0)
|
||||
p = k.pop('payload', '').encode('utf-8')
|
||||
k['payload_sz'] = len(p)
|
||||
@ -400,7 +400,7 @@ class TestParser(BaseTest):
|
||||
pb('\033_Gi=12\033\\', c(id=12))
|
||||
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1)
|
||||
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7)
|
||||
t('a=t,t=d,s=100,z=9', action='t', transmission_type='d', data_width=100, z_index=9)
|
||||
t('a=t,t=d,s=100,z=9,q=2', action='t', transmission_type='d', data_width=100, z_index=9, quiet=2)
|
||||
e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c')
|
||||
e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
|
||||
e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user