Get drop status updates working

This commit is contained in:
Kovid Goyal 2026-03-06 15:47:10 +05:30
parent 111b35b6a7
commit 3a30c47942
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
11 changed files with 125 additions and 9 deletions

View file

@ -68,6 +68,21 @@ 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.
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
accepts. Until the client does so the terminal will indicate to the OS that
the drop is not accepted. To do so, the client sends an escape code of the
form::
OSC _dnd_code ; t=m:o=O ; MIME list ST
Here the ``o`` key is the operation the client intends to perform if a drop
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
indication of the first MIME type in the list.
Metadata reference
---------------------------
@ -89,6 +104,11 @@ Key Value Default Description
When it is set, all responses from
the terminal in that session will
have it set to the same value.
``o`` Positive integer ``0`` What drop operation to perform. ``0``
means rejected, ``1`` means copy and
``2`` means move.
**Keys for location**
-----------------------------------------------------------
``x`` Integer ``0`` Cell x-coordinate origin is 0, 0 at top left of screen

View file

@ -334,6 +334,11 @@ def parsers() -> None:
't': ('type', flag('aA')),
'm': ('more', 'uint'),
'i': ('client_id', 'uint'),
'o': ('operation', 'uint'),
'x': ('cell_x', 'int'),
'y': ('cell_y', 'int'),
'X': ('pixel_x', 'int'),
'Y': ('pixel_y', 'int'),
}
text = generate(
'parse_dnd_code', 'screen_handle_dnd_command', 'dnd_command', keymap, 'DnDCommand',

3
glfw/glfw3.h vendored
View file

@ -4976,7 +4976,8 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun ca
GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun callback);
GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow *window, GLFWdropeventfun callback);
GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window); // ask for update before GLFW_DROP_DROP happens
// ask for update before GLFW_DROP_DROP happens
GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window);
GLFWAPI int glfwRequestDropData(GLFWwindow *window, const char *mime);
GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op);
GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* window, GLFWdragsourcefun callback);

View file

@ -8,6 +8,7 @@
#include "dnd.h"
#include "base64.h"
#include "control-codes.h"
#include "iqsort.h"
static void
drop_free_offered_mimes(Window *w) {
@ -18,6 +19,12 @@ drop_free_offered_mimes(Window *w) {
w->drop.num_offerred_mimes = 0;
}
static void
drop_free_accepted_mimes(Window *w) {
free(w->drop.accepted_mimes); w->drop.accepted_mimes = NULL;
w->drop.accepted_mimes_sz = 0;
}
static void
free_pending(PendingData *pending) {
if (pending->items) {
@ -30,6 +37,7 @@ free_pending(PendingData *pending) {
void
drop_free_data(Window *w) {
drop_free_offered_mimes(w);
drop_free_accepted_mimes(w);
free_pending(&w->drop.pending);
}
@ -154,11 +162,61 @@ drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) {
}
}
void
drop_set_status(Window *w, int operation, const char *payload, size_t payload_sz, bool more) {
if (!w->drop.accept_in_progress) {
drop_free_accepted_mimes(w); w->drop.accept_in_progress = true; w->drop.accepted_operation = 0;
switch(operation) {
case 1: w->drop.accepted_operation = GLFW_DRAG_OPERATION_COPY; break;
case 2: w->drop.accepted_operation = GLFW_DRAG_OPERATION_MOVE; break;
default: w->drop.accepted_operation = GLFW_DRAG_OPERATION_NONE; break;
}
}
w->drop.accepted_mimes = realloc(w->drop.accepted_mimes, w->drop.accepted_mimes_sz + payload_sz + 2);
if (w->drop.accepted_mimes) {
memcpy(w->drop.accepted_mimes + w->drop.accepted_mimes_sz, payload, payload_sz);
w->drop.accepted_mimes_sz += payload_sz;
}
if (!more) {
w->drop.accept_in_progress = false;
if (w->drop.accepted_mimes) {
for (size_t i = 0; i < w->drop.accepted_mimes_sz; i++)
if (w->drop.accepted_mimes[i] == ' ') w->drop.accepted_mimes[i] = 0;
w->drop.accepted_mimes[w->drop.accepted_mimes_sz++] = 0;
}
OSWindow *osw = os_window_for_kitty_window(w->id);
if (osw) request_drop_status_update(osw);
}
}
size_t
drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_count) {
if (w->drop.accept_in_progress) return allowed_mimes_count;
if (w->drop.accepted_operation == GLFW_DRAG_OPERATION_NONE) return 0;
typedef struct mime_sorter { const char *m; ssize_t key; } mime_sorter;
if (!w->drop.accepted_mimes) return allowed_mimes_count;
RAII_ALLOC(mime_sorter, ms, malloc(sizeof(mime_sorter) * allowed_mimes_count));
if (!ms) return allowed_mimes_count;
const ssize_t sentinel = allowed_mimes_count;
for (size_t i = 0; i < allowed_mimes_count; i++) {
ms[i].m = allowed_mimes[i];
const char *p = strstr(w->drop.accepted_mimes, ms[i].m);
ms[i].key = p ? p - w->drop.accepted_mimes : sentinel;
}
#define mimes_lt(a, b) ((a)->key < (b)->key)
QSORT(mime_sorter, ms, allowed_mimes_count, mimes_lt);
#undef mimes_lt
while(allowed_mimes_count && ms[allowed_mimes_count-1].key == sentinel) allowed_mimes_count--;
for (size_t i = 0; i < allowed_mimes_count; i++) allowed_mimes[i] = ms[i].m;
return allowed_mimes_count;
}
void
drop_left_child(Window *w) {
w->drop.hovered = false;
drop_free_offered_mimes(w);
if (w->drop.allowed) {
if (w->drop.wanted) {
char buf[128];
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=m:x=-1:y=-1", DND_CODE, w->drop.client_id);
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0);

View file

@ -11,3 +11,5 @@
void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes);
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);
size_t drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_count);

