diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 796d0d8ff..5fcfa02e5 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -265,8 +265,9 @@ operation, it indicates the drag should be started by sending ``t=P:x=-1``. At this time if the user has already cancelled the drag or the terminal determines the drag operation is not allowed, it must respond with ``t=R ; EPERM``. If any other error occurs starting the drag operation, it must respond with the appropriate -POSIX error code. If the drag operation is successfully started, it must respond with -``t=R ; OK``. +POSIX error code. If it determines that the image data after conversion to +display format is too large, it must respond with ``t=R ; EFBIG``. If the drag +operation is successfully started, it must respond with ``t=R ; OK``. Multiplexers ----------------- diff --git a/kitty/dnd.c b/kitty/dnd.c index 5f0ceac95..d1b59745c 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -18,6 +18,7 @@ #include static const size_t MIME_LIST_SIZE_CAP = 1024 * 1024; +static const size_t PRESENT_DATA_CAP = 64 * 1024 * 1024; // In test mode, this callable is invoked instead of schedule_write_to_child_if_possible. // It receives (window_id: int, data: bytes) and its return value is ignored. @@ -869,8 +870,13 @@ drag_free_offer(Window *w) { for (size_t i=0; i < ds.num_mimes; i++) free(ds.items[i].optional_data); free(ds.items); } + for (size_t i = 0; i < arraysz(ds.images); i++) { + if (ds.images[i].data) free(ds.images[i].data); + zero_at_ptr(ds.images + i); + } ds.num_mimes = 0; ds.pre_sent_total_sz = 0; + ds.images_sent_total_sz = 0; } void @@ -912,22 +918,49 @@ drag_add_mimes(Window *w, int allowed_operations, const char *data, size_t sz, b void drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t sz) { if (!ds.offer_being_built || idx >= ds.num_mimes) abrt(EINVAL); - if (sz + ds.pre_sent_total_sz > 64 * 1024 * 1024) abrt(EFBIG); + if (sz + ds.pre_sent_total_sz > PRESENT_DATA_CAP) abrt(EFBIG); ds.pre_sent_total_sz += sz; - DragSourceItem *item = ds.items + idx; - if (!item->data_decode_initialized) { - item->data_decode_initialized = true; - base64_init_stream_decoder(&item->base64_state); +#define item ds.items[idx] + if (!item.data_decode_initialized) { + item.data_decode_initialized = true; + base64_init_stream_decoder(&item.base64_state); } - if (item->data_capacity < sz + item->data_size) { - size_t newcap = MAX(item->data_size * 2, sz + item->data_size); - item->optional_data = realloc(item->optional_data, newcap); - if (!item->optional_data) abrt(ENOMEM); - item->data_capacity = newcap; + if (item.data_capacity < sz + item.data_size) { + size_t newcap = MAX(item.data_size * 2, sz + item.data_size); + item.optional_data = realloc(item.optional_data, newcap); + if (!item.optional_data) abrt(ENOMEM); + item.data_capacity = newcap; } - size_t outlen = item->data_capacity - item->data_size; - if (!base64_decode_stream(&item->base64_state, payload, sz, item->optional_data + item->data_size, &outlen)) abrt(EINVAL); - item->data_size += outlen; + size_t outlen = item.data_capacity - item.data_size; + if (!base64_decode_stream(&item.base64_state, payload, sz, item.optional_data + item.data_size, &outlen)) abrt(EINVAL); + item.data_size += outlen; +#undef item +} + +void +drag_add_image(Window *w, unsigned idx, int fmt, int width, int height, const uint8_t *payload, size_t sz) { + if (idx + 1 >= arraysz(ds.images)) abrt(EFBIG); + if (ds.images_sent_total_sz + sz > PRESENT_DATA_CAP) abrt(EFBIG); + ds.images_sent_total_sz += sz; +#define img ds.images[idx] + if (!img.started) { + if (fmt != 24 && fmt != 32 && fmt != 100) abrt(EINVAL); + if (width < 1 || height < 1) abrt(EINVAL); + img.started = true; + img.width = width; img.height = height; + img.fmt = fmt; + base64_init_stream_decoder(&img.base64_state); + } + if (img.capacity < sz + img.sz) { + size_t newcap = MAX(img.sz * 2, sz + img.sz); + img.data = realloc(img.data, newcap); + if (!img.data) abrt(ENOMEM); + img.capacity = newcap; + } + size_t outlen = img.capacity - img.sz; + if (!base64_decode_stream(&img.base64_state, payload, sz, img.data + img.sz, &outlen)) abrt(EINVAL); + img.sz += outlen; +#undef img } #undef abrt diff --git a/kitty/dnd.h b/kitty/dnd.h index 646d78006..2d03198ed 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -26,3 +26,4 @@ void dnd_set_test_write_func(PyObject *func); void drag_free_offer(Window *w); void drag_add_mimes(Window *w, int allowed_operations, const char *data, size_t sz, bool has_more); void drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t sz); +void drag_add_image(Window *w, unsigned idx_, int fmt, int width, int height, const uint8_t *payload, size_t sz); diff --git a/kitty/screen.c b/kitty/screen.c index 101497f93..c484af24c 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1550,6 +1550,7 @@ screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *pa } break; case 'p': { if (cmd->cell_x >= 0) drag_add_pre_sent_data(w, cmd->cell_x, payload, cmd->payload_sz); + else drag_add_image(w, -cmd->cell_x, cmd->cell_y, cmd->pixel_x, cmd->pixel_y, payload, cmd->payload_sz); } break; } } diff --git a/kitty/state.h b/kitty/state.h index 68025b262..7d0b0c83f 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -299,7 +299,10 @@ typedef struct Window { struct { double x, y; monotonic_t at; } initial_left_press; char *mimes_buf; size_t num_mimes, bufsz; DragSourceItem *items; - size_t pre_sent_total_sz; + struct { + int width, height, fmt; uint8_t *data; size_t sz, capacity; bool started; base64_state base64_state; + } images[16]; + size_t pre_sent_total_sz, images_sent_total_sz; int allowed_operations; bool offer_being_built; } drag_source;