Send dropped message

This commit is contained in:
Kovid Goyal 2026-03-06 19:05:54 +05:30
parent 3a30c47942
commit 4f404f8c85
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 73 additions and 27 deletions

View file

@ -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``

View file

@ -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'),

View file

@ -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];

View file

@ -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);

View file

@ -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:

View file

@ -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;