More work on DnD protocol

This commit is contained in:
Kovid Goyal 2026-04-08 19:07:15 +05:30
parent 390ae2efac
commit 348151c457
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 122 additions and 18 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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