From 054ea80e000aa5387bc54ee7e4aa121a2c668c87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:21:21 +0000 Subject: [PATCH] Implement drag source data functions: drag_free_data, drag_get_data, drag_process_item_data Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/39fe0e9c-1286-442c-8d7d-7cea5905a178 Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com> --- kitty/dnd.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 8 deletions(-) diff --git a/kitty/dnd.c b/kitty/dnd.c index 7fb818f01..7c87ee344 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -866,7 +866,7 @@ drag_free_built_data(Window *w) { if (ds.items) { for (size_t i=0; i < ds.num_mimes; i++) { free(ds.items[i].optional_data); - if (ds.items[i].fd_plus_one) safe_close(ds.items[i].fd_plus_one, __FILE__, __LINE__); + if (ds.items[i].fd_plus_one > 0) safe_close(ds.items[i].fd_plus_one - 1, __FILE__, __LINE__); } free(ds.items); } @@ -1049,7 +1049,19 @@ drag_start(Window *w) { if (err != 0) { abrt(err); } else { - drag_free_built_data(w); + // Free images and optional_data but keep the items array for later + // data requests from the drop target + for (size_t i = 0; i < ds.num_mimes; i++) { + free(ds.items[i].optional_data); + ds.items[i].optional_data = NULL; + ds.items[i].data_size = 0; + ds.items[i].data_capacity = 0; + ds.items[i].data_decode_initialized = false; + } + 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.state = DRAG_SOURCE_STARTED; drag_send_error(w, 0); // send OK } @@ -1082,18 +1094,54 @@ drag_notify(Window *w, DragNotifyType type) { int drag_free_data(Window *w, const char *mime_type, const char* data, size_t sz) { - (void)w; (void)mime_type; (void)data; (void)sz; + (void)w; (void)mime_type; (void)sz; + free((void*)data); return 0; } const char* drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) { *err_code = ENOENT; *sz = 0; + if (!ds.items) return NULL; for (size_t i = 0; i < ds.num_mimes; i++) { if (strcmp(ds.items[i].mime_type, mime_type) == 0) { + if (ds.items[i].fd_plus_one < 0) { + // Error was stored by drag_process_item_data + *err_code = -ds.items[i].fd_plus_one; + ds.items[i].fd_plus_one = 0; + return NULL; + } + if (ds.items[i].fd_plus_one > 0) { + // Data is available in the temp file, read it + int fd = ds.items[i].fd_plus_one - 1; + off_t end = lseek(fd, 0, SEEK_END); + if (end < 0) { *err_code = EIO; return NULL; } + if (lseek(fd, 0, SEEK_SET) < 0) { *err_code = EIO; return NULL; } + char *data = malloc(end ? (size_t)end : 1); + if (!data) { *err_code = ENOMEM; return NULL; } + size_t total = 0; + while (total < (size_t)end) { + ssize_t n = read(fd, data + total, (size_t)end - total); + if (n < 0) { + if (errno == EINTR) continue; + free(data); + *err_code = EIO; + return NULL; + } + if (n == 0) break; + total += (size_t)n; + } + // Close and reset the fd after reading + safe_close(fd, __FILE__, __LINE__); + ds.items[i].fd_plus_one = 0; + *sz = total; + *err_code = 0; + return data; + } + // No fd yet, request data from the client char buf[128]; - size_t sz = snprintf(buf, sizeof(buf), "t=e:x=%d:y=%zu", DRAG_NOTIFY_FINISHED + 2, i); - queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, sz, NULL, 0, false); + int header_sz = snprintf(buf, sizeof(buf), "\x1b]%d;t=e:x=%d:y=%zu", DND_CODE, DRAG_NOTIFY_FINISHED + 2, i); + queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false); *err_code = EAGAIN; return NULL; } @@ -1101,11 +1149,88 @@ drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) { return NULL; } +static int +parse_errno_name(const uint8_t *data, size_t sz) { + if (sz >= 6 && memcmp(data, "ENOENT", 6) == 0) return ENOENT; + if (sz >= 5 && memcmp(data, "EPERM", 5) == 0) return EPERM; + if (sz >= 6 && memcmp(data, "EINVAL", 6) == 0) return EINVAL; + if (sz >= 6 && memcmp(data, "ENOMEM", 6) == 0) return ENOMEM; + if (sz >= 5 && memcmp(data, "EFBIG", 5) == 0) return EFBIG; + if (sz >= 3 && memcmp(data, "EIO", 3) == 0) return EIO; + return EIO; +} + +static int +open_item_tmpfile(void) { + int fd = -1; +#ifdef O_TMPFILE + fd = safe_open("/tmp", O_TMPFILE | O_CLOEXEC | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); +#endif + if (fd < 0) { + char name[] = "/tmp/kitty-dnd-XXXXXXXXXXXX"; + fd = safe_mkstemp(name); + if (fd >= 0) unlink(name); + } + return fd; +} + void drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *payload, size_t payload_sz) { - (void)w; (void)idx; (void)has_more; (void)payload; (void)payload_sz; - // call notify_drag_data_ready() after writing payload to file and - // dont forget to check the return value of notify_drag_data_ready() + if ((ds.state != DRAG_SOURCE_STARTED && ds.state != DRAG_SOURCE_DROPPED) || idx >= ds.num_mimes || !ds.items) return; + + if (has_more < 0) { + // Error from the client program + if (ds.items[idx].fd_plus_one > 0) { + safe_close(ds.items[idx].fd_plus_one - 1, __FILE__, __LINE__); + } + int err = parse_errno_name(payload, payload_sz); + ds.items[idx].fd_plus_one = -err; + ds.items[idx].data_decode_initialized = false; + int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type); + if (ret) cancel_drag(w, ret); + return; + } + + // Open temp file if not yet open + if (!ds.items[idx].fd_plus_one) { + int fd = open_item_tmpfile(); + if (fd < 0) { cancel_drag(w, ENOMEM); return; } + ds.items[idx].fd_plus_one = fd + 1; + ds.items[idx].data_decode_initialized = true; + base64_init_stream_decoder(&ds.items[idx].base64_state); + } + + // Decode and write payload data + if (payload_sz > 0) { + RAII_ALLOC(uint8_t, decoded, malloc(payload_sz)); + if (!decoded) { cancel_drag(w, ENOMEM); return; } + size_t outlen = payload_sz; + if (!base64_decode_stream(&ds.items[idx].base64_state, payload, payload_sz, decoded, &outlen)) { + cancel_drag(w, EINVAL); + return; + } + size_t written = 0; + while (written < outlen) { + ssize_t n = write(ds.items[idx].fd_plus_one - 1, decoded + written, outlen - written); + if (n < 0) { + if (errno == EINTR) continue; + cancel_drag(w, EIO); + return; + } + written += (size_t)n; + } + } + + if (has_more == 0) { + // All data received, seek to beginning and notify + if (lseek(ds.items[idx].fd_plus_one - 1, 0, SEEK_SET) < 0) { + cancel_drag(w, EIO); + return; + } + ds.items[idx].data_decode_initialized = false; + int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type); + if (ret) cancel_drag(w, ret); + } } #undef img #undef abrt