From 0dfb01e8c53ff041fcaaff31bcad5fcc8ad3d1bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:36:49 +0000 Subject: [PATCH] Implement X11 drag source backend for the new API Fixes #9522 --- glfw/x11_init.c | 1 + glfw/x11_platform.h | 21 ++ glfw/x11_window.c | 711 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 728 insertions(+), 5 deletions(-) diff --git a/glfw/x11_init.c b/glfw/x11_init.c index b2cce6a17..0b518af51 100644 --- a/glfw/x11_init.c +++ b/glfw/x11_init.c @@ -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 diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index 70a8541f3..a74dae830 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -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 { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 94f132de1..78193eaa8 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -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; } // }}}