diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 8b55ffd26..985deb836 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -66,7 +66,8 @@ only if the list changes. When the drag leaves the window, the terminal will send the same event but with ``x, y = -1, -1`` to indicate that the drag has left the window. For such events the list of MIME types must be empty. Note that the terminal must never -send negative cell co-ordinates for any other reason. +send negative cell co-ordinates for any other reason. No more movement escape +codes ``t=m`` will be sent until this drop or another re-enters the window. The client program must inform the terminal whether it will accept the potential drop and which MIME types of the set of offered MIME types it @@ -81,9 +82,23 @@ occurs which can be either ``1`` for copy or ``2`` for move or ``0`` for not accepted. The MIME list is the ordered list of MIME types from the offered list that the client wants. If no MIME type list is present, it is equivalent to no change in the offered list of MIME types. The list should be ordered in order -of decreasing preference. Some platforms may assume show the user some +of decreasing preference. Some platforms may show the user some indication of the first MIME type in the list. +When the user triggers a drop on the window, the terminal will send an escape +code of the form:: + + OSC _dnd_code ; t=M: ... ; MIME list ST + +This is the same as the movement escape codes above, except that ``t=M`` +(upper case M instead of lower case m), indicating this is a drop. +Once this escape code is received, no more movement escape codes ``t=m`` +will be sent until a new drop enters the window. The MIME list here is +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. + + Metadata reference --------------------------- @@ -97,6 +112,7 @@ Key Value Default Description ``(a, A, ``a`` - start accepting drops )`` ``A`` - stop accepting drops ``m`` - a drop move event + ``M`` - a drop dropped event ``m`` Chunking indicator ``0`` ``0`` or ``i`` diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 6fbb74df6..4eb89292b 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('aA')), + 't': ('type', flag('aAmM')), 'm': ('more', 'uint'), 'i': ('client_id', 'uint'), 'o': ('operation', 'uint'), diff --git a/kitty/dnd.c b/kitty/dnd.c index 4a11ab3b7..5be91a703 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -41,6 +41,17 @@ drop_free_data(Window *w) { free_pending(&w->drop.pending); } +static void +reset_drop(Window *w) { + bool wanted = w->drop.wanted; uint32_t cid = w->drop.client_id; + drop_free_data(w); + zero_at_ptr(&w->drop); + if (wanted) { + w->drop.wanted = wanted; + w->drop.client_id = cid; + } +} + 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; @@ -102,6 +113,18 @@ flush_pending(id_type id, PendingData *pending) { return pending->count > 0; } +#define check_for_pending_writes() \ + add_main_loop_timer(ms_to_monotonic_t(20), false, flush_pending_payloads, (void*)(uintptr_t)id, NULL) + +static void +flush_pending_payloads(id_type timer_id UNUSED, void *x) { + id_type id = (uintptr_t)x; + Window *w = window_for_window_id(id); + if (w && w->drop.wanted) { + if (!flush_pending(w->id, &w->drop.pending)) check_for_pending_writes(); + } +} + static void queue_payload_to_child(id_type id, PendingData *pending, const char *header, size_t header_sz, const char *data, size_t data_sz) { size_t offset = 0; @@ -114,14 +137,16 @@ queue_payload_to_child(id_type id, PendingData *pending, const char *header, siz PendingEntry *e = &pending->items[pending->count++]; e->buf = buf; e->header_sz = header_sz; e->data_sz = data_sz - offset; } + if (pending->count) check_for_pending_writes(); } void -drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) { +drop_move_on_child(Window *w, const char** mimes, size_t num_mimes, bool is_drop) { if (!w->drop.hovered) { - drop_free_offered_mimes(w); + reset_drop(w); w->drop.hovered = true; } + if (is_drop) { w->drop.dropped = true; w->drop.hovered = false; } size_t mimes_total_size = 0; if (mimes && (w->drop.offerred_mimes == NULL || string_arrays_cmp(mimes, num_mimes, w->drop.offerred_mimes, w->drop.num_offerred_mimes) != 0)) { drop_free_offered_mimes(w); @@ -139,10 +164,11 @@ drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) { w->drop.num_offerred_mimes = num_mimes; } // we simply drop this event if there is too much data being written to the child - if (w->drop.pending.count) return; + if (w->drop.pending.count && !is_drop) return; char buf[128]; - int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=m:x=%u:y=%u:X=%d:Y=%d", DND_CODE, w->drop.client_id, - w->mouse_pos.cell_x, w->mouse_pos.cell_y, (int)w->mouse_pos.global_x, (int)w->mouse_pos.global_y); + int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=%c:x=%u:y=%u:X=%d:Y=%d", DND_CODE, w->drop.client_id, + is_drop ? 'M' : 'm', w->mouse_pos.cell_x, w->mouse_pos.cell_y, + (int)w->mouse_pos.global_x, (int)w->mouse_pos.global_y); if (mimes_total_size) { mimes_total_size += 1; RAII_ALLOC(char, mbuf, malloc(mimes_total_size)); @@ -157,8 +183,7 @@ drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) { } } else { buf[header_size++] = 0x1b; buf[header_size++] = '\\'; - bool found, too_much_data; - schedule_write_to_child_if_possible(w->id, buf, header_size, &found, &too_much_data); + queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0); } } @@ -215,6 +240,7 @@ drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_co void drop_left_child(Window *w) { w->drop.hovered = false; + w->drop.dropped = false; drop_free_offered_mimes(w); if (w->drop.wanted) { char buf[128]; diff --git a/kitty/dnd.h b/kitty/dnd.h index 29ae140f2..19381a111 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -8,7 +8,7 @@ #include "state.h" -void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes); +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_set_status(Window *w, int operation, const char *payload, size_t payload_sz, bool more); diff --git a/kitty/glfw.c b/kitty/glfw.c index 4ca71caf1..03dfd65ef 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -808,7 +808,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { 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) { - drop_move_on_child(w, ev->mimes, ev->num_mimes); + drop_move_on_child(w, ev->mimes, ev->num_mimes, false); ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes); return; } @@ -836,20 +836,24 @@ 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; - if (ev->from_self) { - if (global_state.drag_source.drag_data) { - global_state.drag_source.was_dropped = true; - WINDOW_CALLBACK(on_drop, "OOii", global_state.drag_source.drag_data, Py_True, - global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); - } else log_error("Got a drop from self but drag_source.drag_data is NULL"); - ev->finish_drop(window, GLFW_DRAG_OPERATION_COPY); - break; - } - update_allowed_mimes_for_drop(ev); - ev->num_mimes = remove_duplicate_mimes(ev->mimes, ev->num_mimes); - global_state.drop_dest.num_left = ev->num_mimes; - if (!global_state.drop_dest.num_left || !(global_state.drop_dest.data = PyDict_New())) { - ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); + if (is_kitty_ui_drag) { + if (ev->from_self) { + if (global_state.drag_source.drag_data) { + global_state.drag_source.was_dropped = true; + WINDOW_CALLBACK(on_drop, "OOii", global_state.drag_source.drag_data, Py_True, + global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); + } else log_error("Got a drop from self but drag_source.drag_data is NULL"); + ev->finish_drop(window, GLFW_DRAG_OPERATION_COPY); + break; + } + update_allowed_mimes_for_drop(ev); + ev->num_mimes = remove_duplicate_mimes(ev->mimes, ev->num_mimes); + global_state.drop_dest.num_left = ev->num_mimes; + 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); } break; case GLFW_DROP_DATA_AVAILABLE: diff --git a/kitty/state.h b/kitty/state.h index 4233f49ff..9d49d1ebd 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -246,7 +246,7 @@ typedef struct Window { bool is_hovering; } scrollbar; struct { - bool wanted, hovered; + bool wanted, hovered, dropped; uint32_t client_id; PendingData pending;