Implement X11 drag source backend for the new API

Fixes #9522
This commit is contained in:
copilot-swe-agent[bot]
2026-02-18 10:36:49 +00:00
committed by Kovid Goyal
parent 3cd2aa6a2f
commit 0dfb01e8c5
3 changed files with 728 additions and 5 deletions

1
glfw/x11_init.c vendored
View File

@@ -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
View File

@@ -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
View File

@@ -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; }
// }}}