From 747f7b076ddf46c8ee4ad04fe4249d8aba08236d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 8 Oct 2017 22:51:00 +0530 Subject: [PATCH] Implement control code for deleting images --- graphics-protocol.asciidoc | 43 ++++++++++++++++++++- kitty/graphics.c | 78 +++++++++++++++++++++++++++++++++++--- kitty/graphics.h | 4 +- kitty/parser.c | 9 +++-- kitty_tests/graphics.py | 34 +++++++++++++++++ kitty_tests/parser.py | 2 +- 6 files changed, 156 insertions(+), 14 deletions(-) diff --git a/graphics-protocol.asciidoc b/graphics-protocol.asciidoc index 865089503..96199be6e 100644 --- a/graphics-protocol.asciidoc +++ b/graphics-protocol.asciidoc @@ -256,6 +256,40 @@ they are semi-transparent. You can specify z-index values using the `z` key. Negative z-index values mean that the images will be drawn under the text. This allows rendering of text on top of images. +== Deleting images + +Images can be deleted by using the delete action `a=d`. If specified without any +other keys, it will delete all images visible on screen. To delete specific images, +use the `d` key as described in the table below. Note that each value of d has +both a lowercase and an uppercase variant. The lowercase variant only deletes the +images without necessarily freeing up the stored image data, so that the images can be +re-displayed without needing to resend the data. The uppercase variants will delete +the image data as well, provided that the image is not referenced elsewhere, such as in the +scrollback buffer. The values of the `x` and `y` keys are the same as cursor positions (i.e. +x=1, y=1 is the top left cell). + +|=== +| Value of `d` | Meaning + +| `a` or `A` | Delete all images visible on screen +| `i` or `I` | Delete all images with the specified id, specified using the `i` key. +| `c` or `C` | Delete all images that intersect with the current cursor position. +| `p` or `P` | Delete all images that intersect a specific cell, the cell is specified using the `x` and `y` keys +| `q` or `Q` | Delete all images that intersect a specific cell having a specific z-index. The cell and z-index is specified using the `x`, `y` and `z` keys. +| `x` or `X` | Delete all images that intersect the specified column, specified using the `x` key. +| `y` or `Y` | Delete all images that intersect the specified row, specified using the `y` key. +| `z` or `Z` | Delete all images that have the specified z-index, specified using the `z` key. + +Some examples: + +``` +_Ga=d\ # delete all visible images +_Ga=d,i=10\ # delete the image with id=10 +_Ga=Z,z=-1\ # delete the images with z-index -1, also freeing up image data +_Ga=P,x=3,y=4\ # delete all images that intersect the cell at (3, 4) +``` +|=== + == Control data reference The table below shows all the control data keys as well as what values they can @@ -265,7 +299,7 @@ take, and the default value they take when missing. All integers are 32-bit. |=== |Key | Value | Default | Description -| `a` | Single character. `(t, T, q, p)` | `t` | The overall action this graphics command is performing. +| `a` | Single character. `(t, T, q, p, d)` | `t` | The overall action this graphics command is performing. 4+^.^h| Keys for image transmission @@ -290,6 +324,11 @@ take, and the default value they take when missing. All integers are 32-bit. | `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 | `z` | Integer | `0` | The *z-index* vertical stacking order of the image + +4+^.^h| Keys for deleting images + +| `d` | Single character. `(a, A, c, C, p, P, q, Q, x, X, y, Y, z, Z)`. | `a` | What to delete. + |=== @@ -298,7 +337,7 @@ take, and the default value they take when missing. All integers are 32-bit. When resetting the terminal, all images that are visible on the screen must be cleared. When switching from the main screen to the alternate screen buffer (1049 private mode) all images in the alternate screen must be cleared, just as -all text is cleared. The clear command (usually `[H[2J`) should also +all text is cleared. The clear screen escape code (usually `[2J`) should also clear all images. This is so that the clear command works. The other commands to erase text must have no effect on graphics. diff --git a/kitty/graphics.c b/kitty/graphics.c index e3e379a83..f4f092a8e 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -561,6 +561,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b } c->x += num_cols; c->y += num_rows - 1; ref->effective_num_rows = num_rows; + ref->effective_num_cols = num_cols; } static int @@ -630,7 +631,7 @@ 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 (*filter_func)(ImageRef*, Image*, const void*)) { +filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*)) { Image *img; ImageRef *ref; size_t i, j; @@ -643,7 +644,7 @@ filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--); } } - if (img->refcnt == 0) { + if (img->refcnt == 0 && (free_images || img->client_id == 0)) { free_image(img); remove_from_array(self->images, sizeof(Image), i, self->image_count--); } @@ -702,7 +703,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { void grman_scroll_images(GraphicsManager *self, const ScrollData *data) { - filter_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func); + filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func); } static inline bool @@ -712,7 +713,71 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) { void grman_clear(GraphicsManager *self) { - filter_refs(self, NULL, clear_filter_func); + filter_refs(self, NULL, true, clear_filter_func); +} + +static inline bool +id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data) { + uint32_t iid = *(uint32_t*)data; + return img->client_id == iid; +} + +static inline bool +x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { + const GraphicsCommand *g = data; + return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols)); +} + +static inline bool +y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { + const GraphicsCommand *g = data; + return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows)); +} + +static inline bool +z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { + const GraphicsCommand *g = data; + return ref->z_index == g->z_index; +} + + +static inline bool +point_filter_func(ImageRef *ref, Image *img, const void *data) { + return x_filter_func(ref, img, data) && y_filter_func(ref, img, data); +} + +static inline bool +point3d_filter_func(ImageRef *ref, Image *img, const void *data) { + return z_filter_func(ref, img, data) && point_filter_func(ref, img, data); +} + + +static void +handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty) { + static GraphicsCommand d; + switch (g->delete_action) { +#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func); *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: + D('a', 'A', NULL, clear_filter_func); + D('i', 'I', &g->id, id_filter_func); + G('p', 'P', point_filter_func); + G('q', 'Q', point3d_filter_func); + G('x', 'X', x_filter_func); + G('y', 'Y', y_filter_func); + G('z', 'Z', z_filter_func); + case 'c': + case 'C': + d.x_offset = c->x + 1; d.y_offset = c->y + 1; + I('C', &d, point_filter_func); + default: + REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action); + break; +#undef G +#undef D +#undef I + } } // }}} @@ -737,12 +802,15 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint break; case 'p': if (!g->id) { - REPORT_ERROR("%s", "Put graphics command without image id"); + REPORT_ERROR("Put graphics command without image id"); break; } handle_put_command(self, g, c, is_dirty, NULL); ret = create_add_response(self, true, g->id); break; + case 'd': + handle_delete_command(self, g, c, is_dirty); + break; default: REPORT_ERROR("Unknown graphics command action: %c", g->action); break; diff --git a/kitty/graphics.h b/kitty/graphics.h index 5717abc38..a86a0efe8 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -8,7 +8,7 @@ #include "data-types.h" typedef struct { - unsigned char action, transmission_type, compressed; + unsigned char action, transmission_type, compressed, delete_action; uint32_t format, more, id, data_sz, data_offset; 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; @@ -34,7 +34,7 @@ typedef struct { typedef struct { uint32_t src_width, src_height, src_x, src_y; - uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows; + uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols; int32_t z_index; int32_t start_row, start_column; ImageRect src_rect; diff --git a/kitty/parser.c b/kitty/parser.c index 5019a5f23..120d5980f 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -540,6 +540,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { enum GR_STATES state = KEY, value_state = FLAG; enum KEYS { action='a', + delete_action='d', transmission_type='t', compressed='o', format = 'f', @@ -577,7 +578,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { switch(key) { #define KS(n, vs) case n: state = EQUAL; value_state = vs; break #define U(x) KS(x, UINT) - KS(action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT); + KS(action, FLAG); KS(delete_action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT); U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); #undef U #undef KS @@ -598,7 +599,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { case FLAG: switch(key) { #define F(a) case a: g.a = screen->parser_buf[pos++] & 0xff; break - F(action); F(transmission_type); F(compressed); + F(action); F(delete_action); F(transmission_type); F(compressed); default: break; } @@ -675,8 +676,8 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { #define A(x) #x, g.x #define U(x) #x, (unsigned int)(g.x) #define I(x) #x, (int)(g.x) - REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", - A(action), A(transmission_type), A(compressed), + 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} y#", "graphics_command", + A(action), A(delete_action), A(transmission_type), A(compressed), U(format), U(more), U(id), U(data_sz), U(data_offset), U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset), U(payload_sz), I(z_index), diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index 5b8d4f642..82da1915c 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -292,3 +292,37 @@ class TestGraphics(BaseTest): s.index() s.reset() self.ae(s.grman.image_count, 1) + + def test_gr_delete(self): + cw, ch = 10, 20 + s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch) + + def delete(ac=None, **kw): + cmd = 'a=d' + if ac: + cmd += ',d={}'.format(ac) + if kw: + cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items()) + send_command(s, cmd) + + put_image(s, cw, ch) + delete() + self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1) + delete('A') + self.ae(s.grman.image_count, 0) + iid = put_image(s, cw, ch)[0] + delete('I', i=iid) + self.ae(s.grman.image_count, 0) + s.reset() + put_image(s, cw, ch) + put_image(s, cw, ch) + delete('C') + self.ae(s.grman.image_count, 2) + s.cursor_position(1, 1) + delete('C') + self.ae(s.grman.image_count, 1) + delete('P', x=2, y=1) + self.ae(s.grman.image_count, 0) + put_image(s, cw, ch, z=9) + delete('Z', z=9) + self.ae(s.grman.image_count, 0) diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 7d7ce6e99..98b00702d 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -202,7 +202,7 @@ class TestParser(BaseTest): for p, v in tuple(k.items()): if isinstance(v, str) and p != 'payload': k[p] = v.encode('ascii') - for f in 'action transmission_type compressed'.split(): + 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').split():