Implement drop data transmission
This commit is contained in:
parent
4f404f8c85
commit
5ad55ec936
9 changed files with 188 additions and 46 deletions
|
|
@ -98,6 +98,22 @@ 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.
|
||||
|
||||
Requesting data is done by sending an escape code of the form::
|
||||
|
||||
OSC _dnd_code ; t=r ; MIME type ST
|
||||
|
||||
This will request data for the specifid MIME type. The terminal must respond
|
||||
with a series of escape codes of the form::
|
||||
|
||||
OSC _dnd_code ; t=r ; data ST
|
||||
|
||||
End of data is indicated by an empty payload. If some error occures while
|
||||
getting the data, the terminal must send an escape code of the form::
|
||||
|
||||
OSC _dnd_code ; t=R ; POSIX error name ST
|
||||
|
||||
Here POSIX error name is a POSIX symbolic error name such as ``ENOENT`` or
|
||||
``EIO`` or the value ``EUNKNOWN`` for an unknown error.
|
||||
|
||||
Metadata reference
|
||||
---------------------------
|
||||
|
|
@ -113,6 +129,7 @@ Key Value Default Description
|
|||
)`` ``A`` - stop accepting drops
|
||||
``m`` - a drop move event
|
||||
``M`` - a drop dropped event
|
||||
``r`` - request dropped data
|
||||
|
||||
``m`` Chunking indicator ``0`` ``0`` or ``i``
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ def parsers() -> None:
|
|||
write_header(text, 'kitty/parse-multicell-command.h')
|
||||
|
||||
keymap = {
|
||||
't': ('type', flag('aAmM')),
|
||||
't': ('type', flag('aAmMrR')),
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('client_id', 'uint'),
|
||||
'o': ('operation', 'uint'),
|
||||
|
|
|
|||
84
kitty/dnd.c
84
kitty/dnd.c
|
|
@ -39,6 +39,8 @@ drop_free_data(Window *w) {
|
|||
drop_free_offered_mimes(w);
|
||||
drop_free_accepted_mimes(w);
|
||||
free_pending(&w->drop.pending);
|
||||
free(w->drop.registered_mimes); w->drop.registered_mimes = NULL;
|
||||
free(w->drop.getting_data_for_mime); w->drop.getting_data_for_mime = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -52,6 +54,40 @@ reset_drop(Window *w) {
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more) {
|
||||
w->drop.wanted = on;
|
||||
w->drop.client_id = client_id;
|
||||
if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); return; }
|
||||
if (!payload || !payload_sz) return;
|
||||
size_t sz = w->drop.registered_mimes ? strlen(w->drop.registered_mimes) : 0;
|
||||
if (sz + payload_sz > 1024 * 1024) return;
|
||||
w->drop.registered_mimes = realloc(w->drop.registered_mimes, sz + payload_sz + 1);
|
||||
if (w->drop.registered_mimes) {
|
||||
memcpy(w->drop.registered_mimes + sz, payload, payload_sz);
|
||||
sz += payload_sz;
|
||||
w->drop.registered_mimes[sz] = 0;
|
||||
}
|
||||
if (more) return;
|
||||
if (w->drop.registered_mimes) {
|
||||
OSWindow *osw = os_window_for_kitty_window(w->id);
|
||||
if (osw) {
|
||||
size_t num = 0;
|
||||
RAII_ALLOC(const char*, mimes, malloc(sizeof(char*) * strlen(w->drop.registered_mimes)));
|
||||
if (mimes) {
|
||||
char* token = strtok(w->drop.registered_mimes, " ");
|
||||
while (token != NULL) {
|
||||
mimes[num++] = token;
|
||||
token = strtok(NULL, " ");
|
||||
}
|
||||
register_mimes_for_drop(osw, mimes, num);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(w->drop.registered_mimes); w->drop.registered_mimes = NULL;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
|
@ -237,6 +273,54 @@ drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_mimes_co
|
|||
return allowed_mimes_count;
|
||||
}
|
||||
|
||||
static const char*
|
||||
get_errno_name(int err) {
|
||||
switch (err) {
|
||||
case EPERM: return "EPERM";
|
||||
case ENOENT: return "ENOENT";
|
||||
case EIO: return "EIO";
|
||||
default: return "EUNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
drop_send_error(Window *w, int error_code) {
|
||||
char buf[128];
|
||||
uint8_t ebuf[32];
|
||||
size_t b64_len = sizeof(ebuf);
|
||||
const char *e = get_errno_name(error_code);
|
||||
base64_encode8((const uint8_t*)e, strlen(e), ebuf, &b64_len, false);
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=R", DND_CODE, w->drop.client_id);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, (char*)ebuf, b64_len);
|
||||
}
|
||||
|
||||
void
|
||||
drop_request_data(Window *w, const char *mime) {
|
||||
if (w->drop.getting_data_for_mime) { free(w->drop.getting_data_for_mime); w->drop.getting_data_for_mime = NULL; }
|
||||
OSWindow *osw = os_window_for_kitty_window(w->id);
|
||||
if (!osw) return;
|
||||
if (w->drop.offerred_mimes) {
|
||||
for (size_t i = 0; i < w->drop.num_offerred_mimes; i++) {
|
||||
if (strcmp(mime, w->drop.offerred_mimes[i]) == 0) {
|
||||
w->drop.getting_data_for_mime = strdup(mime);
|
||||
if (w->drop.getting_data_for_mime) request_drop_data(osw, w->id, mime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
drop_send_error(w, ENOENT);
|
||||
}
|
||||
|
||||
void
|
||||
drop_dispatch_data(Window *w, const char *data, ssize_t sz) {
|
||||
if (sz < 0) drop_send_error(w, -sz);
|
||||
else {
|
||||
char buf[128];
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=r", DND_CODE, w->drop.client_id);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, sz ? data : NULL, sz);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
drop_left_child(Window *w) {
|
||||
w->drop.hovered = false;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@
|
|||
|
||||
#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);
|
||||
void drop_free_data(Window *w);
|
||||
void drop_request_data(Window *w, const char *mime);
|
||||
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);
|
||||
void drop_dispatch_data(Window *w, const char *data, ssize_t sz);
|
||||
|
|
|
|||
55
kitty/glfw.c
55
kitty/glfw.c
|
|
@ -767,28 +767,17 @@ read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) {
|
|||
}
|
||||
|
||||
void
|
||||
register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id) {
|
||||
Window *w = window_for_window_id(window_id);
|
||||
OSWindow *osw = os_window_for_kitty_window(window_id);
|
||||
if (w && osw && osw->handle) {
|
||||
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) {
|
||||
register_mimes_for_drop(OSWindow *w, const char **mimes, size_t sz) {
|
||||
(void)w; (void)mimes; (void)sz;
|
||||
#ifdef __APPLE__
|
||||
RAII_ALLOC(char, copy, malloc(payload_sz + 1)); if (!copy) return;
|
||||
RAII_ALLOC(const char*, mimes, calloc(payload_sz, sizeof(char*))); if (!mimes) return;
|
||||
memcpy(copy, payload, payload_sz); copy[payload_sz] = 0;
|
||||
size_t num = 0;
|
||||
char* token = strtok(copy, " ");
|
||||
while (token != NULL) {
|
||||
mimes[num++] = token;
|
||||
token = strtok(NULL, " ");
|
||||
}
|
||||
glfwCocoaRegisterMIMETypes(osw->handle, mimes, num);
|
||||
if (w->handle) glfwCocoaRegisterMIMETypes(w->handle, mimes, sz);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
request_drop_data(OSWindow *w, id_type wid, const char* mime) {
|
||||
global_state.drop_dest.client_window_data_request = wid;
|
||||
if (w->handle) glfwRequestDropData(w->handle, mime);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -800,6 +789,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
|
|||
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;}
|
||||
}
|
||||
bool is_client_drop = !is_kitty_ui_drag && wid && (w = window_for_window_id(wid)) && w->drop.wanted;
|
||||
switch (ev->type) {
|
||||
case GLFW_DROP_ENTER:
|
||||
case GLFW_DROP_MOVE:
|
||||
|
|
@ -807,7 +797,7 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
|
|||
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 && wid && (w = window_for_window_id(wid)) && w->drop.wanted) {
|
||||
if (is_client_drop) {
|
||||
drop_move_on_child(w, ev->mimes, ev->num_mimes, false);
|
||||
ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes);
|
||||
return;
|
||||
|
|
@ -817,9 +807,8 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
|
|||
ev->from_self ? Py_True : Py_False, Py_False);
|
||||
/* fallthrough */
|
||||
case GLFW_DROP_STATUS_UPDATE:
|
||||
if (is_kitty_ui_drag) update_allowed_mimes_for_drop(ev);
|
||||
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);
|
||||
if (is_client_drop) ev->num_mimes = drop_update_mimes(w, ev->mimes, ev->num_mimes);
|
||||
else update_allowed_mimes_for_drop(ev);
|
||||
break;
|
||||
case GLFW_DROP_LEAVE:
|
||||
for (size_t tc = 0; tc < os_window->num_tabs; tc++) {
|
||||
|
|
@ -836,6 +825,7 @@ 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;
|
||||
global_state.drop_dest.client_window_data_request = 0;
|
||||
if (is_kitty_ui_drag) {
|
||||
if (ev->from_self) {
|
||||
if (global_state.drag_source.drag_data) {
|
||||
|
|
@ -852,13 +842,20 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) {
|
|||
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);
|
||||
}
|
||||
} else if (is_client_drop) drop_move_on_child(w, ev->mimes, ev->num_mimes, true);
|
||||
break;
|
||||
case GLFW_DROP_DATA_AVAILABLE:
|
||||
if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC);
|
||||
else read_drop_data(window, ev);
|
||||
if (global_state.drop_dest.client_window_data_request) {
|
||||
if ((w = window_for_window_id(global_state.drop_dest.client_window_data_request))) {
|
||||
char buf[3072];
|
||||
ssize_t ret = ev->read_data(window, ev, buf, sizeof(buf));
|
||||
drop_dispatch_data(w, buf, ret);
|
||||
if (ret <= 0) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC);
|
||||
}
|
||||
} else {
|
||||
if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC);
|
||||
else read_drop_data(window, ev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,16 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
(void)is_negative;
|
||||
size_t sz;
|
||||
|
||||
enum KEYS { type = 't', more = 'm', client_id = 'i' };
|
||||
enum KEYS {
|
||||
type = 't',
|
||||
more = 'm',
|
||||
client_id = 'i',
|
||||
operation = 'o',
|
||||
cell_x = 'x',
|
||||
cell_y = 'y',
|
||||
pixel_x = 'X',
|
||||
pixel_y = 'Y'
|
||||
};
|
||||
|
||||
enum KEYS key = 'a';
|
||||
if (parser_buf[pos] == ';')
|
||||
|
|
@ -39,6 +48,21 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
case client_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case operation:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case cell_x:
|
||||
value_state = INT;
|
||||
break;
|
||||
case cell_y:
|
||||
value_state = INT;
|
||||
break;
|
||||
case pixel_x:
|
||||
value_state = INT;
|
||||
break;
|
||||
case pixel_y:
|
||||
value_state = INT;
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR(
|
||||
"Malformed DnDCommand control block, invalid key character: 0x%x",
|
||||
|
|
@ -62,7 +86,8 @@ 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 != 'a') {
|
||||
if (g.type != 'A' && g.type != 'M' && g.type != 'R' && g.type != 'a' &&
|
||||
g.type != 'm' && g.type != 'r') {
|
||||
REPORT_ERROR("Malformed DnDCommand control block, unknown flag value "
|
||||
"for type: 0x%x",
|
||||
g.type);
|
||||
|
|
@ -109,7 +134,10 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
break
|
||||
READ_UINT;
|
||||
switch (key) {
|
||||
;
|
||||
I(cell_x);
|
||||
I(cell_y);
|
||||
I(pixel_x);
|
||||
I(pixel_y);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -125,6 +153,7 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
switch (key) {
|
||||
U(more);
|
||||
U(client_id);
|
||||
U(operation);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -182,14 +211,18 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
break;
|
||||
}
|
||||
|
||||
REPORT_VA_COMMAND("K s {sc sI sI ss#}", self->window_id, "dnd_command",
|
||||
REPORT_VA_COMMAND(
|
||||
"K s {sc sI sI sI si si si si ss#}", self->window_id, "dnd_command",
|
||||
|
||||
"type", g.type,
|
||||
"type", g.type,
|
||||
|
||||
"more", (unsigned int)g.more, "client_id",
|
||||
(unsigned int)g.client_id,
|
||||
"more", (unsigned int)g.more, "client_id", (unsigned int)g.client_id,
|
||||
"operation", (unsigned int)g.operation,
|
||||
|
||||
"", (char *)parser_buf, g.payload_sz);
|
||||
"cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x",
|
||||
(int)g.pixel_x, "pixel_y", (int)g.pixel_y,
|
||||
|
||||
"", (char *)parser_buf, g.payload_sz);
|
||||
|
||||
screen_handle_dnd_command(self->screen, &g, parser_buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1511,12 +1511,18 @@ screen_dirty_line_graphics(Screen *self, const unsigned int top, const unsigned
|
|||
void
|
||||
screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *payload) {
|
||||
if (!self->window_id) return;
|
||||
Window *w = window_for_window_id(self->window_id);
|
||||
if (!w) return;
|
||||
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);
|
||||
case 'a': drop_register_window(w, payload, cmd->payload_sz, true, cmd->client_id, cmd->more); break;
|
||||
case 'A': drop_register_window(w, NULL, 0, false, cmd->client_id, cmd->more); break;
|
||||
case 'm': drop_set_status(w, cmd->operation, (const char*)payload, cmd->payload_sz, cmd->more); break;
|
||||
case 'r': {
|
||||
char buf[256];
|
||||
if (w && cmd->payload_sz + 1 < sizeof(buf)) {
|
||||
memcpy(buf, payload, cmd->payload_sz); buf[cmd->payload_sz] = 0;
|
||||
drop_request_data(w, buf);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,7 +326,6 @@ bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms);
|
|||
void screen_check_pause_rendering(Screen *self, monotonic_t now);
|
||||
void screen_designate_charset(Screen *self, uint32_t which, uint32_t as);
|
||||
void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params);
|
||||
void register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id);
|
||||
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
|
||||
DECLARE_CH_SCREEN_HANDLER(bell)
|
||||
DECLARE_CH_SCREEN_HANDLER(backspace)
|
||||
|
|
|
|||
|
|
@ -248,13 +248,14 @@ typedef struct Window {
|
|||
struct {
|
||||
bool wanted, hovered, dropped;
|
||||
uint32_t client_id;
|
||||
char *registered_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;
|
||||
|
||||
char *getting_data_for_mime;
|
||||
} drop;
|
||||
} Window;
|
||||
|
||||
|
|
@ -404,7 +405,7 @@ typedef struct GlobalState {
|
|||
|
||||
struct {
|
||||
PyObject *data;
|
||||
id_type os_window_id;
|
||||
id_type os_window_id, client_window_data_request;
|
||||
double x, y;
|
||||
size_t num_left;
|
||||
bool drop_has_happened;
|
||||
|
|
@ -536,3 +537,5 @@ 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);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue