diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index f1ca3e955..f4023d749 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -271,6 +271,40 @@ 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``. +As the drag progresses, status changes are reported using the ``t=e`` escape +code. The variants are listed in the table below: + +.. list-table:: Drag offer events + + * - Code + - Description + * - ``t=e : x=1 : y=idx`` + - The drag has been accepted by a client. ``idx`` is a zero based index into the list of MIME types + pointing to the MIME type the client is likely to want + * - ``t=e : x=2 : o=O`` + - The action the client is likely to perform has changed to the value indicated by the ``o`` key + * - ``t=e : x=3`` + - The drag offer has been dropped onto a client, there are likely to be requests for data in the near future + * - ``t=e : x=4 : y=0 or 1`` + - The drag is finished. If ``y=1`` then the drag was canceled by the user. + * - ``t=e : x=5 : y=idx`` + - Request data for the MIME type at the zero based index ``idx`` in the list of MIME types + +The client program should respond to data requests with escape codes of the +form:: + + OSC _dnd_code ; t=e:m=0 or 1 ; base64 encoded data ST + +This, is the data for requested MIME type. The data should be chunkedusing the +``m`` key. End of data is denoted by ``m=0`` and an rmpty payload. If an error +occurs the client should send:: + + OSC _dnd_code ; t=E; ERR_CODE ST + +Where ``ERR_CODE`` is a POSIX error code such as ``ENOENT`` if the MIME type is +not found or ``EIO`` if an IO error occurred and so on. + + Multiplexers ----------------- @@ -302,6 +336,7 @@ Key Value Default Description ``O`` - stop offering drags ``p`` - present data for drag offers ``P`` - Change drag image or start drag + ``e`` - a drag offer event occurred ``m`` Chunking indicator ``0`` ``0`` or ``i`` diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index cf0532edb..4214397fc 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('aAmMrRsdoOpP')), + 't': ('type', flag('aAmMrRsdoOpPeE')), 'm': ('more', 'uint'), 'i': ('client_id', 'uint'), 'o': ('operation', 'uint'), diff --git a/kitty/dnd.c b/kitty/dnd.c index da4629211..8c9e2c848 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -1052,6 +1052,52 @@ drag_start(Window *w) { } } +void +drag_notify(Window *w, DragNotifyType type) { + char buf[128]; + size_t sz = snprintf(buf, sizeof(buf), "t=e:x=%d", type + 1); + switch(type) { + case DRAG_NOTIFY_ACCEPTED: + for (size_t i = 0; i < ds.num_mimes; i++) { + if (strcmp(ds.items[i].mime_type, global_state.drag_source.accepted_mime_type) == 0) { + sz += snprintf(buf + sz, sizeof(buf) - sz, "y=%zu", i); break; + } + } + case DRAG_NOTIFY_ACTION_CHANGED: + switch (global_state.drag_source.action) { + case GLFW_DRAG_OPERATION_MOVE: + sz += snprintf(buf + sz, sizeof(buf) - sz, "o=2"); break; + default: + sz += snprintf(buf + sz, sizeof(buf) - sz, "o=1"); break; + } + case DRAG_NOTIFY_DROPPED: break; + case DRAG_NOTIFY_FINISHED: + sz += snprintf(buf + sz, sizeof(buf) - sz, "y=%d", global_state.drag_source.was_canceled ? 1 : 0); break; + } + queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, sz, NULL, 0, false); +} + +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; + return 0; +} + +const char* +drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) { + *err_code = ENOENT; *sz = 0; + for (size_t i = 0; i < ds.num_mimes; i++) { + if (strcmp(ds.items[i].mime_type, mime_type) == 0) { + 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); + *err_code = EAGAIN; + return NULL; + } + } + return NULL; +} + #undef img #undef abrt #undef ds diff --git a/kitty/dnd.h b/kitty/dnd.h index 6bb059f92..1b77e390b 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -8,6 +8,7 @@ #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); @@ -23,9 +24,13 @@ void drop_finish(Window *w); void dnd_set_test_write_func(PyObject *func); +typedef enum { DRAG_NOTIFY_ACCEPTED, DRAG_NOTIFY_ACTION_CHANGED, DRAG_NOTIFY_DROPPED, DRAG_NOTIFY_FINISHED } DragNotifyType; void drag_free_offer(Window *w); void drag_add_mimes(Window *w, int allowed_operations, uint32_t client_id, 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); void drag_change_image(Window *w, unsigned idx); void drag_start(Window *w); +void drag_notify(Window *w, DragNotifyType type); +int drag_free_data(Window *w, const char *mime_type, const char* data, size_t sz); +const char* drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code); diff --git a/kitty/glfw.c b/kitty/glfw.c index 708617e74..90efff5bd 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1084,42 +1084,59 @@ cancel_current_drag_source(void) { static void drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) { -#define finish \ +#define finish { \ call_boss(on_drag_source_finished, "OOsiOO", \ - ds.was_dropped && !global_state.drop_dest.os_window_id ? Py_True : Py_False, ds.was_canceled ? Py_True: Py_False, \ - ds.accepted_mime_type ? ds.accepted_mime_type : "", \ - ds.action, ds.drag_data ? ds.drag_data : Py_None, ds.needs_toplevel_on_wayland ? Py_True : Py_False); \ - free_drag_source(); + ds.was_dropped && !global_state.drop_dest.os_window_id ? Py_True : Py_False, ds.was_canceled ? Py_True: Py_False, \ + ds.accepted_mime_type ? ds.accepted_mime_type : "", \ + ds.action, ds.drag_data ? ds.drag_data : Py_None, ds.needs_toplevel_on_wayland ? Py_True : Py_False); \ + free_drag_source(); \ +} + Window *w = NULL; + bool is_client_drag = false; + if (ds.from_window && (w = window_for_window_id(ds.from_window)) && w->drag_source.state) { + is_client_drag = true; + } switch (ev->type) { - case GLFW_DRAG_DATA_REQUEST: // we currently pre-provide all data so this should never happen - if (ev->data_sz) { - // previously returned data is consumed, free it - } else { - ev->err_num = ENOENT; + case GLFW_DRAG_DATA_REQUEST: + ev->err_num = ENOENT; + if (is_client_drag) { + ev->err_num = 0; + if (ev->data_sz) { + ev->err_num = drag_free_data(w, ev->mime_type, ev->data, ev->data_sz); + } else { + ev->data = drag_get_data(w, ev->mime_type, &ev->data_sz, &ev->err_num); + } } break; case GLFW_DRAG_ACCEPTED: free(ds.accepted_mime_type); ds.accepted_mime_type = ev->mime_type ? strdup(ev->mime_type) : NULL; + if (is_client_drag) drag_notify(w, DRAG_NOTIFY_ACCEPTED); break; case GLFW_DRAG_ACTION_CHANGED: ds.action = ev->action; break; + if (is_client_drag) drag_notify(w, DRAG_NOTIFY_ACTION_CHANGED); case GLFW_DRAG_DROPPED: ds.was_dropped = true; // On Wayland, we cant trust the value of ev->action as it is zero // when dropping outside any application which is a case we want to // handle. - if (global_state.drop_dest.os_window_id && ds.drag_data) { - Py_CLEAR(global_state.drop_dest.self_drag_data); - global_state.drop_dest.self_drag_data = Py_NewRef(ds.drag_data); + if (is_client_drag) { + drag_notify(w, DRAG_NOTIFY_DROPPED); + } else { + if (global_state.drop_dest.os_window_id && ds.drag_data) { + Py_CLEAR(global_state.drop_dest.self_drag_data); + global_state.drop_dest.self_drag_data = Py_NewRef(ds.drag_data); + } + finish; } - finish; break; case GLFW_DRAG_CANCELLED: ds.was_canceled = true; /* fallthrough */ case GLFW_DRAG_FINSHED: + if (is_client_drag) drag_notify(w, DRAG_NOTIFY_FINISHED); finish break; } diff --git a/kitty/parse-dnd-command.h b/kitty/parse-dnd-command.h index 9c77b4d5c..a79dbfa52 100644 --- a/kitty/parse-dnd-command.h +++ b/kitty/parse-dnd-command.h @@ -86,9 +86,10 @@ 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 != 'M' && g.type != 'O' && g.type != 'P' && - g.type != 'R' && g.type != 'a' && g.type != 'd' && g.type != 'm' && - g.type != 'o' && g.type != 'p' && g.type != 'r' && g.type != 's') { + if (g.type != 'A' && g.type != 'E' && g.type != 'M' && g.type != 'O' && + g.type != 'P' && g.type != 'R' && g.type != 'a' && g.type != 'd' && + g.type != 'e' && g.type != 'm' && g.type != 'o' && g.type != 'p' && + g.type != 'r' && g.type != 's') { REPORT_ERROR("Malformed DnDCommand control block, unknown flag value " "for type: 0x%x", g.type);