diff --git a/docs/changelog.rst b/docs/changelog.rst index 13b1a3f6d..c22197e47 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -102,6 +102,9 @@ To update |kitty|, :doc:`follow the instructions `. - Improve rendering of rounded corners by using a rectircle equation rather than a cubic bezier (:iss:`3409`) +- Graphics protocol: Add a control to allow clients to specify that the cursor + should not move when displaying an image (:iss:`3411`) + 0.19.3 [2020-12-19] ------------------- diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index f8bb826e8..5e3f160a8 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -401,6 +401,12 @@ colors. number of rows in the image placement rectangle. If either of these cause the cursor to leave either the screen or the scroll area, the exact positioning of the cursor is undefined, and up to implementations. + The client can ask the terminal emulator to not move the cursor at all + by specifying ``C=1`` in the command, which sets the cursor movement policy + to no movement for placing the current image. + +.. versionadded:: 0.20.0 + Support for the C=1 cursor movement policy Deleting images @@ -680,6 +686,8 @@ Key Value Default Description ``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image ``c`` Positive integer ``0`` The number of columns to display the image over ``r`` Positive integer ``0`` The number of rows to display the image over +``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor after the image. + ``1`` is to not move the cursor at all when placing images. ``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image **Keys for animation frame loading** diff --git a/gen-apc-parsers.py b/gen-apc-parsers.py index 6e5d6bccb..83b0b6cd5 100755 --- a/gen-apc-parsers.py +++ b/gen-apc-parsers.py @@ -273,6 +273,7 @@ def graphics_parser() -> None: 'X': ('cell_x_offset', 'uint'), 'Y': ('cell_y_offset', 'uint'), 'z': ('z_index', 'int'), + 'C': ('cursor_movement', 'uint'), } text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand') write_header(text, 'kitty/parse-graphics-command.h') diff --git a/kitty/graphics.c b/kitty/graphics.c index b3a9d687a..46694e0a5 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -699,7 +699,9 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b update_src_rect(ref, img); 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; + if (g->cursor_movement != 1) { + c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1; + } return img->client_id; } diff --git a/kitty/graphics.h b/kitty/graphics.h index 852b439a1..7343a80d6 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, image_number, data_sz, data_offset, placement_id, quiet; + uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, cursor_movement; 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 484bb9231..ca64b5f6e 100644 --- a/kitty/parse-graphics-command.h +++ b/kitty/parse-graphics-command.h @@ -39,7 +39,8 @@ static inline void parse_graphics_code(Screen *screen, num_lines = 'r', cell_x_offset = 'X', cell_y_offset = 'Y', - z_index = 'z' + z_index = 'z', + cursor_movement = 'C' }; enum KEYS key = 'a'; @@ -121,6 +122,9 @@ static inline void parse_graphics_code(Screen *screen, case z_index: value_state = INT; break; + case cursor_movement: + value_state = UINT; + break; default: REPORT_ERROR("Malformed GraphicsCommand control block, invalid key " "character: 0x%x", @@ -144,9 +148,9 @@ 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' && g.action != 'f' && - g.action != 'a') { + if (g.action != 't' && g.action != 'a' && g.action != 'T' && + g.action != 'f' && g.action != 'd' && g.action != 'p' && + g.action != 'q') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for action: 0x%x", g.action); @@ -156,16 +160,16 @@ static inline void parse_graphics_code(Screen *screen, case delete_action: { g.delete_action = screen->parser_buf[pos++] & 0xff; - if (g.delete_action != 'F' && g.delete_action != 'n' && - g.delete_action != 'x' && g.delete_action != 'I' && - g.delete_action != 'p' && g.delete_action != 'i' && - g.delete_action != 'C' && g.delete_action != 'Q' && - g.delete_action != 'A' && g.delete_action != 'z' && - g.delete_action != 'y' && g.delete_action != 'c' && - g.delete_action != 'Z' && g.delete_action != 'N' && - g.delete_action != 'X' && g.delete_action != 'q' && - g.delete_action != 'P' && g.delete_action != 'f' && - g.delete_action != 'Y' && g.delete_action != 'a') { + if (g.delete_action != 'Q' && g.delete_action != 'N' && + g.delete_action != 'z' && g.delete_action != 'p' && + g.delete_action != 'X' && g.delete_action != 'a' && + g.delete_action != 'y' && g.delete_action != 'f' && + g.delete_action != 'Z' && g.delete_action != 'F' && + g.delete_action != 'Y' && g.delete_action != 'n' && + g.delete_action != 'C' && g.delete_action != 'q' && + g.delete_action != 'c' && g.delete_action != 'i' && + g.delete_action != 'A' && g.delete_action != 'P' && + g.delete_action != 'I' && g.delete_action != 'x') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for delete_action: 0x%x", g.delete_action); @@ -175,8 +179,8 @@ static inline void parse_graphics_code(Screen *screen, case transmission_type: { g.transmission_type = screen->parser_buf[pos++] & 0xff; - if (g.transmission_type != 's' && g.transmission_type != 'f' && - g.transmission_type != 'd' && g.transmission_type != 't') { + if (g.transmission_type != 's' && g.transmission_type != 't' && + g.transmission_type != 'd' && g.transmission_type != 'f') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for transmission_type: 0x%x", g.transmission_type); @@ -264,6 +268,7 @@ static inline void parse_graphics_code(Screen *screen, U(num_lines); U(cell_x_offset); U(cell_y_offset); + U(cursor_movement); default: break; } @@ -322,8 +327,8 @@ 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 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 " + "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", @@ -336,8 +341,9 @@ static inline void parse_graphics_code(Screen *screen, (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); + "cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement", + (unsigned int)g.cursor_movement, "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 5b258a18c..e39c655a0 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -124,9 +124,15 @@ def put_helpers(self, cw, ch): s = self.create_screen(10, 5, cell_width=cw, cell_height=ch) return s, 2 / s.columns, 2 / s.lines - def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0, placement_id=0): - return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d' % ( - z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, cell_y_off, placement_id) + def put_cmd( + z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, + height=0, cell_x_off=0, cell_y_off=0, placement_id=0, + cursor_movement=0 + ): + return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d,C=%d' % ( + z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, + cell_y_off, placement_id, cursor_movement + ) def put_image(screen, w, h, **kw): nonlocal iid @@ -510,6 +516,8 @@ class TestGraphics(BaseTest): rect_eq(l2[1]['dest_rect'], -1, 1, -1 + dx, 1 - dy) self.ae(l2[0]['group_count'], 1), self.ae(l2[1]['group_count'], 1) self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 1) + self.ae(put_image(s, 10, 20, cursor_movement=1)[1], 'OK') + self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 1) s.reset() self.assertEqual(s.grman.disk_cache.total_size, 0) diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 96bc5d2b5..70474844d 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -381,7 +381,7 @@ class TestParser(BaseTest): k[p] = v.encode('ascii') 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' + for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width cursor_movement' ' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet').split(): k.setdefault(f, 0) p = k.pop('payload', '').encode('utf-8')