From 66bce4b8cdab50ac0742f1781ed91cdd4fdebaf9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 27 Sep 2017 13:59:24 +0530 Subject: [PATCH] Code to read image data --- kitty/graphics.c | 153 +++++++++++++++++++++++++++++++++++++++--- kitty/graphics.h | 18 ++++- kitty/parser.c | 5 +- kitty_tests/parser.py | 8 +-- setup.py | 2 +- 5 files changed, 166 insertions(+), 20 deletions(-) diff --git a/kitty/graphics.c b/kitty/graphics.c index 2813824fb..7564d83b8 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -8,8 +8,25 @@ #include "graphics.h" #include "state.h" +#include +#include +#include +#include + #define REPORT_ERROR(fmt, ...) { fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n"); } +static inline bool +mmap_img_file(GraphicsManager UNUSED *self, Image *img) { + off_t file_sz = lseek(img->load_data.fd, 0, SEEK_END); + if (file_sz == -1) { REPORT_ERROR("Failed to seek in image file with error: [%d] %s", errno, strerror(errno)); return false; } + lseek(img->load_data.fd, 0, SEEK_SET); + void *addr = mmap(0, file_sz, PROT_READ, MAP_PRIVATE, img->load_data.fd, 0); + if (addr == MAP_FAILED) { REPORT_ERROR("Failed to map image file with error: [%d] %s", errno, strerror(errno)); return false; } + img->load_data.mapped_file = addr; + img->load_data.mapped_file_sz = file_sz; + return true; +} + GraphicsManager* grman_realloc(GraphicsManager *old, index_type lines, index_type columns) { GraphicsManager *self = (GraphicsManager *)GraphicsManager_Type.tp_alloc(&GraphicsManager_Type, 0); @@ -28,8 +45,20 @@ grman_realloc(GraphicsManager *old, index_type lines, index_type columns) { return self; } +static inline void +free_load_data(LoadData *ld) { + free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; + ld->buf = NULL; + + if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz); + ld->mapped_file = NULL; ld->mapped_file_sz = 0; + if (ld->fd > 0) close(ld->fd); + ld->fd = -1; +} + GraphicsManager* grman_free(GraphicsManager* self) { + for (size_t i = 0; i < self->image_count; i++) free_load_data(&(self->images[i].load_data)); free(self->images); Py_TYPE(self)->tp_free((PyObject*)self); return NULL; @@ -64,17 +93,121 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) { return self->images + self->image_count++; } -static void -handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t UNUSED *payload) { - bool existing; - Image *img = find_or_create_image(self, g->id, &existing); - if (existing) { - free(img->load_buf); img->load_buf = NULL; - } else { - img->internal_id = internal_id_counter++; - img->client_id = g->id; +static inline void +remove_from_array(void *array, size_t item_size, size_t idx, size_t array_count) { + size_t num_to_right = array_count - 1 - idx; + uint8_t *p = (uint8_t*)array; + if (num_to_right > 0) memmove(p + (idx * item_size), p + ((idx + 1) * item_size), num_to_right * item_size); + memset(p + (item_size * (array_count - 1)), 0, item_size); +} + +static inline void +free_image(Image *img) { + img->data_loaded = false; + free_load_data(&(img->load_data)); +} + +static inline void +remove_images(GraphicsManager *self, bool(*predicate)(Image*)) { + for (size_t i = self->image_count; i-- > 0;) { + if (predicate(self->images + i)) { + free_image(self->images + i); + remove_from_array(self->images, sizeof(Image), i, self->image_count--); + } } - img->width = g->data_width; img->height = g->data_height; +} + +static inline Image* +img_by_internal_id(GraphicsManager *self, size_t id) { + for (size_t i = 0; i < self->image_count; i++) { + if (self->images[i].internal_id == id) return self->images + i; + } + return NULL; +} + + +static bool +add_trim_predicate(Image *img) { + return !img->data_loaded || (!img->client_id && !img->refcnt); +} + + +static void +handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload) { + bool existing, init_img = true; + Image *img; + unsigned char tt = g->transmission_type ? g->transmission_type : 'd'; + if (tt == 'd' && (g->more && self->loading_image)) init_img = false; + if (init_img) { + remove_images(self, add_trim_predicate); + img = find_or_create_image(self, g->id, &existing); + if (existing) { + free_load_data(&img->load_data); + img->data_loaded = false; + } else { + img->internal_id = internal_id_counter++; + img->client_id = g->id; + } + img->width = g->data_width; img->height = g->data_height; + size_t sz = img->width * img->height; + switch(g->format) { + case 100: // PNG + sz = sz * 4 + 1024; + break; + case 8: + case 24: + case 32: + sz *= g->format / 8; + break; + default: break; + } + if (g->compressed) sz += 1024; // compression header + img->load_data.max_data_sz = sz + 10; + if (tt == 'd') { + if (g->more) self->loading_image = img->internal_id; + img->load_data.buf = malloc(img->load_data.max_data_sz + 4); + if (img->load_data.buf == NULL) fatal("Out of memory while allocating image load data buffer"); + img->load_data.buf_capacity = img->load_data.max_data_sz; + img->load_data.buf_used = 0; + } + } else { + img = img_by_internal_id(self, self->loading_image); + if (img == NULL) { + self->loading_image = 0; + REPORT_ERROR("%s", "More payload loading refers to non-existent image"); + return; + } + } + int fd; + switch(tt) { + case 'd': // direct + if (g->payload_sz >= img->load_data.buf_capacity - img->load_data.buf_used) { + REPORT_ERROR("%s", "Too much data transmitted"); + return; + } + memcpy(img->load_data.buf + img->load_data.buf_used, payload, g->payload_sz); + img->load_data.buf_used += g->payload_sz; + if (!g->more) { img->data_loaded = true; self->loading_image = 0; } + break; + case 'f': // file + case 't': // temporary file + case 's': // POSIX shared memory + if (tt == 's') fd = shm_open((const char*)payload, O_RDONLY, 0); + else fd = open((const char*)payload, O_CLOEXEC | O_RDONLY); + if (fd == -1) { + REPORT_ERROR("Failed to open file for graphics transmission with error: [%d] %s", errno, strerror(errno)); + return; + } + img->load_data.fd = fd; + img->data_loaded = mmap_img_file(self, img); + if (tt == 't') unlink((const char*)payload); + else if (tt == 's') shm_unlink((const char*)payload); + break; + default: + REPORT_ERROR("Unknown transmission type: %c", g->transmission_type); + return; + } + } void diff --git a/kitty/graphics.h b/kitty/graphics.h index 208a9ee4a..05744e3d7 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -8,18 +8,30 @@ #include "data-types.h" typedef struct { - unsigned char action, transmission_type; + unsigned char action, transmission_type, compressed; uint32_t format, more, id; uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines; int32_t z_index; size_t payload_sz; } GraphicsCommand; +typedef struct { + uint8_t *buf; + size_t buf_capacity, buf_used; + + int fd; + uint8_t *mapped_file; + size_t mapped_file_sz; + + size_t max_data_sz; +} LoadData; typedef struct { uint32_t gl_id, client_id, width, height; size_t internal_id, refcnt; - uint8_t *load_buf; + + bool data_loaded; + LoadData load_data; } Image; @@ -27,7 +39,7 @@ typedef struct { PyObject_HEAD index_type lines, columns; - size_t image_count, images_capacity; + size_t image_count, images_capacity, loading_image; Image *images; } GraphicsManager; PyTypeObject GraphicsManager_Type; diff --git a/kitty/parser.c b/kitty/parser.c index fad1dbd78..137d86ea3 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -541,6 +541,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { enum KEYS { action='a', transmission_type='t', + compressed='o', format = 'f', more = 'm', id = 'i', @@ -671,8 +672,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 sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", - A(action), A(transmission_type), + REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", + A(action), A(transmission_type), A(compressed), U(format), U(more), U(id), U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(payload_sz), I(z_index), diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 2086fa479..54c18514f 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'.split(): + for f in 'action transmission_type compressed'.split(): k.setdefault(f, b'\0') for f in 'format more id width height x_offset y_offset data_height data_width num_cells num_lines z_index'.split(): k.setdefault(f, 0) @@ -219,9 +219,9 @@ class TestParser(BaseTest): s = self.create_screen() pb = partial(self.parse_bytes_dump, s) pb('\033_Gi=12\033\\', c(id=12)) - t('a=t,t=f,s=100,z=-9', payload='X', action='t', transmission_type='f', data_width=100, z_index=-9, payload_sz=1) - t('a=t,t=f,s=100,z=9', payload='payload', action='t', transmission_type='f', data_width=100, z_index=9, payload_sz=7) - t('a=t,t=f,s=100,z=9', action='t', transmission_type='f', data_width=100, z_index=9) + t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1) + t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7) + t('a=t,t=d,s=100,z=9', action='t', transmission_type='d', data_width=100, z_index=9) e(',s=1', 'Malformed graphics control block, invalid key character: 0x2c') e('W=1', 'Malformed graphics control block, invalid key character: 0x57') e('1=1', 'Malformed graphics control block, invalid key character: 0x31') diff --git a/setup.py b/setup.py index 8e00a47f7..7938e26e1 100755 --- a/setup.py +++ b/setup.py @@ -170,7 +170,7 @@ def init_env(debug=False, sanitize=False, native_optimizations=True, profile=Fal else: glfw_ldflags = pkg_config('glfw3', '--libs') glew_libs = pkg_config('glew', '--libs') - ldpaths = pylib + glew_libs + font_libs + glfw_ldflags + ['-lunistring'] + ldpaths = pylib + glew_libs + font_libs + glfw_ldflags + ['-lunistring', '-lrt'] try: os.mkdir(build_dir)