diff --git a/docs/changelog.rst b/docs/changelog.rst index 4f62baf45..fd3c16884 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,8 @@ To update |kitty|, :doc:`follow the instructions `. 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`) diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index 67b14dec1..0824ffcee 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -430,6 +430,18 @@ Some examples:: _Ga=d,d=Z,z=-1\ # delete the placements with z-index -1, also freeing up image data _Ga=d,d=p,x=3,y=4\ # 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. diff --git a/gen-apc-parsers.py b/gen-apc-parsers.py index 0cc3db9d0..3e42d9e88 100755 --- a/gen-apc-parsers.py +++ b/gen-apc-parsers.py @@ -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'), diff --git a/kitty/graphics.c b/kitty/graphics.c index 84fef712b..05f4144e9 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -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]; +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 (iid) { - if (!has_add_respose) { + if (is_ok_response) { if (!data_loaded) return NULL; - snprintf(add_response, 10, "OK"); + snprintf(command_response, 10, "OK"); } - 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 (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); diff --git a/kitty/graphics.h b/kitty/graphics.h index ff0f8600a..16efa5f30 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -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; diff --git a/kitty/parse-graphics-command.h b/kitty/parse-graphics-command.h index ed2f49f0b..5418b157e 100644 --- a/kitty/parse-graphics-command.h +++ b/kitty/parse-graphics-command.h @@ -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); } diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index a91025e21..a198aa8f5 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -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) diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 34178a2a4..cd73f574d 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -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')