3
kitty/glfw-wrapper.c generated
View file

@ -359,6 +359,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetDropEventCallback_impl) = dlsym(handle, "glfwSetDropEventCallback");
if (glfwSetDropEventCallback_impl == NULL) fail("Failed to load glfw function glfwSetDropEventCallback with error: %s", dlerror());
*(void **) (&glfwRequestDropUpdate_impl) = dlsym(handle, "glfwRequestDropUpdate");
if (glfwRequestDropUpdate_impl == NULL) fail("Failed to load glfw function glfwRequestDropUpdate with error: %s", dlerror());
*(void **) (&glfwRequestDropData_impl) = dlsym(handle, "glfwRequestDropData");
if (glfwRequestDropData_impl == NULL) fail("Failed to load glfw function glfwRequestDropData with error: %s", dlerror());

4
kitty/glfw-wrapper.h generated
View file

@ -2290,6 +2290,10 @@ typedef GLFWdropeventfun (*glfwSetDropEventCallback_func)(GLFWwindow*, GLFWdrope
GFW_EXTERN glfwSetDropEventCallback_func glfwSetDropEventCallback_impl;
#define glfwSetDropEventCallback glfwSetDropEventCallback_impl
typedef void (*glfwRequestDropUpdate_func)(GLFWwindow*);
GFW_EXTERN glfwRequestDropUpdate_func glfwRequestDropUpdate_impl;
#define glfwRequestDropUpdate glfwRequestDropUpdate_impl
typedef int (*glfwRequestDropData_func)(GLFWwindow*, const char*);
GFW_EXTERN glfwRequestDropData_func glfwRequestDropData_impl;
#define glfwRequestDropData glfwRequestDropData_impl

View file

@ -771,7 +771,7 @@ register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_s
Window *w = window_for_window_id(window_id);
OSWindow *osw = os_window_for_kitty_window(window_id);
if (w && osw && osw->handle) {
w->drop.allowed = on;
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) {
@ -795,7 +795,7 @@ static void
on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
if (!set_callback_window(window)) return;
OSWindow *os_window = global_state.callback_os_window;
Window *w = NULL;
Window *w = NULL; id_type wid = global_state.mouse_hover_in_window;
bool is_kitty_ui_drag = false;
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;}
@ -803,11 +803,13 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
switch (ev->type) {
case GLFW_DROP_ENTER:
case GLFW_DROP_MOVE:
global_state.drop_dest.drop_has_happened = false;
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 && global_state.mouse_hover_in_window && (w = window_for_window_id(global_state.mouse_hover_in_window)) && w->drop.allowed) {
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);
ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes);
return;
}
call_boss(on_drop_move, "KiiOO",
@ -816,8 +818,8 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
/* fallthrough */
case GLFW_DROP_STATUS_UPDATE:
if (is_kitty_ui_drag) update_allowed_mimes_for_drop(ev);
else {
}
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);
break;
case GLFW_DROP_LEAVE:
for (size_t tc = 0; tc < os_window->num_tabs; tc++) {
@ -833,6 +835,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
break;
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;
@ -856,6 +859,12 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
}
}
void
request_drop_status_update(OSWindow *osw) {
if (osw && osw->handle && !global_state.drop_dest.drop_has_happened && global_state.drop_dest.os_window_id == osw->id)
glfwRequestDropUpdate(osw->handle);
}
static void
application_close_requested_callback(int flags) {
if (flags) {

View file

@ -15,6 +15,7 @@
#include "data-types.h"
#include "control-codes.h"
#include "screen.h"
#include "dnd.h"
#include "state.h"
#include "iqsort.h"
#include "fonts.h"
@ -1513,6 +1514,10 @@ screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *pa
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);
} break;
}
}

View file

@ -19,6 +19,8 @@ typedef struct DnDCommand {
unsigned more;
uint32_t client_id;
size_t payload_sz;
int32_t cell_x, cell_y, pixel_x, pixel_y;
uint32_t operation;
} DnDCommand;
typedef struct {

View file

@ -246,10 +246,15 @@ typedef struct Window {
bool is_hovering;
} scrollbar;
struct {
bool allowed, hovered;
bool wanted, hovered;
uint32_t client_id;
const char **offerred_mimes; size_t num_offerred_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;
} drop;
} Window;
@ -402,6 +407,7 @@ typedef struct GlobalState {
id_type os_window_id;
double x, y;
size_t num_left;
bool drop_has_happened;
} drop_dest;
struct {
@ -529,3 +535,4 @@ void setup_os_window_for_rendering(OSWindow*, Tab*, Window*, bool);
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);