mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-18 14:34:52 +02:00
committed by
Kovid Goyal
parent
3cd2aa6a2f
commit
0dfb01e8c5
1
glfw/x11_init.c
vendored
1
glfw/x11_init.c
vendored
@@ -513,6 +513,7 @@ static bool initExtensions(void)
|
||||
_glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False);
|
||||
_glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False);
|
||||
_glfw.x11.XdndLeave = XInternAtom(_glfw.x11.display, "XdndLeave", False);
|
||||
_glfw.x11.XdndProxy = XInternAtom(_glfw.x11.display, "XdndProxy", False);
|
||||
|
||||
// ICCCM, EWMH and Motif window property atoms
|
||||
// These can be set safely even without WM support
|
||||
|
||||
21
glfw/x11_platform.h
vendored
21
glfw/x11_platform.h
vendored
@@ -325,6 +325,7 @@ typedef struct _GLFWlibraryX11
|
||||
Atom XdndSelection;
|
||||
Atom XdndTypeList;
|
||||
Atom XdndLeave;
|
||||
Atom XdndProxy;
|
||||
|
||||
// Selection (clipboard) atoms
|
||||
Atom TARGETS;
|
||||
@@ -406,6 +407,26 @@ typedef struct _GLFWlibraryX11
|
||||
Atom* type_atoms; // Atoms for each MIME type
|
||||
size_t type_count;
|
||||
Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink
|
||||
bool active; // Whether a drag is currently active
|
||||
Window current_target;// Current drop target window under cursor
|
||||
Window proxy_target; // Proxy target if current_target has XdndProxy
|
||||
int xdnd_version; // Xdnd version supported by current target
|
||||
bool waiting_for_status; // Waiting for XdndStatus from target
|
||||
bool accepted; // Whether target accepted the drag
|
||||
Atom accepted_action; // Action accepted by target
|
||||
struct {
|
||||
const char *mime_type;
|
||||
Window requestor;
|
||||
Atom property;
|
||||
Atom target;
|
||||
bool inflight;
|
||||
} *pending_requests;
|
||||
size_t pending_count;
|
||||
size_t pending_capacity;
|
||||
// Thumbnail window for drag icon
|
||||
Window thumbnail_window;
|
||||
Pixmap thumbnail_pixmap;
|
||||
GC thumbnail_gc;
|
||||
} drag;
|
||||
|
||||
struct {
|
||||
|
||||
711
glfw/x11_window.c
vendored
711
glfw/x11_window.c
vendored
@@ -94,6 +94,16 @@ x11_cancel_momentum_scroll_timer(void) {
|
||||
//
|
||||
static unsigned _glfwDispatchX11Events(void);
|
||||
|
||||
// Forward declarations for drag source helper functions
|
||||
static void send_drag_data(const char *mime_type, const char *data, size_t data_sz,
|
||||
Window requestor, Atom property, Atom target);
|
||||
static bool add_pending_request(const char *mime_type, Window requestor, Atom property, Atom target);
|
||||
static void handle_drag_motion(int root_x, int root_y, Time timestamp);
|
||||
static void handle_drag_button_release(Time timestamp);
|
||||
static void handle_xdnd_status(const XClientMessageEvent *event);
|
||||
static void handle_xdnd_finished(const XClientMessageEvent *event);
|
||||
static bool create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y);
|
||||
|
||||
static void
|
||||
handleEvents(monotonic_t timeout) {
|
||||
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
|
||||
@@ -1139,7 +1149,52 @@ handleSelectionRequest(XEvent* event) {
|
||||
}
|
||||
}
|
||||
if (mime_type) {
|
||||
// TODO: Create a pending request and send GLFW_DRAG_DATA_REQUEST to application
|
||||
// Check if we have preset data
|
||||
const char *data = NULL;
|
||||
size_t data_sz = 0;
|
||||
for (size_t i = 0; i < _glfw.drag.item_count; i++) {
|
||||
if (strcmp(_glfw.drag.items[i].mime_type, mime_type) == 0) {
|
||||
data = _glfw.drag.items[i].optional_data;
|
||||
data_sz = _glfw.drag.items[i].data_size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data && data_sz > 0) {
|
||||
// We have preset data, send it immediately
|
||||
send_drag_data(mime_type, data, data_sz, request->requestor,
|
||||
request->property, request->target);
|
||||
reply.xselection.property = request->property;
|
||||
} else {
|
||||
// Add to pending requests for on-demand data
|
||||
if (add_pending_request(mime_type, request->requestor,
|
||||
request->property, request->target)) {
|
||||
// Request data from application
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
GLFWDragEvent ev = {
|
||||
.type = GLFW_DRAG_DATA_REQUEST,
|
||||
.mime_type = mime_type,
|
||||
.data = NULL,
|
||||
.data_sz = 0,
|
||||
.err_num = 0
|
||||
};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
|
||||
if (ev.data && ev.data_sz > 0) {
|
||||
// Data provided synchronously
|
||||
send_drag_data(mime_type, ev.data, ev.data_sz,
|
||||
request->requestor, request->property,
|
||||
request->target);
|
||||
reply.xselection.property = request->property;
|
||||
} else {
|
||||
// Data will be provided asynchronously via _glfwPlatformDragDataReady
|
||||
// Don't send SelectionNotify yet - it will be sent when data is ready
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2025,6 +2080,11 @@ static void processEvent(XEvent *event)
|
||||
{
|
||||
const int mods = translateState(event->xbutton.state);
|
||||
|
||||
// Handle drag drop on button release
|
||||
if (_glfw.x11.drag.active && event->xbutton.button == Button1) {
|
||||
handle_drag_button_release(event->xbutton.time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->xbutton.button == Button1)
|
||||
{
|
||||
@@ -2092,6 +2152,17 @@ static void processEvent(XEvent *event)
|
||||
|
||||
case MotionNotify:
|
||||
{
|
||||
// Handle drag motion
|
||||
if (_glfw.x11.drag.active) {
|
||||
int root_x, root_y;
|
||||
Window child;
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
event->xmotion.window, _glfw.x11.root,
|
||||
event->xmotion.x, event->xmotion.y,
|
||||
&root_x, &root_y, &child);
|
||||
handle_drag_motion(root_x, root_y, event->xmotion.time);
|
||||
}
|
||||
|
||||
x11_cancel_momentum_scroll_timer();
|
||||
glfw_cancel_momentum_scroll();
|
||||
handle_mouse_move_event(window, event->xmotion.x, event->xmotion.y);
|
||||
@@ -2184,6 +2255,8 @@ static void processEvent(XEvent *event)
|
||||
else if (event->xclient.message_type == _glfw.x11.XdndDrop) { drop(window, event); }
|
||||
else if (event->xclient.message_type == _glfw.x11.XdndLeave) { drop_leave(window, event); }
|
||||
else if (event->xclient.message_type == _glfw.x11.XdndPosition) { drop_move(window, event); }
|
||||
else if (event->xclient.message_type == _glfw.x11.XdndStatus) { handle_xdnd_status(&event->xclient); }
|
||||
else if (event->xclient.message_type == _glfw.x11.XdndFinished) { handle_xdnd_finished(&event->xclient); }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3814,11 +3887,639 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
}
|
||||
|
||||
// Drag source {{{
|
||||
|
||||
// Helper function to check if a window supports XdndAware
|
||||
static bool
|
||||
window_supports_xdnd(Window win, int *version_out) {
|
||||
Atom actual_type;
|
||||
int actual_format;
|
||||
unsigned long count, bytes_after;
|
||||
unsigned char *data = NULL;
|
||||
bool supported = false;
|
||||
|
||||
if (XGetWindowProperty(_glfw.x11.display, win, _glfw.x11.XdndAware,
|
||||
0, 1, False, XA_ATOM,
|
||||
&actual_type, &actual_format,
|
||||
&count, &bytes_after, &data) == Success) {
|
||||
if (actual_type == XA_ATOM && actual_format == 32 && count > 0) {
|
||||
supported = true;
|
||||
if (version_out) {
|
||||
int version = *(int*)data;
|
||||
*version_out = (version < _GLFW_XDND_VERSION) ? version : _GLFW_XDND_VERSION;
|
||||
}
|
||||
}
|
||||
if (data) XFree(data);
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
// Find the XdndAware window at the given coordinates
|
||||
static Window
|
||||
find_xdnd_aware_target(Window root, int root_x, int root_y, int *version_out) {
|
||||
Window target = None;
|
||||
Window child = root;
|
||||
Window parent = root;
|
||||
Window *children = NULL;
|
||||
unsigned int nchildren = 0;
|
||||
|
||||
// Walk down the window tree to find the deepest window at these coordinates
|
||||
while (child != None) {
|
||||
target = child;
|
||||
int x, y;
|
||||
if (!XTranslateCoordinates(_glfw.x11.display, root, target,
|
||||
root_x, root_y, &x, &y, &child)) {
|
||||
break;
|
||||
}
|
||||
if (child == None) break;
|
||||
}
|
||||
|
||||
// Walk up the tree to find an XdndAware window
|
||||
while (target != None && target != root) {
|
||||
if (window_supports_xdnd(target, version_out)) {
|
||||
// Check for XdndProxy
|
||||
Atom actual_type;
|
||||
int actual_format;
|
||||
unsigned long count, bytes_after;
|
||||
unsigned char *data = NULL;
|
||||
|
||||
if (XGetWindowProperty(_glfw.x11.display, target, _glfw.x11.XdndProxy,
|
||||
0, 1, False, XA_WINDOW,
|
||||
&actual_type, &actual_format,
|
||||
&count, &bytes_after, &data) == Success) {
|
||||
if (actual_type == XA_WINDOW && actual_format == 32 && count > 0) {
|
||||
Window proxy = *(Window*)data;
|
||||
XFree(data);
|
||||
// Verify the proxy is XdndAware
|
||||
if (window_supports_xdnd(proxy, NULL)) {
|
||||
return proxy;
|
||||
}
|
||||
} else if (data) {
|
||||
XFree(data);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
// Move to parent
|
||||
Window new_parent;
|
||||
if (XQueryTree(_glfw.x11.display, target, &parent, &new_parent, &children, &nchildren)) {
|
||||
if (children) XFree(children);
|
||||
target = new_parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
// Send XdndEnter message to target window
|
||||
static void
|
||||
send_xdnd_enter(Window target, int version) {
|
||||
XEvent event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
event.xclient.type = ClientMessage;
|
||||
event.xclient.window = target;
|
||||
event.xclient.message_type = _glfw.x11.XdndEnter;
|
||||
event.xclient.format = 32;
|
||||
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
|
||||
event.xclient.data.l[1] = (version << 24);
|
||||
|
||||
// If we have more than 3 types, set the type list flag
|
||||
if (_glfw.x11.drag.type_count > 3) {
|
||||
event.xclient.data.l[1] |= 1; // More than 3 types, use property
|
||||
// Set the XdndTypeList property on source window
|
||||
XChangeProperty(_glfw.x11.display, _glfw.x11.drag.source_window,
|
||||
_glfw.x11.XdndTypeList, XA_ATOM, 32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)_glfw.x11.drag.type_atoms,
|
||||
_glfw.x11.drag.type_count);
|
||||
} else {
|
||||
// Embed up to 3 types in the message
|
||||
for (size_t i = 0; i < _glfw.x11.drag.type_count && i < 3; i++) {
|
||||
event.xclient.data.l[2 + i] = _glfw.x11.drag.type_atoms[i];
|
||||
}
|
||||
}
|
||||
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
// Send XdndPosition message to target window
|
||||
static void
|
||||
send_xdnd_position(Window target, int root_x, int root_y, Time timestamp) {
|
||||
XEvent event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
event.xclient.type = ClientMessage;
|
||||
event.xclient.window = target;
|
||||
event.xclient.message_type = _glfw.x11.XdndPosition;
|
||||
event.xclient.format = 32;
|
||||
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
|
||||
event.xclient.data.l[1] = 0; // Reserved
|
||||
event.xclient.data.l[2] = (root_x << 16) | root_y;
|
||||
event.xclient.data.l[3] = timestamp;
|
||||
event.xclient.data.l[4] = _glfw.x11.drag.action_atom;
|
||||
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfw.x11.drag.waiting_for_status = true;
|
||||
}
|
||||
|
||||
// Send XdndLeave message to target window
|
||||
static void
|
||||
send_xdnd_leave(Window target) {
|
||||
XEvent event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
event.xclient.type = ClientMessage;
|
||||
event.xclient.window = target;
|
||||
event.xclient.message_type = _glfw.x11.XdndLeave;
|
||||
event.xclient.format = 32;
|
||||
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
|
||||
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
// Send XdndDrop message to target window
|
||||
static void
|
||||
send_xdnd_drop(Window target, Time timestamp) {
|
||||
XEvent event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
event.xclient.type = ClientMessage;
|
||||
event.xclient.window = target;
|
||||
event.xclient.message_type = _glfw.x11.XdndDrop;
|
||||
event.xclient.format = 32;
|
||||
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
|
||||
event.xclient.data.l[1] = 0; // Reserved
|
||||
event.xclient.data.l[2] = timestamp;
|
||||
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
// Create thumbnail window for drag operation
|
||||
static bool
|
||||
create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y) {
|
||||
if (!thumbnail || !thumbnail->pixels || thumbnail->width <= 0 || thumbnail->height <= 0) {
|
||||
return true; // No thumbnail is fine
|
||||
}
|
||||
|
||||
// Create an override-redirect window for the drag icon
|
||||
XSetWindowAttributes attrs;
|
||||
attrs.override_redirect = True;
|
||||
attrs.background_pixel = 0;
|
||||
|
||||
_glfw.x11.drag.thumbnail_window = XCreateWindow(
|
||||
_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
x, y,
|
||||
thumbnail->width, thumbnail->height,
|
||||
0, // border width
|
||||
CopyFromParent, // depth
|
||||
InputOutput, // class
|
||||
CopyFromParent, // visual
|
||||
CWOverrideRedirect | CWBackPixel,
|
||||
&attrs
|
||||
);
|
||||
|
||||
if (!_glfw.x11.drag.thumbnail_window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create pixmap for the thumbnail image
|
||||
_glfw.x11.drag.thumbnail_pixmap = XCreatePixmap(
|
||||
_glfw.x11.display,
|
||||
_glfw.x11.drag.thumbnail_window,
|
||||
thumbnail->width,
|
||||
thumbnail->height,
|
||||
DefaultDepth(_glfw.x11.display, _glfw.x11.screen)
|
||||
);
|
||||
|
||||
if (!_glfw.x11.drag.thumbnail_pixmap) {
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create GC for drawing
|
||||
_glfw.x11.drag.thumbnail_gc = XCreateGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, 0, NULL);
|
||||
if (!_glfw.x11.drag.thumbnail_gc) {
|
||||
XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap);
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert RGBA image data to XImage and draw to pixmap
|
||||
Visual* visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen);
|
||||
XImage* ximage = XCreateImage(
|
||||
_glfw.x11.display,
|
||||
visual,
|
||||
DefaultDepth(_glfw.x11.display, _glfw.x11.screen),
|
||||
ZPixmap,
|
||||
0, // offset
|
||||
NULL, // data (will be set below)
|
||||
thumbnail->width,
|
||||
thumbnail->height,
|
||||
32, // bitmap_pad
|
||||
0 // bytes_per_line (auto-calculate)
|
||||
);
|
||||
|
||||
if (!ximage) {
|
||||
XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc);
|
||||
XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap);
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate buffer for XImage
|
||||
int pixels_size = thumbnail->width * thumbnail->height * 4;
|
||||
unsigned char* ximage_data = malloc(pixels_size);
|
||||
if (!ximage_data) {
|
||||
XDestroyImage(ximage);
|
||||
XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc);
|
||||
XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap);
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
ximage->data = (char*)ximage_data;
|
||||
|
||||
// Convert RGBA to the format expected by XImage
|
||||
const unsigned char* src = thumbnail->pixels;
|
||||
for (int i = 0; i < thumbnail->width * thumbnail->height; i++) {
|
||||
unsigned long pixel;
|
||||
unsigned char r = src[i * 4 + 0];
|
||||
unsigned char g = src[i * 4 + 1];
|
||||
unsigned char b = src[i * 4 + 2];
|
||||
unsigned char a = src[i * 4 + 3];
|
||||
|
||||
// Premultiply alpha
|
||||
if (a < 255) {
|
||||
r = (r * a) / 255;
|
||||
g = (g * a) / 255;
|
||||
b = (b * a) / 255;
|
||||
}
|
||||
|
||||
// Convert to X11 pixel format (typically BGRA on little-endian)
|
||||
if (ximage->byte_order == LSBFirst) {
|
||||
pixel = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
} else {
|
||||
pixel = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
|
||||
XPutPixel(ximage, i % thumbnail->width, i / thumbnail->width, pixel);
|
||||
}
|
||||
|
||||
// Draw the image to the pixmap
|
||||
XPutImage(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, _glfw.x11.drag.thumbnail_gc,
|
||||
ximage, 0, 0, 0, 0, thumbnail->width, thumbnail->height);
|
||||
|
||||
// Clean up XImage (including its data)
|
||||
XDestroyImage(ximage); // This also frees ximage_data
|
||||
|
||||
// Set the pixmap as the window's background
|
||||
XSetWindowBackgroundPixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, _glfw.x11.drag.thumbnail_pixmap);
|
||||
|
||||
// Map the window to make it visible
|
||||
XMapRaised(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
XFlush(_glfw.x11.display);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle motion during drag
|
||||
static void
|
||||
handle_drag_motion(int root_x, int root_y, Time timestamp) {
|
||||
if (!_glfw.x11.drag.active) return;
|
||||
|
||||
// Move thumbnail window to follow cursor
|
||||
if (_glfw.x11.drag.thumbnail_window != None) {
|
||||
XMoveWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, root_x + 10, root_y + 10);
|
||||
}
|
||||
|
||||
int version = _GLFW_XDND_VERSION;
|
||||
Window new_target = find_xdnd_aware_target(_glfw.x11.root, root_x, root_y, &version);
|
||||
|
||||
if (new_target != _glfw.x11.drag.current_target) {
|
||||
// Send leave to old target
|
||||
if (_glfw.x11.drag.current_target != None) {
|
||||
send_xdnd_leave(_glfw.x11.drag.current_target);
|
||||
}
|
||||
|
||||
_glfw.x11.drag.current_target = new_target;
|
||||
_glfw.x11.drag.waiting_for_status = false;
|
||||
_glfw.x11.drag.accepted = false;
|
||||
|
||||
// Send enter to new target
|
||||
if (new_target != None) {
|
||||
_glfw.x11.drag.xdnd_version = version;
|
||||
send_xdnd_enter(new_target, version);
|
||||
}
|
||||
}
|
||||
|
||||
// Send position to current target
|
||||
if (_glfw.x11.drag.current_target != None && !_glfw.x11.drag.waiting_for_status) {
|
||||
send_xdnd_position(_glfw.x11.drag.current_target, root_x, root_y, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle button release during drag (drop)
|
||||
static void
|
||||
handle_drag_button_release(Time timestamp) {
|
||||
if (!_glfw.x11.drag.active) return;
|
||||
|
||||
if (_glfw.x11.drag.current_target != None && _glfw.x11.drag.accepted) {
|
||||
send_xdnd_drop(_glfw.x11.drag.current_target, timestamp);
|
||||
} else {
|
||||
// Drag was cancelled or not accepted
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
GLFWDragEvent ev = {.type = GLFW_DRAG_CANCELLED};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
}
|
||||
_glfwFreeDragSourceData();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XdndStatus message from drop target
|
||||
static void
|
||||
handle_xdnd_status(const XClientMessageEvent *event) {
|
||||
if (!_glfw.x11.drag.active) return;
|
||||
if (event->data.l[0] != (long)_glfw.x11.drag.current_target) return;
|
||||
|
||||
_glfw.x11.drag.waiting_for_status = false;
|
||||
_glfw.x11.drag.accepted = (event->data.l[1] & 1) != 0;
|
||||
|
||||
if (_glfw.x11.drag.accepted && event->data.l[4] != None) {
|
||||
_glfw.x11.drag.accepted_action = event->data.l[4];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XdndFinished message from drop target
|
||||
static void
|
||||
handle_xdnd_finished(const XClientMessageEvent *event) {
|
||||
if (!_glfw.x11.drag.active) return;
|
||||
if (event->data.l[0] != (long)_glfw.x11.drag.current_target) return;
|
||||
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
bool accepted = (event->data.l[1] & 1) != 0;
|
||||
GLFWDragOperationType action = 0;
|
||||
|
||||
if (accepted && event->data.l[2] != None) {
|
||||
Atom action_atom = event->data.l[2];
|
||||
if (action_atom == _glfw.x11.XdndActionCopy) {
|
||||
action = GLFW_DRAG_OPERATION_COPY;
|
||||
} else if (action_atom == _glfw.x11.XdndActionMove) {
|
||||
action = GLFW_DRAG_OPERATION_MOVE;
|
||||
} else if (action_atom == _glfw.x11.XdndActionLink) {
|
||||
action = GLFW_DRAG_OPERATION_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
GLFWDragEvent ev = {.type = GLFW_DRAG_FINSHED, .action = action};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
}
|
||||
|
||||
_glfwFreeDragSourceData();
|
||||
}
|
||||
|
||||
// Add a pending data request
|
||||
static bool
|
||||
add_pending_request(const char *mime_type, Window requestor, Atom property, Atom target) {
|
||||
if (_glfw.x11.drag.pending_count >= _glfw.x11.drag.pending_capacity) {
|
||||
size_t new_capacity = _glfw.x11.drag.pending_capacity == 0 ? 8 : _glfw.x11.drag.pending_capacity * 2;
|
||||
void *new_ptr = realloc(_glfw.x11.drag.pending_requests,
|
||||
new_capacity * sizeof(_glfw.x11.drag.pending_requests[0]));
|
||||
if (!new_ptr) return false;
|
||||
_glfw.x11.drag.pending_requests = new_ptr;
|
||||
_glfw.x11.drag.pending_capacity = new_capacity;
|
||||
}
|
||||
|
||||
size_t idx = _glfw.x11.drag.pending_count++;
|
||||
_glfw.x11.drag.pending_requests[idx].mime_type = _glfw_strdup(mime_type);
|
||||
_glfw.x11.drag.pending_requests[idx].requestor = requestor;
|
||||
_glfw.x11.drag.pending_requests[idx].property = property;
|
||||
_glfw.x11.drag.pending_requests[idx].target = target;
|
||||
_glfw.x11.drag.pending_requests[idx].inflight = true;
|
||||
|
||||
return _glfw.x11.drag.pending_requests[idx].mime_type != NULL;
|
||||
}
|
||||
|
||||
// Send drag data via XChangeProperty
|
||||
static void
|
||||
send_drag_data(const char *mime_type, const char *data, size_t data_sz,
|
||||
Window requestor, Atom property, Atom target) {
|
||||
(void)mime_type; // Parameter kept for consistency with other platforms
|
||||
if (data && data_sz > 0) {
|
||||
XChangeProperty(_glfw.x11.display, requestor, property,
|
||||
target, 8, PropModeReplace,
|
||||
(unsigned char*)data, data_sz);
|
||||
} else {
|
||||
// Send empty property to indicate no data
|
||||
XChangeProperty(_glfw.x11.display, requestor, property,
|
||||
target, 8, PropModeReplace,
|
||||
(unsigned char*)"", 0);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
||||
(void)window; (void)thumbnail;
|
||||
return ENOTSUP;
|
||||
// If a drag is already active, cancel it first
|
||||
if (_glfw.x11.drag.active) {
|
||||
_glfwFreeDragSourceData();
|
||||
}
|
||||
|
||||
// Convert MIME types to atoms
|
||||
_glfw.x11.drag.type_count = _glfw.drag.item_count;
|
||||
_glfw.x11.drag.type_atoms = calloc(_glfw.drag.item_count, sizeof(Atom));
|
||||
if (!_glfw.x11.drag.type_atoms) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < _glfw.drag.item_count; i++) {
|
||||
_glfw.x11.drag.type_atoms[i] = XInternAtom(_glfw.x11.display,
|
||||
_glfw.drag.items[i].mime_type,
|
||||
False);
|
||||
}
|
||||
|
||||
// Determine action atom based on operations
|
||||
if (_glfw.drag.operations & GLFW_DRAG_OPERATION_COPY) {
|
||||
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
|
||||
} else if (_glfw.drag.operations & GLFW_DRAG_OPERATION_MOVE) {
|
||||
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionMove;
|
||||
} else {
|
||||
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
|
||||
}
|
||||
|
||||
_glfw.x11.drag.source_window = window->x11.handle;
|
||||
_glfw.x11.drag.active = true;
|
||||
_glfw.x11.drag.current_target = None;
|
||||
_glfw.x11.drag.waiting_for_status = false;
|
||||
_glfw.x11.drag.accepted = false;
|
||||
|
||||
// Initialize thumbnail fields
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
|
||||
// Get current cursor position for thumbnail placement
|
||||
Window root_return, child_return;
|
||||
int root_x, root_y, win_x, win_y;
|
||||
unsigned int mask_return;
|
||||
XQueryPointer(_glfw.x11.display, _glfw.x11.root,
|
||||
&root_return, &child_return,
|
||||
&root_x, &root_y, &win_x, &win_y, &mask_return);
|
||||
|
||||
// Create thumbnail window if thumbnail is provided
|
||||
if (!create_drag_thumbnail(thumbnail, root_x + 10, root_y + 10)) {
|
||||
// Thumbnail creation failed, but continue with drag operation
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
}
|
||||
|
||||
// Grab the pointer to track drag motion
|
||||
int result = XGrabPointer(_glfw.x11.display, window->x11.handle, False,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
None, None, CurrentTime);
|
||||
|
||||
if (result != GrabSuccess) {
|
||||
free(_glfw.x11.drag.type_atoms);
|
||||
_glfw.x11.drag.type_atoms = NULL;
|
||||
_glfw.x11.drag.type_count = 0;
|
||||
_glfw.x11.drag.active = false;
|
||||
// Clean up thumbnail if it was created
|
||||
if (_glfw.x11.drag.thumbnail_gc != None) {
|
||||
XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc);
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
}
|
||||
if (_glfw.x11.drag.thumbnail_pixmap != None) {
|
||||
XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap);
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
}
|
||||
if (_glfw.x11.drag.thumbnail_window != None) {
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
}
|
||||
return EIO;
|
||||
}
|
||||
|
||||
// Set ourselves as the XdndSelection owner
|
||||
XSetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection,
|
||||
window->x11.handle, CurrentTime);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
_glfwPlatformFreeDragSourceData(void) {
|
||||
if (_glfw.x11.drag.active) {
|
||||
// Send leave to current target
|
||||
if (_glfw.x11.drag.current_target != None) {
|
||||
send_xdnd_leave(_glfw.x11.drag.current_target);
|
||||
}
|
||||
|
||||
// Ungrab the pointer
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
|
||||
_glfw.x11.drag.active = false;
|
||||
_glfw.x11.drag.current_target = None;
|
||||
}
|
||||
|
||||
// Clean up thumbnail resources
|
||||
if (_glfw.x11.drag.thumbnail_gc != None) {
|
||||
XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc);
|
||||
_glfw.x11.drag.thumbnail_gc = None;
|
||||
}
|
||||
if (_glfw.x11.drag.thumbnail_pixmap != None) {
|
||||
XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap);
|
||||
_glfw.x11.drag.thumbnail_pixmap = None;
|
||||
}
|
||||
if (_glfw.x11.drag.thumbnail_window != None) {
|
||||
XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window);
|
||||
_glfw.x11.drag.thumbnail_window = None;
|
||||
}
|
||||
|
||||
// Free type atoms
|
||||
if (_glfw.x11.drag.type_atoms) {
|
||||
free(_glfw.x11.drag.type_atoms);
|
||||
_glfw.x11.drag.type_atoms = NULL;
|
||||
}
|
||||
_glfw.x11.drag.type_count = 0;
|
||||
|
||||
// Free pending requests
|
||||
if (_glfw.x11.drag.pending_requests) {
|
||||
for (size_t i = 0; i < _glfw.x11.drag.pending_count; i++) {
|
||||
free((void*)_glfw.x11.drag.pending_requests[i].mime_type);
|
||||
}
|
||||
free(_glfw.x11.drag.pending_requests);
|
||||
_glfw.x11.drag.pending_requests = NULL;
|
||||
}
|
||||
_glfw.x11.drag.pending_count = 0;
|
||||
_glfw.x11.drag.pending_capacity = 0;
|
||||
}
|
||||
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type) {
|
||||
// Find the pending request for this MIME type
|
||||
for (size_t i = 0; i < _glfw.x11.drag.pending_count; i++) {
|
||||
if (_glfw.x11.drag.pending_requests[i].inflight &&
|
||||
strcmp(_glfw.x11.drag.pending_requests[i].mime_type, mime_type) == 0) {
|
||||
|
||||
// Get the drag event with data from the application
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (!window) return ENODEV;
|
||||
|
||||
GLFWDragEvent ev = {
|
||||
.type = GLFW_DRAG_DATA_REQUEST,
|
||||
.mime_type = mime_type,
|
||||
.data = NULL,
|
||||
.data_sz = 0,
|
||||
.err_num = 0
|
||||
};
|
||||
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
|
||||
// Send the data
|
||||
send_drag_data(mime_type, ev.data, ev.data_sz,
|
||||
_glfw.x11.drag.pending_requests[i].requestor,
|
||||
_glfw.x11.drag.pending_requests[i].property,
|
||||
_glfw.x11.drag.pending_requests[i].target);
|
||||
|
||||
// Send SelectionNotify
|
||||
XEvent reply;
|
||||
memset(&reply, 0, sizeof(reply));
|
||||
reply.xselection.type = SelectionNotify;
|
||||
reply.xselection.display = _glfw.x11.display;
|
||||
reply.xselection.requestor = _glfw.x11.drag.pending_requests[i].requestor;
|
||||
reply.xselection.selection = _glfw.x11.XdndSelection;
|
||||
reply.xselection.target = _glfw.x11.drag.pending_requests[i].target;
|
||||
reply.xselection.property = _glfw.x11.drag.pending_requests[i].property;
|
||||
reply.xselection.time = CurrentTime;
|
||||
|
||||
XSendEvent(_glfw.x11.display,
|
||||
_glfw.x11.drag.pending_requests[i].requestor,
|
||||
False, 0, &reply);
|
||||
|
||||
_glfw.x11.drag.pending_requests[i].inflight = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void _glfwPlatformFreeDragSourceData(void) {}
|
||||
int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; }
|
||||
// }}}
|
||||
|
||||
Reference in New Issue
Block a user