More work on the dnd kitten
This commit is contained in:
parent
ff7c6425e6
commit
d3114ec948
6 changed files with 80 additions and 23 deletions
|
|
@ -123,9 +123,16 @@ func (dnd *dnd) handle_data_request(idx int, send_remote_data bool) (err error)
|
||||||
mime := dnd.drag_status.offered_mimes[idx]
|
mime := dnd.drag_status.offered_mimes[idx]
|
||||||
ds := dnd.drag_sources[mime]
|
ds := dnd.drag_sources[mime]
|
||||||
send_remote_data = send_remote_data && mime == "text/uri-list" && len(ds.uri_list) > 0
|
send_remote_data = send_remote_data && mime == "text/uri-list" && len(ds.uri_list) > 0
|
||||||
|
for _, dr := range dnd.drag_status.data_requests {
|
||||||
|
if dr.index == idx {
|
||||||
|
dnd.finish_drag("EINVAL")
|
||||||
|
return fmt.Errorf("terminal sent a duplicate drag data request")
|
||||||
|
}
|
||||||
|
}
|
||||||
dr := &data_request{drag_source: ds, send_remote_data: send_remote_data, index: idx}
|
dr := &data_request{drag_source: ds, send_remote_data: send_remote_data, index: idx}
|
||||||
if ds.path == "" {
|
if ds.path == "" {
|
||||||
dnd.lp.QueueDnDData(DC{Type: 'e', Y: idx, Payload: utils.UnsafeStringToBytes(base64.RawStdEncoding.EncodeToString(ds.data))})
|
dnd.lp.QueueDnDData(DC{Type: 'e', Y: idx, Payload: utils.UnsafeStringToBytes(base64.RawStdEncoding.EncodeToString(ds.data))})
|
||||||
|
dnd.lp.QueueDnDData(DC{Type: 'e', Y: idx}) // EOF
|
||||||
if !dr.send_remote_data {
|
if !dr.send_remote_data {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
kitty/dnd.c
65
kitty/dnd.c
|
|
@ -14,6 +14,7 @@
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
@ -100,6 +101,11 @@ rmtree_best_effort(const char *relpath, int dirfd) {
|
||||||
safe_close(dirfd, __FILE__, __LINE__);
|
safe_close(dirfd, __FILE__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
dnd_is_test_mode(void) {
|
||||||
|
return g_dnd_test_write_func != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
mktempdir_in_cache(const char *prefix, int *fd) {
|
mktempdir_in_cache(const char *prefix, int *fd) {
|
||||||
char *ans = NULL;
|
char *ans = NULL;
|
||||||
|
|
@ -176,11 +182,6 @@ dnd_set_test_write_func(PyObject *func, size_t mime_list_size_cap, size_t presen
|
||||||
REMOTE_DRAG_LIMIT = remote_drag_limit ? remote_drag_limit : DEFAULT_REMOTE_DRAG_LIMIT;
|
REMOTE_DRAG_LIMIT = remote_drag_limit ? remote_drag_limit : DEFAULT_REMOTE_DRAG_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
dnd_is_test_mode(void) {
|
|
||||||
return g_dnd_test_write_func != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) {
|
string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) {
|
||||||
if (an != bn) return (int)an - (int)bn;
|
if (an != bn) return (int)an - (int)bn;
|
||||||
|
|
@ -1521,6 +1522,7 @@ drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) {
|
||||||
// No unread data
|
// No unread data
|
||||||
if (!ds.items[i].data_decode_initialized) {
|
if (!ds.items[i].data_decode_initialized) {
|
||||||
// Transfer complete and all data read
|
// Transfer complete and all data read
|
||||||
|
ds.items[i].data_requested_from_client = false;
|
||||||
*err_code = 0;
|
*err_code = 0;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1529,11 +1531,14 @@ drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// No fd yet, request data from the client
|
// No fd yet, request data from the client
|
||||||
char buf[128];
|
if (!ds.items[i].data_requested_from_client) {
|
||||||
ds.items[i].requested_remote_files = ds.is_remote_client && ds.items[i].is_uri_list;
|
char buf[128];
|
||||||
int header_sz = snprintf(buf, sizeof(buf), "\x1b]%d;t=e:x=%d:y=%zu:Y=%d",
|
ds.items[i].requested_remote_files = ds.is_remote_client && ds.items[i].is_uri_list;
|
||||||
DND_CODE, DRAG_NOTIFY_FINISHED + 2, i, ds.items[i].requested_remote_files);
|
int header_sz = snprintf(buf, sizeof(buf), "\x1b]%d;t=e:x=%d:y=%zu:Y=%d",
|
||||||
queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false);
|
DND_CODE, DRAG_NOTIFY_FINISHED + 2, i, ds.items[i].requested_remote_files);
|
||||||
|
queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false);
|
||||||
|
ds.items[i].data_requested_from_client = true;
|
||||||
|
}
|
||||||
*err_code = EAGAIN;
|
*err_code = EAGAIN;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1582,8 +1587,10 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
||||||
int err = parse_errno_name(payload, payload_sz);
|
int err = parse_errno_name(payload, payload_sz);
|
||||||
ds.items[idx].fd_plus_one = -err;
|
ds.items[idx].fd_plus_one = -err;
|
||||||
ds.items[idx].data_decode_initialized = false;
|
ds.items[idx].data_decode_initialized = false;
|
||||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
if (!dnd_is_test_mode()) {
|
||||||
if (ret) cancel_drag(w, ret);
|
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||||
|
if (ret) cancel_drag(w, ret);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1592,8 +1599,10 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
||||||
ds.items[idx].data_decode_initialized = false;
|
ds.items[idx].data_decode_initialized = false;
|
||||||
if (ds.items[idx].fd_plus_one > 0) {
|
if (ds.items[idx].fd_plus_one > 0) {
|
||||||
if (!ds.items[idx].requested_remote_files) {
|
if (!ds.items[idx].requested_remote_files) {
|
||||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
if (!dnd_is_test_mode()) {
|
||||||
if (ret) cancel_drag(w, ret);
|
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||||
|
if (ret) cancel_drag(w, ret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -1631,8 +1640,10 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
||||||
ds.items[idx].data_capacity += outlen;
|
ds.items[idx].data_capacity += outlen;
|
||||||
// Notify as soon as any data is available
|
// Notify as soon as any data is available
|
||||||
if (!ds.items[idx].requested_remote_files) {
|
if (!ds.items[idx].requested_remote_files) {
|
||||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
if (!dnd_is_test_mode()) {
|
||||||
if (ret) cancel_drag(w, ret);
|
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||||
|
if (ret) cancel_drag(w, ret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1727,7 +1738,7 @@ finish_remote_data(Window *w, size_t item_idx) {
|
||||||
if ((ret = write_all(fd, "\r\n", 2))) abrt(ret);
|
if ((ret = write_all(fd, "\r\n", 2))) abrt(ret);
|
||||||
}
|
}
|
||||||
free(ds.items[item_idx].uri_list); ds.items[item_idx].uri_list = NULL; ds.items[item_idx].num_uris = 0;
|
free(ds.items[item_idx].uri_list); ds.items[item_idx].uri_list = NULL; ds.items[item_idx].num_uris = 0;
|
||||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[item_idx].mime_type);
|
int ret = dnd_is_test_mode() ? 0 : notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[item_idx].mime_type);
|
||||||
abrt(ret);
|
abrt(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2262,6 +2273,25 @@ dnd_test_start_drag_offer(PyObject *self UNUSED, PyObject *args) {
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
dnd_test_drag_get_data(PyObject *self UNUSED, PyObject *args) {
|
||||||
|
const char *mime; unsigned long long window_id;
|
||||||
|
if (!PyArg_ParseTuple(args, "Ks", &window_id, &mime)) return NULL;
|
||||||
|
Window *w = window_for_window_id((id_type)window_id);
|
||||||
|
if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; }
|
||||||
|
int err; size_t sz;
|
||||||
|
const char *data = drag_get_data(w, mime, &sz, &err);
|
||||||
|
if (err != 0) {
|
||||||
|
if (data) drag_free_data(w, mime, data, sz);
|
||||||
|
errno = err;
|
||||||
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *ans = PyBytes_FromStringAndSize(data ? data : "", sz);
|
||||||
|
if (data) drag_free_data(w, mime, data, sz);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef dnd_methods[] = {
|
static PyMethodDef dnd_methods[] = {
|
||||||
{"dnd_set_test_write_func", (PyCFunction)py_dnd_set_test_write_func, METH_VARARGS, ""},
|
{"dnd_set_test_write_func", (PyCFunction)py_dnd_set_test_write_func, METH_VARARGS, ""},
|
||||||
METHODB(dnd_test_create_fake_window, METH_NOARGS),
|
METHODB(dnd_test_create_fake_window, METH_NOARGS),
|
||||||
|
|
@ -2275,6 +2305,7 @@ static PyMethodDef dnd_methods[] = {
|
||||||
METHODB(dnd_test_drag_notify, METH_VARARGS),
|
METHODB(dnd_test_drag_notify, METH_VARARGS),
|
||||||
METHODB(dnd_test_drag_finish, METH_VARARGS),
|
METHODB(dnd_test_drag_finish, METH_VARARGS),
|
||||||
METHODB(dnd_test_probe_state, METH_VARARGS),
|
METHODB(dnd_test_probe_state, METH_VARARGS),
|
||||||
|
METHODB(dnd_test_drag_get_data, METH_VARARGS),
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ void drop_set_status(Window *w, int operation, const char *payload, size_t paylo
|
||||||
size_t drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_count);
|
size_t drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_count);
|
||||||
void drop_dispatch_data(Window *w, const char *mime_type, const char *data, ssize_t sz);
|
void drop_dispatch_data(Window *w, const char *mime_type, const char *data, ssize_t sz);
|
||||||
void drop_finish(Window *w);
|
void drop_finish(Window *w);
|
||||||
bool dnd_is_test_mode(void);
|
|
||||||
|
|
||||||
typedef enum { DRAG_NOTIFY_ACCEPTED, DRAG_NOTIFY_ACTION_CHANGED, DRAG_NOTIFY_DROPPED, DRAG_NOTIFY_FINISHED } DragNotifyType;
|
typedef enum { DRAG_NOTIFY_ACCEPTED, DRAG_NOTIFY_ACTION_CHANGED, DRAG_NOTIFY_DROPPED, DRAG_NOTIFY_FINISHED } DragNotifyType;
|
||||||
void drag_free_offer(Window *w);
|
void drag_free_offer(Window *w);
|
||||||
|
|
|
||||||
|
|
@ -946,7 +946,6 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
||||||
|
|
||||||
switch (ev->type) {
|
switch (ev->type) {
|
||||||
case GLFW_DRAG_DATA_REQUEST:
|
case GLFW_DRAG_DATA_REQUEST:
|
||||||
ev->err_num = ENOENT;
|
|
||||||
if (is_client_drag) {
|
if (is_client_drag) {
|
||||||
ev->err_num = 0;
|
ev->err_num = 0;
|
||||||
if (ev->data_sz) {
|
if (ev->data_sz) {
|
||||||
|
|
@ -954,7 +953,7 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
||||||
} else {
|
} else {
|
||||||
ev->data = drag_get_data(w, ev->mime_type, &ev->data_sz, &ev->err_num);
|
ev->data = drag_get_data(w, ev->mime_type, &ev->data_sz, &ev->err_num);
|
||||||
}
|
}
|
||||||
}
|
} else ev->err_num = ENOENT;
|
||||||
break;
|
break;
|
||||||
case GLFW_DRAG_ACCEPTED:
|
case GLFW_DRAG_ACCEPTED:
|
||||||
free(ds.accepted_mime_type);
|
free(ds.accepted_mime_type);
|
||||||
|
|
@ -994,7 +993,6 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
||||||
|
|
||||||
int
|
int
|
||||||
notify_drag_data_ready(id_type os_window_id, const char *mime_type) {
|
notify_drag_data_ready(id_type os_window_id, const char *mime_type) {
|
||||||
if (dnd_is_test_mode()) return 0; // In test mode, always succeed
|
|
||||||
OSWindow *w = os_window_for_id(os_window_id);
|
OSWindow *w = os_window_for_id(os_window_id);
|
||||||
GLFWDragSourceItem item = {.mime_type = mime_type};
|
GLFWDragSourceItem item = {.mime_type = mime_type};
|
||||||
if (w && w->handle) return glfwStartDrag(w->handle, &item, 1, NULL, -1, false);
|
if (w && w->handle) return glfwStartDrag(w->handle, &item, 1, NULL, -1, false);
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,7 @@ typedef struct Window {
|
||||||
struct { int32_t x, y, X, Y; bool active; } in_flight_remote_file_data;
|
struct { int32_t x, y, X, Y; bool active; } in_flight_remote_file_data;
|
||||||
struct {
|
struct {
|
||||||
const char *mime_type; uint8_t *optional_data; size_t data_size, data_capacity; base64_state base64_state;
|
const char *mime_type; uint8_t *optional_data; size_t data_size, data_capacity; base64_state base64_state;
|
||||||
bool data_decode_initialized, is_uri_list, requested_remote_files;
|
bool data_decode_initialized, is_uri_list, requested_remote_files, data_requested_from_client;
|
||||||
int fd_plus_one;
|
int fd_plus_one;
|
||||||
char** uri_list; size_t num_uris;
|
char** uri_list; size_t num_uris;
|
||||||
DragRemoteItem *remote_items; size_t num_remote_items;
|
DragRemoteItem *remote_items; size_t num_remote_items;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import errno
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
dnd_test_cleanup_fake_window,
|
dnd_test_cleanup_fake_window,
|
||||||
dnd_test_create_fake_window,
|
dnd_test_create_fake_window,
|
||||||
dnd_test_drag_finish,
|
dnd_test_drag_finish,
|
||||||
|
dnd_test_drag_get_data,
|
||||||
dnd_test_drag_notify,
|
dnd_test_drag_notify,
|
||||||
dnd_test_fake_drop_data,
|
dnd_test_fake_drop_data,
|
||||||
dnd_test_fake_drop_event,
|
dnd_test_fake_drop_event,
|
||||||
|
|
@ -328,11 +330,29 @@ def assert_files_have_same_content(self, a, b):
|
||||||
def test_dnd_kitten_drag(self):
|
def test_dnd_kitten_drag(self):
|
||||||
img_drag_path = 'image.png'
|
img_drag_path = 'image.png'
|
||||||
with open(os.path.join(self.kitten_wd, img_drag_path), 'wb') as f:
|
with open(os.path.join(self.kitten_wd, img_drag_path), 'wb') as f:
|
||||||
f.write(os.urandom(1113))
|
self.img_drag_data = os.urandom(10113)
|
||||||
|
f.write(self.img_drag_data)
|
||||||
create_fs(self.src_data_dir)
|
create_fs(self.src_data_dir)
|
||||||
self.finish_setup(cli_args=(f'--drag=image/png:{img_drag_path}', self.src_data_dir)) # )))
|
self.finish_setup(cli_args=(f'--drag=image/png:{img_drag_path}', self.src_data_dir)) # )))
|
||||||
self.dnd_kitten_drag(False, img_drag_path)
|
self.dnd_kitten_drag(False, img_drag_path)
|
||||||
self.exit_kitten()
|
self.exit_kitten()
|
||||||
|
self.img_drag_data = None
|
||||||
|
|
||||||
|
def read_drag_data(self, mime):
|
||||||
|
ans = b''
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
chunk = dnd_test_drag_get_data(self.capture.window_id, mime)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
ans += chunk
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno == errno.EAGAIN:
|
||||||
|
self.pty.process_input_from_child()
|
||||||
|
continue
|
||||||
|
del chunk, ans
|
||||||
|
raise
|
||||||
|
return ans
|
||||||
|
|
||||||
def dnd_kitten_drag(self, remote_client, img_drop_path):
|
def dnd_kitten_drag(self, remote_client, img_drop_path):
|
||||||
# self.pty.log_data_flow = True
|
# self.pty.log_data_flow = True
|
||||||
|
|
@ -365,4 +385,6 @@ def end_drag(canceled=True):
|
||||||
dnd_test_drag_notify(self.capture.window_id, 2)
|
dnd_test_drag_notify(self.capture.window_id, 2)
|
||||||
self.send_dnd_command_to_kitten('DRAG_STATUS')
|
self.send_dnd_command_to_kitten('DRAG_STATUS')
|
||||||
self.wait_for_responses('text/uri-list:2:true')
|
self.wait_for_responses('text/uri-list:2:true')
|
||||||
|
self.assertEqual(self.img_drag_data, self.read_drag_data('image/png'))
|
||||||
|
# self.read_drag_data('text/uri-list')
|
||||||
end_drag(False)
|
end_drag(False)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue