diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index 227e994c1..5b212acb2 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -1051,6 +1051,8 @@ Key Value Default Description ``o`` Single character. ``null`` The type of data compression. ``only z`` ``m`` zero or one ``0`` Whether there is more chunked data available. +``N`` zero or one ``0`` If set to ``1``, keep the transmitted image or frame data in memory only, + without writing it to the graphics disk cache. **Keys for image display** ----------------------------------------------------------- diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 51666f4a7..0d23cca6e 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -313,6 +313,7 @@ def parsers() -> None: 'U': ('unicode_placement', 'uint'), 'P': ('parent_id', 'uint'), 'Q': ('parent_placement_id', 'uint'), + 'N': ('no_disk_cache', 'uint'), 'H': ('offset_from_parent_x', 'int'), 'V': ('offset_from_parent_y', 'int'), } diff --git a/kitty/disk-cache.c b/kitty/disk-cache.c index a32c85835..088ef79f8 100644 --- a/kitty/disk-cache.c +++ b/kitty/disk-cache.c @@ -33,7 +33,7 @@ typedef struct CacheKey { typedef struct { uint8_t *data; size_t data_sz; - bool written_to_disk, uses_encryption; + bool written_to_disk, uses_encryption, memory_only; off_t pos_in_cache_file; uint8_t encryption_key[64]; } CacheValue; @@ -593,7 +593,7 @@ create_cache_entry(void) { } bool -add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *data, size_t data_sz) { +add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *data, size_t data_sz, bool memory_only) { DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return false; if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return false; } @@ -618,11 +618,14 @@ add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *d if (s->data) free(s->data); } s->data = copied_data; s->data_sz = data_sz; copied_data = NULL; + s->memory_only = memory_only; + s->written_to_disk = memory_only; + if (memory_only) s->pos_in_cache_file = -1; self->total_size += s->data_sz; end: mutex(unlock); if (PyErr_Occurred()) return false; - wakeup_write_loop(self); + if (!memory_only) wakeup_write_loop(self); return true; } @@ -740,7 +743,7 @@ disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void*, void *key, unsig mutex(lock); cache_map_for_loop(i) { CacheValue *s = i.data->val; - if (s->written_to_disk && s->data && matches(data, i.data->key.hash_key, i.data->key.hash_keylen)) { + if (s->written_to_disk && !s->memory_only && s->data && matches(data, i.data->key.hash_key, i.data->key.hash_keylen)) { free(s->data); s->data = NULL; ans++; } @@ -848,7 +851,7 @@ add(PyObject *self, PyObject *args) { const char *key, *data; Py_ssize_t keylen, datalen; PA("y#y#", &key, &keylen, &data, &datalen); - if (!add_to_disk_cache(self, key, keylen, data, datalen)) return NULL; + if (!add_to_disk_cache(self, key, keylen, data, datalen, false)) return NULL; Py_RETURN_NONE; } diff --git a/kitty/disk-cache.h b/kitty/disk-cache.h index d6c1ea26f..17538fe8c 100644 --- a/kitty/disk-cache.h +++ b/kitty/disk-cache.h @@ -9,7 +9,7 @@ #include "data-types.h" PyObject* create_disk_cache(void); -bool add_to_disk_cache(PyObject *self, const void *key, size_t key_sz, const void *data, size_t data_sz); +bool add_to_disk_cache(PyObject *self, const void *key, size_t key_sz, const void *data, size_t data_sz, bool memory_only); bool remove_from_disk_cache(PyObject *self_, const void *key, size_t key_sz); void* read_from_disk_cache(PyObject *self_, const void *key, size_t key_sz, void*(allocator)(void*, size_t), void*, bool); PyObject* read_from_disk_cache_python(PyObject *self_, const void *key, size_t key_sz, bool); diff --git a/kitty/graphics.c b/kitty/graphics.c index 7365a0223..b56481e21 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -41,9 +41,9 @@ cache_key(const ImageAndFrame x, char *key) { #define CK(x) key, cache_key(x, key) static bool -add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) { +add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz, bool memory_only) { char key[CACHE_KEY_BUFFER_SIZE]; - return add_to_disk_cache(self->disk_cache, CK(x), data, sz); + return add_to_disk_cache(self->disk_cache, CK(x), data, sz, memory_only); } static bool @@ -768,11 +768,12 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ .is_opaque = self->currently_loading.is_opaque, .is_4byte_aligned = self->currently_loading.is_4byte_aligned, .width = img->width, .height = img->height, + .memory_only = !!g->no_disk_cache, }; if (!is_query) { - if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz)) { + if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz, img->root_frame.memory_only)) { if (PyErr_Occurred()) PyErr_Print(); - ABRT("ENOSPC", "Failed to store image data in disk cache"); + ABRT("ENOSPC", "Failed to store image data in cache"); } upload_to_gpu(self, img, img->root_frame.is_opaque, img->root_frame.is_4byte_aligned, self->currently_loading.data); self->used_storage += required_sz; @@ -1595,6 +1596,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I .alpha_blend = g->compose_mode != 1 && !load_data->is_opaque, .gap = g->gap > 0 ? g->gap : (g->gap < 0) ? 0 : DEFAULT_GAP, .bgcolor = g->bgcolor, + .memory_only = !!g->no_disk_cache, }; Frame *frame; if (is_new_frame) { @@ -1635,7 +1637,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I } } *frame = transmitted_frame; - if (!add_to_cache(self, key, load_data->data, load_data->data_sz)) { + if (!add_to_cache(self, key, load_data->data, load_data->data_sz, frame->memory_only)) { img->extra_framecnt--; if (PyErr_Occurred()) PyErr_Print(); ABRT("ENOSPC", "Failed to cache data for image frame"); @@ -1651,6 +1653,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I if (g->gap != 0) change_gap(img, frame, transmitted_frame.gap); CoalescedFrameData cfd = get_coalesced_frame_data(self, img, frame); if (!cfd.buf) ABRT("EINVAL", "No data associated with frame number: %u", frame_number); + frame->memory_only = transmitted_frame.memory_only; frame->alpha_blend = false; frame->base_frame_id = 0; frame->bgcolor = 0; frame->is_opaque = cfd.is_opaque; frame->is_4byte_aligned = cfd.is_4byte_aligned; frame->x = 0; frame->y = 0; frame->width = img->width; frame->height = img->height; @@ -1664,7 +1667,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I }; compose(d, cfd.buf, load_data->data); const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = frame->id }; - bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height); + bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height, frame->memory_only); if (added && frame == current_frame(img)) { update_current_frame(self, img, &cfd); *is_dirty = true; @@ -1868,9 +1871,9 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm }; compose_rectangles(d, dest_data.buf, src_data.buf); const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = dest_frame->id }; - if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height)) { + if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height, dest_frame->memory_only)) { if (PyErr_Occurred()) PyErr_Print(); - set_command_failed_response("ENOSPC", "Failed to store image data in disk cache"); + set_command_failed_response("ENOSPC", "Failed to store image data in cache"); } // frame is now a fully coalesced frame dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height; diff --git a/kitty/graphics.h b/kitty/graphics.h index 0d348abc1..36d2b303f 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, parent_id, parent_placement_id; + uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, parent_id, parent_placement_id, no_disk_cache; uint32_t width, height, x_offset, y_offset; union { uint32_t cursor_movement, compose_mode; }; union { uint32_t cell_x_offset; }; @@ -71,7 +71,7 @@ typedef struct { typedef struct { uint32_t gap, id, width, height, x, y, base_frame_id, bgcolor; - bool is_opaque, is_4byte_aligned, alpha_blend; + bool is_opaque, is_4byte_aligned, alpha_blend, memory_only; } Frame; typedef enum { ANIMATION_STOPPED = 0, ANIMATION_LOADING = 1, ANIMATION_RUNNING = 2} AnimationState; diff --git a/kitty/parse-graphics-command.h b/kitty/parse-graphics-command.h index 092740f80..0bd92181d 100644 --- a/kitty/parse-graphics-command.h +++ b/kitty/parse-graphics-command.h @@ -46,6 +46,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, unicode_placement = 'U', parent_id = 'P', parent_placement_id = 'Q', + no_disk_cache = 'N', offset_from_parent_x = 'H', offset_from_parent_y = 'V' }; @@ -141,6 +142,9 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, case parent_placement_id: value_state = UINT; break; + case no_disk_cache: + value_state = UINT; + break; case offset_from_parent_x: value_state = INT; break; @@ -299,6 +303,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, U(unicode_placement); U(parent_id); U(parent_placement_id); + U(no_disk_cache); default: break; } @@ -359,7 +364,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, REPORT_VA_COMMAND( "K 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 sI si si si ss#}", + "sI sI sI sI sI si si si ss#}", self->window_id, "graphics_command", "action", g.action, "delete_action", g.delete_action, "transmission_type", @@ -379,6 +384,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, (unsigned int)g.cursor_movement, "unicode_placement", (unsigned int)g.unicode_placement, "parent_id", (unsigned int)g.parent_id, "parent_placement_id", (unsigned int)g.parent_placement_id, + "no_disk_cache", (unsigned int)g.no_disk_cache, "z_index", (int)g.z_index, "offset_from_parent_x", (int)g.offset_from_parent_x, "offset_from_parent_y", diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index eeaafd23b..ce8f1202f 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -386,6 +386,20 @@ class TestGraphics(BaseTest): self.assertIsNone(li(payload='2' * 12, z=77, m=1, q=2)) self.assertIsNone(li(payload='2' * 12)) + def test_no_disk_cache_graphics_image(self): + s, g, pl, sl = load_helpers(self) + self.assertEqual(g.disk_cache.end_of_data_offset(), 0) + self.ae(pl('abc', s=1, v=1, f=24, N=1), 'OK') + self.assertTrue(g.disk_cache.wait_for_write()) + self.assertEqual(g.disk_cache.end_of_data_offset(), 0) + img = g.image_for_client_id(1) + self.assertIsNotNone(img) + self.ae(img['data'], b'abc') + + self.ae(pl('def', s=1, v=1, f=24, i=2), 'OK') + self.assertTrue(g.disk_cache.wait_for_write()) + self.assertGreater(g.disk_cache.end_of_data_offset(), 0) + def test_load_images(self): s, g, pl, sl = load_helpers(self) self.assertEqual(g.disk_cache.total_size, 0) diff --git a/tools/tui/graphics/command.go b/tools/tui/graphics/command.go index fb2e485fa..247d60782 100644 --- a/tools/tui/graphics/command.go +++ b/tools/tui/graphics/command.go @@ -143,7 +143,7 @@ type GraphicsCommand struct { d GRT_d U GRT_U - s, v, S, O, x, y, w, h, X, Y, c, r uint64 + s, v, S, O, x, y, w, h, X, Y, c, r, N uint64 i, I, p uint32 @@ -176,6 +176,7 @@ func (self *GraphicsCommand) serialize_non_default_fields() (ans []string) { write_key('U', self.U, null.U) write_key('d', self.d, null.d) + write_key('N', self.N, null.N) write_key('s', self.s, null.s) write_key('v', self.v, null.v) write_key('S', self.S, null.S) @@ -376,6 +377,8 @@ func (self *GraphicsCommand) SetString(key byte, value string) (err error) { err = set_val(&self.U, GRT_U_from_string, value) case 'd': err = set_val(&self.d, GRT_d_from_string, value) + case 'N': + err = set_uval(&self.N, value) case 's': err = set_uval(&self.s, value) case 'v': @@ -753,6 +756,19 @@ func (self *GraphicsCommand) SetFrameToMakeCurrent(c uint64) *GraphicsCommand { return self } +func (self *GraphicsCommand) NoDiskCache() bool { + return self.N != 0 +} + +func (self *GraphicsCommand) SetNoDiskCache(noDiskCache bool) *GraphicsCommand { + if noDiskCache { + self.N = 1 + } else { + self.N = 0 + } + return self +} + func (self *GraphicsCommand) ImageId() uint32 { return self.i }