mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-14 00:47:02 +00:00
Send drop move events to child
This commit is contained in:
parent
8d069d3bcd
commit
b0e57b4dce
12 changed files with 263 additions and 28 deletions
|
|
@ -24,7 +24,9 @@ their metadata. Each chunk must have a payload of no more than 4096 base64
|
|||
encoded bytes without trailing padding, except the last chunk which may
|
||||
optionally have trailing padding. Only the first chunk is guaranteed to have
|
||||
metadata other than the ``m`` key. Subsequent chunks may optionally omit all
|
||||
metadata except the ``m`` and ``i`` keys.
|
||||
metadata except the ``m`` and ``i`` keys. While a chunked transfer is in
|
||||
progress it is a protocol error to for the sending side to
|
||||
send any protocol related escape codes other than chunked ones.
|
||||
|
||||
All integer values used in this escape code must be 32-bit signed or unsigned
|
||||
integers encoded in decimal representation.
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ def parsers() -> None:
|
|||
keymap = {
|
||||
't': ('type', flag('aA')),
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('client_id', 'uint'),
|
||||
}
|
||||
text = generate(
|
||||
'parse_dnd_code', 'screen_handle_dnd_command', 'dnd_command', keymap, 'DnDCommand',
|
||||
|
|
|
|||
|
|
@ -321,9 +321,10 @@ add_child(ChildMonitor *self, PyObject *args) {
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#define schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end) \
|
||||
static const unsigned write_buf_limit = 100 * 1024 * 1024;
|
||||
|
||||
#define schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end, found, too_much_data) \
|
||||
ChildMonitor *self = the_monitor; \
|
||||
bool found = false; \
|
||||
const char *data; \
|
||||
size_t szval, sz = 0; \
|
||||
va_start(ap, num); \
|
||||
|
|
@ -339,8 +340,8 @@ add_child(ChildMonitor *self, PyObject *args) {
|
|||
screen_mutex(lock, write); \
|
||||
size_t space_left = screen->write_buf_sz - screen->write_buf_used; \
|
||||
if (space_left < sz) { \
|
||||
if (screen->write_buf_used + sz > 100 * 1024 * 1024) { \
|
||||
log_error("Too much data being sent to child with id: %lu, ignoring it", id); \
|
||||
if (screen->write_buf_used + sz > write_buf_limit) { \
|
||||
too_much_data = true; \
|
||||
screen_mutex(unlock, write); \
|
||||
break; \
|
||||
} \
|
||||
|
|
@ -366,19 +367,57 @@ add_child(ChildMonitor *self, PyObject *args) {
|
|||
break; \
|
||||
} \
|
||||
} \
|
||||
children_mutex(unlock); \
|
||||
return found;
|
||||
children_mutex(unlock);
|
||||
|
||||
bool
|
||||
schedule_write_to_child(unsigned long id, unsigned int num, ...) {
|
||||
va_list ap;
|
||||
#define get_next_arg(ap) data = va_arg(ap, const char*); szval = va_arg(ap, size_t);
|
||||
schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end);
|
||||
#undef get_next_arg
|
||||
void
|
||||
schedule_write_to_child_if_possible(id_type id, const char *data, size_t sz, bool *found, bool *too_much_data) {
|
||||
children_mutex(lock);
|
||||
ChildMonitor *self = the_monitor;
|
||||
*found = false; *too_much_data = false;
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].id == id) {
|
||||
Screen *screen = children[i].screen;
|
||||
screen_mutex(lock, write);
|
||||
size_t space_left = screen->write_buf_sz - screen->write_buf_used;
|
||||
if (space_left < sz) {
|
||||
if (screen->write_buf_used + sz > write_buf_limit) {
|
||||
*too_much_data = true;
|
||||
screen_mutex(unlock, write);
|
||||
break;
|
||||
}
|
||||
screen->write_buf_sz = screen->write_buf_used + sz;
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
||||
}
|
||||
*found = true;
|
||||
memcpy(screen->write_buf + screen->write_buf_used, data, sz);
|
||||
screen->write_buf_used += sz;
|
||||
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
|
||||
screen->write_buf_sz = BUFSIZ;
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
||||
}
|
||||
if (screen->write_buf_used) wakeup_io_loop(self, false);
|
||||
screen_mutex(unlock, write);
|
||||
break;
|
||||
}
|
||||
}
|
||||
children_mutex(unlock);
|
||||
}
|
||||
|
||||
bool
|
||||
schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject *ap, const char *suffix) {
|
||||
schedule_write_to_child(id_type id, unsigned num, ...) {
|
||||
va_list ap;
|
||||
bool too_much_data = false, found = false;
|
||||
#define get_next_arg(ap) data = va_arg(ap, const char*); szval = va_arg(ap, size_t);
|
||||
schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end, found, too_much_data);
|
||||
#undef get_next_arg
|
||||
if (too_much_data) log_error("Too much data being written to child with id: %llu dropping it", id);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool
|
||||
schedule_write_to_child_python(id_type id, const char *prefix, PyObject *ap, const char *suffix) {
|
||||
if (!PyTuple_Check(ap)) return false;
|
||||
bool has_prefix = prefix && prefix[0], has_suffix = suffix && suffix[0];
|
||||
const size_t extra = (has_prefix ? 1 : 0) + (has_suffix ? 1 : 0);
|
||||
|
|
@ -403,7 +442,10 @@ schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject *a
|
|||
} \
|
||||
} \
|
||||
}
|
||||
schedule_write_to_child_generic(id, num, py_start, get_next_arg, py_end);
|
||||
bool found = false, too_much_data = false;
|
||||
schedule_write_to_child_generic(id, num, py_start, get_next_arg, py_end, found, too_much_data);
|
||||
if (too_much_data) log_error("Too much data being written to child with id: %llu dropping it", id);
|
||||
return found;
|
||||
#undef py_start
|
||||
#undef py_end
|
||||
#undef get_next_arg
|
||||
|
|
|
|||
|
|
@ -311,8 +311,9 @@ void cursor_from_sgr(Cursor *self, int *params, unsigned int count, bool is_grou
|
|||
const char* cursor_as_sgr(const Cursor *);
|
||||
|
||||
PyObject* cm_thread_write(PyObject *self, PyObject *args);
|
||||
bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
|
||||
bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject* tuple_of_str_or_bytes, const char *suffix);
|
||||
bool schedule_write_to_child(id_type id, unsigned int num, ...);
|
||||
bool schedule_write_to_child_python(id_type id, const char *prefix, PyObject* tuple_of_str_or_bytes, const char *suffix);
|
||||
void schedule_write_to_child_if_possible(id_type id, const char *data, size_t sz, bool *found, bool *too_much_data);
|
||||
bool set_iutf8(int, bool);
|
||||
|
||||
DynamicColor colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor defval);
|
||||
|
|
|
|||
146
kitty/dnd.c
Normal file
146
kitty/dnd.c
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* dnd.c
|
||||
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "dnd.h"
|
||||
#include "base64.h"
|
||||
|
||||
static void
|
||||
drop_free_offered_mimes(Window *w) {
|
||||
if (w->drop.offerred_mimes) {
|
||||
for (size_t i = 0; i < w->drop.num_offerred_mimes; i++) free((void*)w->drop.offerred_mimes[i]);
|
||||
free(w->drop.offerred_mimes); w->drop.offerred_mimes = NULL;
|
||||
}
|
||||
w->drop.num_offerred_mimes = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
free_pending(PendingData *pending) {
|
||||
if (pending->items) {
|
||||
for (size_t i = 0; i < pending->count; i++) free(pending->items[i].buf);
|
||||
free(pending->items);
|
||||
}
|
||||
zero_at_ptr(pending);
|
||||
}
|
||||
|
||||
void
|
||||
drop_free_data(Window *w) {
|
||||
drop_free_offered_mimes(w);
|
||||
free_pending(&w->drop.pending);
|
||||
}
|
||||
|
||||
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;
|
||||
for (size_t i = 0; i < an; i++) {
|
||||
int ret = strcmp(a[i], b[i]);
|
||||
if (ret != 0) return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
send_payload_to_child(id_type id, const char *header, size_t header_sz, const char *data, size_t data_sz) {
|
||||
size_t offset = 0;
|
||||
char buf[4096 + 1024];
|
||||
memcpy(buf, header, header_sz);
|
||||
buf[header_sz++] = ':'; buf[header_sz++] = 'm'; buf[header_sz++] = '=';
|
||||
while (offset < data_sz) {
|
||||
size_t chunk = data_sz - offset;
|
||||
size_t p = header_sz;
|
||||
buf[p++] = offset + 3072 >= data_sz ? '0' : '1';
|
||||
buf[p++] = ';';
|
||||
size_t b64_len = sizeof(buf) - p;
|
||||
base64_encode8((const uint8_t*)data + offset, chunk, (uint8_t*)buf + p, &b64_len, false);
|
||||
p += b64_len;
|
||||
buf[p++] = 0x1b; buf[p++] = '\\';
|
||||
bool found, too_much_data;
|
||||
schedule_write_to_child_if_possible(id, buf, p, &found, &too_much_data);
|
||||
if (too_much_data) break;
|
||||
if (!found) return data_sz;
|
||||
offset += chunk;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool
|
||||
flush_pending(id_type id, PendingData *pending) {
|
||||
while (pending->count) {
|
||||
PendingEntry *e = pending->items;
|
||||
size_t written = send_payload_to_child(id, e->buf, e->header_sz, e->buf + e->header_sz, e->data_sz);
|
||||
if (written < e->data_sz) {
|
||||
if (written) {
|
||||
e->data_sz -= written;
|
||||
memmove(e->buf + e->header_sz, e->buf + e->header_sz + written, e->data_sz);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
free(e->buf);
|
||||
remove_i_from_array(pending->items, 0, pending->count);
|
||||
}
|
||||
}
|
||||
return pending->count > 0;
|
||||
}
|
||||
|
||||
static void
|
||||
queue_payload_to_child(id_type id, PendingData *pending, const char *header, size_t header_sz, const char *data, size_t data_sz) {
|
||||
size_t offset = 0;
|
||||
if (flush_pending(id, pending)) offset = send_payload_to_child(id, header, header_sz, data, data_sz);
|
||||
if (offset < data_sz) {
|
||||
ensure_space_for(pending, items, PendingEntry, pending->count + 1, capacity, 32, true);
|
||||
char *buf = malloc(header_sz + data_sz - offset);
|
||||
if (!buf) fatal("Out of memory");
|
||||
memcpy(buf, header, header_sz); memcpy(buf + header_sz, data, data_sz - offset);
|
||||
PendingEntry *e = &pending->items[pending->count++];
|
||||
e->buf = buf; e->header_sz = header_sz; e->data_sz = data_sz - offset;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) {
|
||||
if (!w->drop.hovered) {
|
||||
drop_free_offered_mimes(w);
|
||||
w->drop.hovered = true;
|
||||
}
|
||||
size_t mimes_total_size = 0;
|
||||
if (mimes && (w->drop.offerred_mimes == NULL || string_arrays_cmp(mimes, num_mimes, w->drop.offerred_mimes, w->drop.num_offerred_mimes) != 0)) {
|
||||
drop_free_offered_mimes(w);
|
||||
w->drop.offerred_mimes = malloc(num_mimes * sizeof(char*));
|
||||
if (w->drop.offerred_mimes) {
|
||||
for (size_t i = 0; i < num_mimes; i++) {
|
||||
size_t l = strlen(mimes[i]);
|
||||
mimes_total_size += 1 + l;
|
||||
char *p = malloc(l + 1);
|
||||
if (!p) fatal("Out of memory");
|
||||
memcpy(p, mimes[i], l); p[l] = 0;
|
||||
w->drop.offerred_mimes[i] = p;
|
||||
}
|
||||
}
|
||||
w->drop.num_offerred_mimes = num_mimes;
|
||||
}
|
||||
// we simply drop this event if there is too much data being written to the child
|
||||
if (w->drop.pending.count) return;
|
||||
char buf[128];
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=m:x=%u:y=%u:X=%d:Y=%d", w->drop.client_id,
|
||||
w->mouse_pos.cell_x, w->mouse_pos.cell_y, (int)w->mouse_pos.global_x, (int)w->mouse_pos.global_y);
|
||||
if (mimes_total_size) {
|
||||
mimes_total_size += 1;
|
||||
RAII_ALLOC(char, mbuf, malloc(mimes_total_size));
|
||||
if (mbuf) {
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < w->drop.num_offerred_mimes && pos < mimes_total_size; i++) {
|
||||
int n = snprintf(mbuf, mimes_total_size - pos, mbuf + pos, "%s ", w->drop.offerred_mimes[i]);
|
||||
if (n < 0) break;
|
||||
pos += n;
|
||||
}
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, mbuf, pos);
|
||||
}
|
||||
} else {
|
||||
buf[header_size++] = 0x1b; buf[header_size++] = '\\';
|
||||
bool found, too_much_data;
|
||||
schedule_write_to_child_if_possible(w->id, buf, header_size, &found, &too_much_data);
|
||||
}
|
||||
}
|
||||
12
kitty/dnd.h
Normal file
12
kitty/dnd.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* dnd.h
|
||||
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "state.h"
|
||||
|
||||
void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes);
|
||||
void drop_free_data(Window *w);
|
||||
14
kitty/glfw.c
14
kitty/glfw.c
|
|
@ -7,6 +7,7 @@
|
|||
#include "state.h"
|
||||
#include "cleanup.h"
|
||||
#include "monotonic.h"
|
||||
#include "dnd.h"
|
||||
#include "charsets.h"
|
||||
#include "control-codes.h"
|
||||
#include <structmember.h>
|
||||
|
|
@ -766,12 +767,14 @@ 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) {
|
||||
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->accepts_drops = on;
|
||||
if (on && payload && payload_sz) {
|
||||
w->drop.allowed = on;
|
||||
w->drop.client_id = client_id;
|
||||
if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); }
|
||||
else if (payload && payload_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;
|
||||
|
|
@ -792,12 +795,17 @@ 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;
|
||||
switch (ev->type) {
|
||||
case GLFW_DROP_ENTER:
|
||||
case GLFW_DROP_MOVE:
|
||||
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 (global_state.mouse_hover_in_window && (w = window_for_window_id(global_state.mouse_hover_in_window)) && w->drop.allowed) {
|
||||
drop_move_on_child(w, ev->mimes, ev->num_mimes);
|
||||
return;
|
||||
}
|
||||
call_boss(on_drop_move, "KiiOO",
|
||||
os_window->id, os_window->last_drag_event.x, os_window->last_drag_event.y,
|
||||
ev->from_self ? Py_True : Py_False, Py_False);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
(void)is_negative;
|
||||
size_t sz;
|
||||
|
||||
enum KEYS { type = 't', more = 'm' };
|
||||
enum KEYS { type = 't', more = 'm', client_id = 'i' };
|
||||
|
||||
enum KEYS key = 'a';
|
||||
if (parser_buf[pos] == ';')
|
||||
|
|
@ -36,6 +36,9 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
case more:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case client_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR(
|
||||
"Malformed DnDCommand control block, invalid key character: 0x%x",
|
||||
|
|
@ -59,7 +62,7 @@ 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 != 'e') {
|
||||
if (g.type != 'A' && g.type != 'a') {
|
||||
REPORT_ERROR("Malformed DnDCommand control block, unknown flag value "
|
||||
"for type: 0x%x",
|
||||
g.type);
|
||||
|
|
@ -121,6 +124,7 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
break
|
||||
switch (key) {
|
||||
U(more);
|
||||
U(client_id);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -178,11 +182,12 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||
break;
|
||||
}
|
||||
|
||||
REPORT_VA_COMMAND("K s {sc sI ss#}", self->window_id, "dnd_command",
|
||||
REPORT_VA_COMMAND("K s {sc sI sI ss#}", self->window_id, "dnd_command",
|
||||
|
||||
"type", g.type,
|
||||
|
||||
"more", (unsigned int)g.more,
|
||||
"more", (unsigned int)g.more, "client_id",
|
||||
(unsigned int)g.client_id,
|
||||
|
||||
"", (char *)parser_buf, g.payload_sz);
|
||||
|
||||
|
|
|
|||
|
|
@ -1511,8 +1511,8 @@ void
|
|||
screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *payload) {
|
||||
if (!self->window_id) return;
|
||||
switch(cmd->type) {
|
||||
case 'a': register_drop_window(self->window_id, payload, cmd->payload_sz, true); break;
|
||||
case 'A': register_drop_window(self->window_id, NULL, 0, false); break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr
|
|||
typedef struct DnDCommand {
|
||||
char type;
|
||||
unsigned more;
|
||||
uint32_t client_id;
|
||||
size_t payload_sz;
|
||||
} DnDCommand;
|
||||
|
||||
|
|
@ -323,7 +324,7 @@ 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);
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include "cleanup.h"
|
||||
#include "dnd.h"
|
||||
#include "options/to-c-generated.h"
|
||||
#include <math.h>
|
||||
#include <sys/mman.h>
|
||||
|
|
@ -351,6 +352,7 @@ update_os_window_title(OSWindow *os_window) {
|
|||
|
||||
static void
|
||||
destroy_window(Window *w) {
|
||||
drop_free_data(w);
|
||||
free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks);
|
||||
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
||||
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
|
||||
|
|
|
|||
|
|
@ -204,9 +204,18 @@ typedef struct WindowBarData {
|
|||
bool needs_render;
|
||||
} WindowBarData;
|
||||
|
||||
typedef struct PendingEntry {
|
||||
char *buf; size_t header_sz;
|
||||
size_t data_sz;
|
||||
} PendingEntry;
|
||||
|
||||
typedef struct PendingData {
|
||||
PendingEntry *items; size_t count, capacity;
|
||||
} PendingData;
|
||||
|
||||
typedef struct Window {
|
||||
id_type id;
|
||||
bool visible, accepts_drops;
|
||||
bool visible;
|
||||
PyObject *title;
|
||||
WindowRenderData render_data;
|
||||
WindowRenderData window_title_render_data;
|
||||
|
|
@ -236,6 +245,12 @@ typedef struct Window {
|
|||
double drag_start_scrolled_by;
|
||||
bool is_hovering;
|
||||
} scrollbar;
|
||||
struct {
|
||||
bool allowed, hovered;
|
||||
uint32_t client_id;
|
||||
const char **offerred_mimes; size_t num_offerred_mimes;
|
||||
PendingData pending;
|
||||
} drop;
|
||||
} Window;
|
||||
|
||||
typedef struct BorderRect {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue