Graphics protocol: Add a control to allow clients to specify that the cursor should not move when displaying an image

Fixes #3411
This commit is contained in:
Kovid Goyal 2021-03-22 22:16:40 +05:30
parent ba32e481ca
commit 83bbcf0aa1
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 54 additions and 26 deletions

View File

@ -102,6 +102,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- 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]
-------------------

View File

@ -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**

View File

@ -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')

View File

@ -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
if (g->cursor_movement != 1) {
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
}
return img->client_id;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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)

View File

@ -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')