More work on DnD protocol implementation

This commit is contained in:
Kovid Goyal 2026-04-04 13:32:18 +05:30
parent 9666cf83ad
commit ce041fab84
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 106 additions and 16 deletions

View file

@ -259,6 +259,7 @@ image by sending::
OSC _dnd_code ; t=P:x=idx ST
Where ``idx`` is now a zero based index with zero being the first image and so on.
Sending an ``idx`` out of bounds means the drag image should be removed.
Once the terminal program has sent all data and images for the drag
operation, it indicates the drag should be started by sending ``t=P:x=-1``. At

View file

@ -10,6 +10,7 @@
#include "control-codes.h"
#include "safe-wrappers.h"
#include "iqsort.h"
#include "png-reader.h"
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
@ -865,7 +866,7 @@ void
drag_free_offer(Window *w) {
free(ds.mimes_buf); ds.mimes_buf = NULL;
ds.allowed_operations = 0;
ds.offer_being_built = false;
ds.state = DRAG_SOURCE_NONE;
if (ds.items) {
for (size_t i=0; i < ds.num_mimes; i++) free(ds.items[i].optional_data);
free(ds.items);
@ -879,13 +880,20 @@ drag_free_offer(Window *w) {
ds.images_sent_total_sz = 0;
}
static void
cancel_drag(Window *w, int error_code) {
if (error_code) drop_send_error(w, error_code);
if (global_state.drag_source.is_active && global_state.drag_source.from_window == w->id) cancel_current_drag_source();
drag_free_offer(w);
}
void
drag_add_mimes(Window *w, int allowed_operations, const char *data, size_t sz, bool has_more) {
#define abrt(code) { drop_send_error(w, code); drag_free_offer(w); return; }
if (allowed_operations && ds.offer_being_built) drag_free_offer(w);
#define abrt(code) { cancel_drag(w, code); return; }
if (allowed_operations && ds.state != DRAG_SOURCE_NONE) cancel_drag(w, 0);
if (allowed_operations && !ds.allowed_operations) ds.allowed_operations = allowed_operations;
if (!ds.allowed_operations) { abrt(EINVAL); }
ds.offer_being_built = true;
ds.state = DRAG_SOURCE_BEING_BUILT;
size_t new_sz = ds.bufsz + sz;
if (new_sz > MIME_LIST_SIZE_CAP) abrt(EFBIG);
ds.mimes_buf = realloc(ds.mimes_buf, ds.bufsz + sz + 1);
@ -917,7 +925,7 @@ drag_add_mimes(Window *w, int allowed_operations, const char *data, size_t sz, b
void
drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t sz) {
if (!ds.offer_being_built || idx >= ds.num_mimes) abrt(EINVAL);
if (ds.state != DRAG_SOURCE_BEING_BUILT || idx >= ds.num_mimes) abrt(EINVAL);
if (sz + ds.pre_sent_total_sz > PRESENT_DATA_CAP) abrt(EFBIG);
ds.pre_sent_total_sz += sz;
#define item ds.items[idx]
@ -937,12 +945,14 @@ drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t s
#undef item
}
#define img ds.images[idx]
void
drag_add_image(Window *w, unsigned idx, int fmt, int width, int height, const uint8_t *payload, size_t sz) {
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL);
if (idx + 1 >= arraysz(ds.images)) abrt(EFBIG);
if (ds.images_sent_total_sz + sz > PRESENT_DATA_CAP) abrt(EFBIG);
ds.images_sent_total_sz += sz;
#define img ds.images[idx]
if (!img.started) {
if (fmt != 24 && fmt != 32 && fmt != 100) abrt(EINVAL);
if (width < 1 || height < 1) abrt(EINVAL);
@ -960,8 +970,66 @@ drag_add_image(Window *w, unsigned idx, int fmt, int width, int height, const ui
size_t outlen = img.capacity - img.sz;
if (!base64_decode_stream(&img.base64_state, payload, sz, img.data + img.sz, &outlen)) abrt(EINVAL);
img.sz += outlen;
#undef img
}
void
drag_change_image(Window *w, unsigned idx) {
ds.img_idx = idx;
if (ds.state == DRAG_SOURCE_STARTED) change_drag_image(idx);
}
static bool
expand_rgb_data(Window *w, size_t idx) {
#define fail(code) { cancel_drag(w, code); return false; }
if (img.sz != (size_t)img.width * (size_t)img.height * 3) fail(EINVAL);
const size_t sz = img.width * img.height * 4;
RAII_ALLOC(uint8_t, expanded, malloc(sz));
if (!expanded) fail(ENOMEM);
memset(expanded, 0xff, sz);
for (int r = 0; r < img.height; r++) {
uint8_t *src_row = img.data + r * img.width * 3, *dest_row = expanded + r * img.width * 4;
for (int c = 0; c < img.width; c++) memcpy(dest_row + c * 4, src_row + c * 3, 3);
}
SWAP(img.data, expanded); img.sz = sz; img.fmt = 32;
return true;
}
static bool
expand_png_data(Window *w, size_t idx) {
png_read_data d = {0};
inflate_png_inner(&d, img.data, img.sz, 2000);
if (d.ok) {
free(img.data);
img.data = d.decompressed;
img.sz = d.sz;
img.width = d.width; img.height = d.height;
} else free(d.decompressed);
free(d.row_pointers);
return d.ok;
}
#undef fail
void
drag_start(Window *w) {
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL);
size_t total_size = 0;
for (size_t idx = 0; idx < arraysz(ds.images); idx++) {
if (img.sz) {
switch (img.fmt) {
case 24:
if (!expand_rgb_data(w, idx)) return;
break;
case 100:
if (!expand_png_data(w, idx)) return;
break;
}
total_size += img.sz;
if (total_size > 2 * PRESENT_DATA_CAP) abrt(EFBIG);
if (img.sz != (size_t)img.width * (size_t)img.height * 4u) abrt(EINVAL);
}
}
}
#undef img
#undef abrt
#undef ds

View file

@ -27,3 +27,5 @@ void drag_free_offer(Window *w);
void drag_add_mimes(Window *w, int allowed_operations, 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);

View file

@ -3110,6 +3110,21 @@ get_thumbnail(PyObject *thumbnails, GLFWimage *thumbnail, int idx) {
return true;
}
bool
change_drag_image(int idx) {
if (!global_state.drag_source.from_os_window) return false;
OSWindow *w = os_window_for_id(global_state.drag_source.from_os_window);
if (!w || !w->handle) { errno = EINVAL; return false; }
if (global_state.drag_source.thumbnail_idx == idx) return true;
GLFWimage thumbnail = {0};
if (idx >=0 && global_state.drag_source.thumbnails && idx < PySequence_Size(global_state.drag_source.thumbnails)) {
if (!get_thumbnail(global_state.drag_source.thumbnails, &thumbnail, idx)) { errno = ENOMEM; PyErr_Clear(); return NULL; }
global_state.drag_source.thumbnail_idx = idx;
} else global_state.drag_source.thumbnail_idx = -1;
errno = glfwStartDrag(w->handle, NULL, 0, thumbnail.pixels ? &thumbnail : NULL, -2, false);
return errno == 0;
}
static PyObject*
change_drag_thumbnail(PyObject *self UNUSED, PyObject *args) {
unsigned long long os_window_id; int idx = -1;

View file

@ -1552,6 +1552,11 @@ screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *pa
if (cmd->cell_x >= 0) drag_add_pre_sent_data(w, cmd->cell_x, payload, cmd->payload_sz);
else drag_add_image(w, -cmd->cell_x, cmd->cell_y, cmd->pixel_x, cmd->pixel_y, payload, cmd->payload_sz);
} break;
case 'P': {
if (cmd->cell_x >= 0) drag_change_image(w, cmd->cell_x);
else drag_start(w);
} break;
}
}

View file

@ -234,13 +234,7 @@ typedef struct DirHandle {
uint32_t id; /* handle id, 1-based; 0 = invalid */
} DirHandle;
typedef struct DragSourceItem {
const char *mime_type;
uint8_t *optional_data;
size_t data_size, data_capacity;
base64_state base64_state;
bool data_decode_initialized;
} DragSourceItem;
typedef enum { DRAG_SOURCE_NONE, DRAG_SOURCE_BEING_BUILT, DRAG_SOURCE_STARTED, DRAG_SOURCE_DROPPED } DragSourceState;
typedef struct Window {
id_type id;
@ -298,13 +292,17 @@ typedef struct Window {
bool can_offer;
struct { double x, y; monotonic_t at; } initial_left_press;
char *mimes_buf; size_t num_mimes, bufsz;
DragSourceItem *items;
struct {
const char *mime_type; uint8_t *optional_data; size_t data_size, data_capacity; base64_state base64_state;
bool data_decode_initialized;
} *items;
struct {
int width, height, fmt; uint8_t *data; size_t sz, capacity; bool started; base64_state base64_state;
} images[16];
size_t pre_sent_total_sz, images_sent_total_sz;
unsigned img_idx;
int allowed_operations;
bool offer_being_built;
DragSourceState state;
} drag_source;
} Window;
@ -595,3 +593,4 @@ void request_drop_status_update(OSWindow *osw);
void register_mimes_for_drop(OSWindow *w, const char **mimes, size_t sz);
void request_drop_data(OSWindow *w, id_type wid, const char* mime);
void cancel_current_drag_source(void);
bool change_drag_image(int idx);