mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-19 15:08:09 +02:00
Compare commits
9 Commits
copilot/re
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42d3cb2148 | ||
|
|
051a5b72e0 | ||
|
|
d994a47fc8 | ||
|
|
a2104d3755 | ||
|
|
9dfe10b682 | ||
|
|
6202fb7847 | ||
|
|
89946ebc07 | ||
|
|
946867bf57 | ||
|
|
cc2d7a1789 |
@@ -176,8 +176,13 @@ Detailed list of changes
|
||||
0.50.0 [future]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Graphics protocol: Add a new :ref:`transient usage hint <image_usage_hints>` that clients can send to terminals to indicate an image is meant for only short duration use (:pull:`10092`)
|
||||
|
||||
- kitten @ get-text: Add support for :code:`alternate` and :code:`alternate_scrollback` extents to fetch text from the alternate screen buffer (:iss:`10165`)
|
||||
|
||||
- Wayland: Fix first OS window being a few cells too small when ``initial_window_width/initial_window_height`` are set in cells and a fractional display scale is in use (:iss:`10146`)
|
||||
|
||||
|
||||
0.47.4 [2026-06-15]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -834,6 +834,27 @@ use the ``i`` key with the image id for all future communication.
|
||||
The ability to use image numbers (see :doc:`kittens/query_terminal` to query kitty version)
|
||||
|
||||
|
||||
.. _image_usage_hints:
|
||||
|
||||
Usage hints
|
||||
-----------------------------
|
||||
|
||||
Clients can specify *usage hints* when creating images using the ``N`` key.
|
||||
These hints allow the terminal to optimise resource consumption such as caching
|
||||
strategies. The value of ``N`` is a bitmask.
|
||||
|
||||
Currently the only usage hint defined is ``transient (N == 1)``.
|
||||
The terminal is free to assume that an image with this hint
|
||||
will be used for only a short time, and so may, for example, evict its
|
||||
data before other images when the image is soft deleted, has no visible
|
||||
placements and the terminal is under storage pressure, or skip writing
|
||||
its data to disk. The terminal is also free to ignore the hint. If an
|
||||
animation frame with the *transient* hint is composited onto another
|
||||
frame, and any of the involved frames have the hint, the resulting
|
||||
composited frame also has the hint. This hint must be specified when the
|
||||
image or frame data is transmitted. It has no effect on placement commands.
|
||||
|
||||
|
||||
.. _animation_protocol:
|
||||
|
||||
Animation
|
||||
@@ -1051,7 +1072,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`` bitmask ``0`` Usage hints from the client to the terminal about the intended use of
|
||||
the image.
|
||||
**Keys for image display**
|
||||
-----------------------------------------------------------
|
||||
``x`` Positive integer ``0`` The left edge (in pixels) of the image area to display
|
||||
|
||||
@@ -313,6 +313,7 @@ def parsers() -> None:
|
||||
'U': ('unicode_placement', 'uint'),
|
||||
'P': ('parent_id', 'uint'),
|
||||
'Q': ('parent_placement_id', 'uint'),
|
||||
'N': ('usage_hints', 'uint'),
|
||||
'H': ('offset_from_parent_x', 'int'),
|
||||
'V': ('offset_from_parent_y', 'int'),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
15
kitty/glfw.c
15
kitty/glfw.c
@@ -1850,8 +1850,21 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
|
||||
// this can happen if the window is moved by the OS to a different monitor when shown or with fractional scales on Wayland
|
||||
// it can also happen with layer shell windows if the callback is
|
||||
// called before the window is fully created
|
||||
xdpi = n_xdpi; ydpi = n_ydpi;
|
||||
xdpi = n_xdpi; ydpi = n_ydpi; xscale = n_xscale; yscale = n_yscale;
|
||||
fonts_data = load_fonts_data(OPT(font_size), xdpi, ydpi);
|
||||
// Re-compute the window size with the updated scale/font metrics. This matters when
|
||||
// the initial size is specified in cells: the first window on Wayland is created with
|
||||
// scale=1 because fractional scale is only sent by the compositor after the surface
|
||||
// exists, so the cell dimensions used for the initial size calculation were wrong.
|
||||
PyObject *new_size = PyObject_CallFunction(get_window_size, "IIddff", fonts_data->fcm.cell_width, fonts_data->fcm.cell_height, fonts_data->logical_dpi_x, fonts_data->logical_dpi_y, xscale, yscale);
|
||||
if (new_size != NULL) {
|
||||
int new_width = PyLong_AsLong(PyTuple_GET_ITEM(new_size, 0)), new_height = PyLong_AsLong(PyTuple_GET_ITEM(new_size, 1));
|
||||
Py_DECREF(new_size);
|
||||
if (!PyErr_Occurred() && (new_width != width || new_height != height)) {
|
||||
glfwSetWindowSize(glfw_window, new_width, new_height);
|
||||
width = new_width; height = new_height;
|
||||
} else PyErr_Clear();
|
||||
} else PyErr_Clear();
|
||||
}
|
||||
}
|
||||
if (is_first_window) {
|
||||
|
||||
@@ -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
|
||||
@@ -287,9 +287,12 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current
|
||||
if (!sorted) fatal("Out of memory");
|
||||
Image **p = sorted;
|
||||
iter_images(self) { *p++ = i.data->val; }
|
||||
#define oldest_img_first(a, b) ((*a)->atime < (*b)->atime)
|
||||
QSORT(Image*, sorted, num_images, oldest_img_first);
|
||||
#undef oldest_img_first
|
||||
#define transient_or_older_first(a, b) ( \
|
||||
(*a)->root_frame.transient != (*b)->root_frame.transient ? \
|
||||
(*a)->root_frame.transient > (*b)->root_frame.transient : \
|
||||
(*a)->atime < (*b)->atime)
|
||||
QSORT(Image*, sorted, num_images, transient_or_older_first);
|
||||
#undef transient_or_older_first
|
||||
|
||||
for (p = sorted; self->used_storage > storage_limit && num_images; p++, num_images--) remove_image(self, *p);
|
||||
if (!num_images || !vt_size(&self->images_by_internal_id)) self->used_storage = 0; // sanity check
|
||||
@@ -768,11 +771,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,
|
||||
.transient = (g->usage_hints & GRAPHICS_USAGE_HINT_TRANSIENT) != 0,
|
||||
};
|
||||
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.transient)) {
|
||||
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;
|
||||
@@ -1354,7 +1358,7 @@ change_gap(Image *img, Frame *f, int32_t gap) {
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
bool is_4byte_aligned, is_opaque;
|
||||
bool is_4byte_aligned, is_opaque, transient;
|
||||
} CoalescedFrameData;
|
||||
|
||||
static void
|
||||
@@ -1450,6 +1454,7 @@ compose(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) {
|
||||
static CoalescedFrameData
|
||||
get_coalesced_frame_data_standalone(const Image *img, const Frame *f, uint8_t *frame_data) {
|
||||
CoalescedFrameData ans = {0};
|
||||
ans.transient = f->transient;
|
||||
bool is_full_frame = f->width == img->width && f->height == img->height && !f->x && !f->y;
|
||||
if (is_full_frame) {
|
||||
ans.buf = frame_data;
|
||||
@@ -1513,6 +1518,7 @@ get_coalesced_frame_data_impl(GraphicsManager *self, Image *img, const Frame *f,
|
||||
};
|
||||
compose(d, base_data.buf, frame_data);
|
||||
free(frame_data);
|
||||
base_data.transient = base_data.transient || f->transient;
|
||||
return base_data;
|
||||
}
|
||||
|
||||
@@ -1553,6 +1559,17 @@ reference_chain_too_large(Image *img, const Frame *frame) {
|
||||
return num >= 5 || drawn_area >= limit;
|
||||
}
|
||||
|
||||
static bool
|
||||
frame_chain_is_transient(Image *img, const Frame *frame) {
|
||||
// matches the recursion depth limit in get_coalesced_frame_data_impl
|
||||
unsigned num = 0;
|
||||
while (frame) {
|
||||
if (frame->transient) return true;
|
||||
if (!frame->base_frame_id || ++num > 32 || !(frame = frame_for_id(img, frame->base_frame_id))) break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Image*
|
||||
handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) {
|
||||
uint32_t frame_number = g->frame_number, fmt = g->format ? g->format : RGBA;
|
||||
@@ -1595,6 +1612,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,
|
||||
.transient = (g->usage_hints & GRAPHICS_USAGE_HINT_TRANSIENT) != 0,
|
||||
};
|
||||
Frame *frame;
|
||||
if (is_new_frame) {
|
||||
@@ -1630,12 +1648,14 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
|
||||
transmitted_frame.x = 0; transmitted_frame.y = 0;
|
||||
transmitted_frame.is_4byte_aligned = cfd.is_4byte_aligned;
|
||||
transmitted_frame.is_opaque = cfd.is_opaque;
|
||||
transmitted_frame.transient = transmitted_frame.transient || cfd.transient;
|
||||
} else {
|
||||
transmitted_frame.base_frame_id = other_frame->id;
|
||||
transmitted_frame.transient = transmitted_frame.transient || frame_chain_is_transient(img, other_frame);
|
||||
}
|
||||
}
|
||||
*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->transient)) {
|
||||
img->extra_framecnt--;
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
ABRT("ENOSPC", "Failed to cache data for image frame");
|
||||
@@ -1651,6 +1671,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->transient = cfd.transient || transmitted_frame.transient;
|
||||
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 +1685,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->transient);
|
||||
if (added && frame == current_frame(img)) {
|
||||
update_current_frame(self, img, &cfd);
|
||||
*is_dirty = true;
|
||||
@@ -1867,10 +1888,13 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm
|
||||
.stride = img->width
|
||||
};
|
||||
compose_rectangles(d, dest_data.buf, src_data.buf);
|
||||
bool transient = src_data.transient || dest_data.transient;
|
||||
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, transient)) {
|
||||
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");
|
||||
} else {
|
||||
dest_frame->transient = transient;
|
||||
}
|
||||
// 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;
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
#include "data-types.h"
|
||||
#include "monotonic.h"
|
||||
|
||||
// Bitmask values for GraphicsCommand.usage_hints
|
||||
#define GRAPHICS_USAGE_HINT_TRANSIENT 1u
|
||||
|
||||
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, usage_hints;
|
||||
uint32_t width, height, x_offset, y_offset;
|
||||
union { uint32_t cursor_movement, compose_mode; };
|
||||
union { uint32_t cell_x_offset; };
|
||||
@@ -71,7 +74,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, transient;
|
||||
} Frame;
|
||||
|
||||
typedef enum { ANIMATION_STOPPED = 0, ANIMATION_LOADING = 1, ANIMATION_RUNNING = 2} AnimationState;
|
||||
|
||||
10
kitty/parse-graphics-command.h
generated
10
kitty/parse-graphics-command.h
generated
@@ -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',
|
||||
usage_hints = '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 usage_hints:
|
||||
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(usage_hints);
|
||||
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",
|
||||
@@ -378,7 +383,8 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
|
||||
"cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement",
|
||||
(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,
|
||||
"parent_placement_id", (unsigned int)g.parent_placement_id, "usage_hints",
|
||||
(unsigned int)g.usage_hints,
|
||||
|
||||
"z_index", (int)g.z_index, "offset_from_parent_x",
|
||||
(int)g.offset_from_parent_x, "offset_from_parent_y",
|
||||
|
||||
@@ -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_transient_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)
|
||||
@@ -1305,6 +1319,25 @@ class TestGraphics(BaseTest):
|
||||
self.ae(g.image_count, 0)
|
||||
self.assertEqual(g.disk_cache.total_size, 0)
|
||||
|
||||
def test_transient_image_preferential_eviction(self):
|
||||
# Transient images should be evicted before non-transient ones when
|
||||
# the storage quota is exceeded, regardless of insertion order.
|
||||
s = self.create_screen()
|
||||
g = s.grman
|
||||
g.storage_limit = 36 * 2
|
||||
li = make_send_command(s)
|
||||
# Load a non-transient image first (older atime) and a transient image second.
|
||||
self.assertEqual(li(a='T', i=1).code, 'OK')
|
||||
self.assertEqual(li(a='T', i=2, N=1).code, 'OK')
|
||||
self.assertEqual(g.image_count, 2)
|
||||
# Adding a third image triggers the quota; the transient image (i=2) must be
|
||||
# evicted first even though the non-transient image (i=1) is older.
|
||||
self.assertEqual(li(a='T', i=3).code, 'OK')
|
||||
self.assertEqual(g.image_count, 2)
|
||||
self.assertIsNone(g.image_for_client_id(2), 'transient image should have been evicted')
|
||||
self.assertIsNotNone(g.image_for_client_id(1), 'non-transient image should survive')
|
||||
self.assertIsNotNone(g.image_for_client_id(3), 'newly added image should survive')
|
||||
|
||||
@unittest.skipIf(Image is None, 'PIL not available, skipping PNG tests')
|
||||
def test_cached_rgba_conversion(self):
|
||||
from kitty.render_cache import ImageRenderCacheForTesting
|
||||
|
||||
@@ -920,7 +920,7 @@ class TestParser(BaseTest):
|
||||
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 cursor_movement'
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement'
|
||||
' parent_id parent_placement_id offset_from_parent_x offset_from_parent_y'
|
||||
' parent_id parent_placement_id usage_hints offset_from_parent_x offset_from_parent_y'
|
||||
).split():
|
||||
k.setdefault(f, 0)
|
||||
p = k.pop('payload', '')
|
||||
@@ -943,6 +943,7 @@ class TestParser(BaseTest):
|
||||
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9)
|
||||
t('a=t,t=d,s=100,z=9', payload='payload', 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)
|
||||
t('N=1', usage_hints=1)
|
||||
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')
|
||||
|
||||
@@ -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,15 @@ func (self *GraphicsCommand) SetFrameToMakeCurrent(c uint64) *GraphicsCommand {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GraphicsCommand) UsageHints() uint64 {
|
||||
return self.N
|
||||
}
|
||||
|
||||
func (self *GraphicsCommand) SetUsageHints(hints uint64) *GraphicsCommand {
|
||||
self.N = hints
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GraphicsCommand) ImageId() uint32 {
|
||||
return self.i
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user