diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 985deb836..d3eca8ff9 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -98,6 +98,22 @@ mandatory, terminals must send the full list of MIME types available in the drop. The client program can now request data for the MIME types it is interested in. +Requesting data is done by sending an escape code of the form:: + + OSC _dnd_code ; t=r ; MIME type ST + +This will request data for the specifid MIME type. The terminal must respond +with a series of escape codes of the form:: + + OSC _dnd_code ; t=r ; data ST + +End of data is indicated by an empty payload. If some error occures while +getting the data, the terminal must send an escape code of the form:: + + OSC _dnd_code ; t=R ; POSIX error name ST + +Here POSIX error name is a POSIX symbolic error name such as ``ENOENT`` or +``EIO`` or the value ``EUNKNOWN`` for an unknown error. Metadata reference --------------------------- @@ -113,6 +129,7 @@ Key Value Default Description )`` ``A`` - stop accepting drops ``m`` - a drop move event ``M`` - a drop dropped event + ``r`` - request dropped data ``m`` Chunking indicator ``0`` ``0`` or ``i`` diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 4eb89292b..aea0a7826 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -331,7 +331,7 @@ def parsers() -> None: write_header(text, 'kitty/parse-multicell-command.h') keymap = { - 't': ('type', flag('aAmM')), + 't': ('type', flag('aAmMrR')), 'm': ('more', 'uint'), 'i': ('client_id', 'uint'), 'o': ('operation', 'uint'), diff --git a/kitty/dnd.c b/kitty/dnd.c index 5be91a703..99d6b0f12 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -39,6 +39,8 @@ drop_free_data(Window *w) { drop_free_offered_mimes(w); drop_free_accepted_mimes(w); free_pending(&w->drop.pending); + free(w->drop.registered_mimes); w->drop.registered_mimes = NULL; + free(w->drop.getting_data_for_mime); w->drop.getting_data_for_mime = NULL; } static void @@ -52,6 +54,40 @@ reset_drop(Window *w) { } } +void +drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more) { + w->drop.wanted = on; + w->drop.client_id = client_id; + if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); return; } + if (!payload || !payload_sz) return; + size_t sz = w->drop.registered_mimes ? strlen(w->drop.registered_mimes) : 0; + if (sz + payload_sz > 1024 * 1024) return; + w->drop.registered_mimes = realloc(w->drop.registered_mimes, sz + payload_sz + 1); + if (w->drop.registered_mimes) { + memcpy(w->drop.registered_mimes + sz, payload, payload_sz); + sz += payload_sz; + w->drop.registered_mimes[sz] = 0; + } + if (more) return; + if (w->drop.registered_mimes) { + OSWindow *osw = os_window_for_kitty_window(w->id); + if (osw) { + size_t num = 0; + RAII_ALLOC(const char*, mimes, malloc(sizeof(char*) * strlen(w->drop.registered_mimes))); + if (mimes) { + char* token = strtok(w->drop.registered_mimes, " "); + while (token != NULL) { + mimes[num++] = token; + token = strtok(NULL, " "); + } + register_mimes_for_drop(osw, mimes, num); + } + } + } + free(w->drop.registered_mimes); w->drop.registered_mimes = NULL; +} + + static int string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) { if (an != bn) return (int)an - (int)bn; @@ -237,6 +273,54 @@ drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_co return allowed_mimes_count; } +static const char* +get_errno_name(int err) { + switch (err) { + case EPERM: return "EPERM"; + case ENOENT: return "ENOENT"; + case EIO: return "EIO"; + default: return "EUNKNOWN"; + } +} + +static void +drop_send_error(Window *w, int error_code) { + char buf[128]; + uint8_t ebuf[32]; + size_t b64_len = sizeof(ebuf); + const char *e = get_errno_name(error_code); + base64_encode8((const uint8_t*)e, strlen(e), ebuf, &b64_len, false); + int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=R", DND_CODE, w->drop.client_id); + queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, (char*)ebuf, b64_len); +} + +void +drop_request_data(Window *w, const char *mime) { + if (w->drop.getting_data_for_mime) { free(w->drop.getting_data_for_mime); w->drop.getting_data_for_mime = NULL; } + OSWindow *osw = os_window_for_kitty_window(w->id); + if (!osw) return; + if (w->drop.offerred_mimes) { + for (size_t i = 0; i < w->drop.num_offerred_mimes; i++) { + if (strcmp(mime, w->drop.offerred_mimes[i]) == 0) { + w->drop.getting_data_for_mime = strdup(mime); + if (w->drop.getting_data_for_mime) request_drop_data(osw, w->id, mime); + return; + } + } + } + drop_send_error(w, ENOENT); +} + +void +drop_dispatch_data(Window *w, const char *data, ssize_t sz) { + if (sz < 0) drop_send_error(w, -sz); + else { + char buf[128]; + int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=r", DND_CODE, w->drop.client_id); + queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, sz ? data : NULL, sz); + } +} + void drop_left_child(Window *w) { w->drop.hovered = false; diff --git a/kitty/dnd.h b/kitty/dnd.h index 19381a111..6b56560ff 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -8,8 +8,11 @@ #include "state.h" +void drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more); void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes, bool is_drop); void drop_left_child(Window *w); void drop_free_data(Window *w); +void drop_request_data(Window *w, const char *mime); void drop_set_status(Window *w, int operation, const char *payload, size_t payload_sz, bool more); size_t drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_count); +void drop_dispatch_data(Window *w, const char *data, ssize_t sz); diff --git a/kitty/glfw.c b/kitty/glfw.c index 03dfd65ef..05001d944 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -767,28 +767,17 @@ read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) { } void -register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id) { - Window *w = window_for_window_id(window_id); - OSWindow *osw = os_window_for_kitty_window(window_id); - if (w && osw && osw->handle) { - w->drop.wanted = on; - w->drop.client_id = client_id; - if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); } - else if (payload && payload_sz) { +register_mimes_for_drop(OSWindow *w, const char **mimes, size_t sz) { + (void)w; (void)mimes; (void)sz; #ifdef __APPLE__ - RAII_ALLOC(char, copy, malloc(payload_sz + 1)); if (!copy) return; - RAII_ALLOC(const char*, mimes, calloc(payload_sz, sizeof(char*))); if (!mimes) return; - memcpy(copy, payload, payload_sz); copy[payload_sz] = 0; - size_t num = 0; - char* token = strtok(copy, " "); - while (token != NULL) { - mimes[num++] = token; - token = strtok(NULL, " "); - } - glfwCocoaRegisterMIMETypes(osw->handle, mimes, num); + if (w->handle) glfwCocoaRegisterMIMETypes(w->handle, mimes, sz); #endif - } - } +} + +void +request_drop_data(OSWindow *w, id_type wid, const char* mime) { + global_state.drop_dest.client_window_data_request = wid; + if (w->handle) glfwRequestDropData(w->handle, mime); } static void @@ -800,6 +789,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { for (size_t i = 0; i < ev->num_mimes; i++) { if (is_droppable_mime(ev->mimes[i]) >= TAB_DRAG_MIME_NUMBER) { is_kitty_ui_drag = true; break;} } + bool is_client_drop = !is_kitty_ui_drag && wid && (w = window_for_window_id(wid)) && w->drop.wanted; switch (ev->type) { case GLFW_DROP_ENTER: case GLFW_DROP_MOVE: @@ -807,7 +797,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { os_window->last_drag_event.x = (int)(ev->xpos * os_window->viewport_x_ratio); os_window->last_drag_event.y = (int)(ev->ypos * os_window->viewport_y_ratio); on_mouse_position_update(ev->xpos, ev->ypos); - if (!is_kitty_ui_drag && wid && (w = window_for_window_id(wid)) && w->drop.wanted) { + if (is_client_drop) { drop_move_on_child(w, ev->mimes, ev->num_mimes, false); ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes); return; @@ -817,9 +807,8 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { ev->from_self ? Py_True : Py_False, Py_False); /* fallthrough */ case GLFW_DROP_STATUS_UPDATE: - if (is_kitty_ui_drag) update_allowed_mimes_for_drop(ev); - else if (wid && (w = window_for_window_id(wid)) && w->drop.wanted && w->drop.hovered) - ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes); + if (is_client_drop) ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes); + else update_allowed_mimes_for_drop(ev); break; case GLFW_DROP_LEAVE: for (size_t tc = 0; tc < os_window->num_tabs; tc++) { @@ -836,6 +825,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { case GLFW_DROP_DROP: Py_CLEAR(global_state.drop_dest.data); global_state.drop_dest.drop_has_happened = true; + global_state.drop_dest.client_window_data_request = 0; if (is_kitty_ui_drag) { if (ev->from_self) { if (global_state.drag_source.drag_data) { @@ -852,13 +842,20 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { if (!global_state.drop_dest.num_left || !(global_state.drop_dest.data = PyDict_New())) { ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); } - } else if (wid && (w = window_for_window_id(wid)) && w->drop.wanted && w->drop.hovered) { - drop_move_on_child(w, ev->mimes, ev->num_mimes, true); - } + } else if (is_client_drop) drop_move_on_child(w, ev->mimes, ev->num_mimes, true); break; case GLFW_DROP_DATA_AVAILABLE: - if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); - else read_drop_data(window, ev); + if (global_state.drop_dest.client_window_data_request) { + if ((w = window_for_window_id(global_state.drop_dest.client_window_data_request))) { + char buf[3072]; + ssize_t ret = ev->read_data(window, ev, buf, sizeof(buf)); + drop_dispatch_data(w, buf, ret); + if (ret <= 0) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); + } + } else { + if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); + else read_drop_data(window, ev); + } break; } } diff --git a/kitty/parse-dnd-command.h b/kitty/parse-dnd-command.h index f80998f08..652922a0e 100644 --- a/kitty/parse-dnd-command.h +++ b/kitty/parse-dnd-command.h @@ -18,7 +18,16 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, (void)is_negative; size_t sz; - enum KEYS { type = 't', more = 'm', client_id = 'i' }; + enum KEYS { + type = 't', + more = 'm', + client_id = 'i', + operation = 'o', + cell_x = 'x', + cell_y = 'y', + pixel_x = 'X', + pixel_y = 'Y' + }; enum KEYS key = 'a'; if (parser_buf[pos] == ';') @@ -39,6 +48,21 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, case client_id: value_state = UINT; break; + case operation: + value_state = UINT; + break; + case cell_x: + value_state = INT; + break; + case cell_y: + value_state = INT; + break; + case pixel_x: + value_state = INT; + break; + case pixel_y: + value_state = INT; + break; default: REPORT_ERROR( "Malformed DnDCommand control block, invalid key character: 0x%x", @@ -62,7 +86,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, case type: { g.type = parser_buf[pos++]; - if (g.type != 'A' && g.type != 'a') { + if (g.type != 'A' && g.type != 'M' && g.type != 'R' && g.type != 'a' && + g.type != 'm' && g.type != 'r') { REPORT_ERROR("Malformed DnDCommand control block, unknown flag value " "for type: 0x%x", g.type); @@ -109,7 +134,10 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, break READ_UINT; switch (key) { - ; + I(cell_x); + I(cell_y); + I(pixel_x); + I(pixel_y); default: break; } @@ -125,6 +153,7 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, switch (key) { U(more); U(client_id); + U(operation); default: break; } @@ -182,14 +211,18 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, break; } - REPORT_VA_COMMAND("K s {sc sI sI ss#}", self->window_id, "dnd_command", + REPORT_VA_COMMAND( + "K s {sc sI sI sI si si si si ss#}", self->window_id, "dnd_command", - "type", g.type, + "type", g.type, - "more", (unsigned int)g.more, "client_id", - (unsigned int)g.client_id, + "more", (unsigned int)g.more, "client_id", (unsigned int)g.client_id, + "operation", (unsigned int)g.operation, - "", (char *)parser_buf, g.payload_sz); + "cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x", + (int)g.pixel_x, "pixel_y", (int)g.pixel_y, + + "", (char *)parser_buf, g.payload_sz); screen_handle_dnd_command(self->screen, &g, parser_buf); } diff --git a/kitty/screen.c b/kitty/screen.c index 2f4c3b47b..a912b8678 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1511,12 +1511,18 @@ screen_dirty_line_graphics(Screen *self, const unsigned int top, const unsigned void screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *payload) { if (!self->window_id) return; + Window *w = window_for_window_id(self->window_id); + if (!w) return; switch(cmd->type) { - case 'a': register_drop_window(self->window_id, payload, cmd->payload_sz, true, cmd->client_id); break; - case 'A': register_drop_window(self->window_id, NULL, 0, false, cmd->client_id); break; - case 'm': { - Window *w = window_for_window_id(self->window_id); - if (w) drop_set_status(w, cmd->operation, (const char*)payload, cmd->payload_sz, cmd->more); + case 'a': drop_register_window(w, payload, cmd->payload_sz, true, cmd->client_id, cmd->more); break; + case 'A': drop_register_window(w, NULL, 0, false, cmd->client_id, cmd->more); break; + case 'm': drop_set_status(w, cmd->operation, (const char*)payload, cmd->payload_sz, cmd->more); break; + case 'r': { + char buf[256]; + if (w && cmd->payload_sz + 1 < sizeof(buf)) { + memcpy(buf, payload, cmd->payload_sz); buf[cmd->payload_sz] = 0; + drop_request_data(w, buf); + } } break; } } diff --git a/kitty/screen.h b/kitty/screen.h index 345f912bb..aa943d799 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -326,7 +326,6 @@ bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms); void screen_check_pause_rendering(Screen *self, monotonic_t now); void screen_designate_charset(Screen *self, uint32_t which, uint32_t as); void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params); -void register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/state.h b/kitty/state.h index 9d49d1ebd..75ea00350 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -248,13 +248,14 @@ typedef struct Window { struct { bool wanted, hovered, dropped; uint32_t client_id; + char *registered_mimes; PendingData pending; const char **offerred_mimes; size_t num_offerred_mimes; char *accepted_mimes; size_t accepted_mimes_sz; int accepted_operation; bool accept_in_progress; - + char *getting_data_for_mime; } drop; } Window; @@ -404,7 +405,7 @@ typedef struct GlobalState { struct { PyObject *data; - id_type os_window_id; + id_type os_window_id, client_window_data_request; double x, y; size_t num_left; bool drop_has_happened; @@ -536,3 +537,5 @@ void swap_window_buffers(OSWindow *w); void take_screenshot_of_rectangular_region(OSWindow *os_window, Region region, unsigned char *dst_buf, unsigned *thumb_w, unsigned *thumb_h); bool current_framebuffer_is_ok(void); void request_drop_status_update(OSWindow *osw); +void register_mimes_for_drop(OSWindow *w, const char **mimes, size_t sz); +void request_drop_data(OSWindow *w, id_type wid, const char* mime);