diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 9bc765cd6..1a712b0dd 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -179,6 +179,11 @@ typedef struct _GLFWwindowNS // Drag source state char** sourceMimes; // Array of MIME type strings for drag source int sourceMimeCount; // Number of source MIME types + + // Pending drag source data requests (for cleanup on cancellation) + GLFWDragSourceData** pendingDragSourceData; + int pendingDragSourceDataCount; + int pendingDragSourceDataCapacity; } _GLFWwindowNS; // Cocoa-specific global data diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 33a9dda5c..0d40fe400 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -770,6 +770,92 @@ typedef struct { int errorCode; // Error code if any } GLFWFilePromiseState; +// Helper function to clean up a single drag source data +static void +cleanup_ns_drag_source_data(GLFWDragSourceData* data) { + if (!data) return; + if (data->platform_data) { + GLFWFilePromiseState* state = (GLFWFilePromiseState*)data->platform_data; + // If the data wasn't finished, call completion with error + if (!data->finished && state->completionHandler) { + NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ECANCELED userInfo:nil]; + state->completionHandler(error); + Block_release(state->completionHandler); + state->completionHandler = nil; + } + if (state->fileHandle) { + @try { + [state->fileHandle closeFile]; + } @catch (NSException* e) { + (void)e; + } + [state->fileHandle release]; + state->fileHandle = nil; + } + if (state->destinationURL) { + [state->destinationURL release]; + state->destinationURL = nil; + } + free(state); + } + free(data->mime_type); + free(data); +} + +// Add a drag source data to the pending array for tracking +static bool +add_ns_pending_drag_source_data(_GLFWwindow* window, GLFWDragSourceData* data) { + if (!window || !data) return false; + + // Grow array if needed + if (window->ns.pendingDragSourceDataCount >= window->ns.pendingDragSourceDataCapacity) { + // Cap maximum capacity to prevent excessive memory use + if (window->ns.pendingDragSourceDataCapacity >= 512) { + return false; + } + int new_capacity = window->ns.pendingDragSourceDataCapacity ? window->ns.pendingDragSourceDataCapacity * 2 : 4; + GLFWDragSourceData** new_array = realloc(window->ns.pendingDragSourceData, + new_capacity * sizeof(GLFWDragSourceData*)); + if (!new_array) return false; + window->ns.pendingDragSourceData = new_array; + window->ns.pendingDragSourceDataCapacity = new_capacity; + } + + window->ns.pendingDragSourceData[window->ns.pendingDragSourceDataCount++] = data; + return true; +} + +// Remove a specific drag source data from the pending array +static void +remove_ns_pending_drag_source_data(_GLFWwindow* window, GLFWDragSourceData* data) { + if (!window || !data) return; + + for (int i = 0; i < window->ns.pendingDragSourceDataCount; i++) { + if (window->ns.pendingDragSourceData[i] == data) { + // Shift remaining elements + for (int j = i; j < window->ns.pendingDragSourceDataCount - 1; j++) { + window->ns.pendingDragSourceData[j] = window->ns.pendingDragSourceData[j + 1]; + } + window->ns.pendingDragSourceDataCount--; + return; + } + } +} + +// Clean up all pending drag source data for a window +static void +cleanup_all_ns_pending_drag_source_data(_GLFWwindow* window) { + if (!window) return; + + for (int i = 0; i < window->ns.pendingDragSourceDataCount; i++) { + cleanup_ns_drag_source_data(window->ns.pendingDragSourceData[i]); + } + free(window->ns.pendingDragSourceData); + window->ns.pendingDragSourceData = NULL; + window->ns.pendingDragSourceDataCount = 0; + window->ns.pendingDragSourceDataCapacity = 0; +} + @interface GLFWFilePromiseProviderDelegate : NSObject { GLFWid windowId; @@ -876,6 +962,16 @@ typedef struct { return; } + // Track this source data for cleanup on cancellation + if (!add_ns_pending_drag_source_data(window, source_data)) { + // Call completion handler with memory error before cleanup + completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]); + // Mark as finished to prevent cleanup_ns_drag_source_data from calling completionHandler again + source_data->finished = true; + cleanup_ns_drag_source_data(source_data); + return; + } + // Notify the application via callback - this will trigger glfwSendDragData calls _glfwInputDragSourceRequest(window, mimeType, source_data); @@ -1656,7 +1752,10 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co drop_data->eof_reached = false; drop_data->current_data = NULL; drop_data->data_offset = 0; - _glfwInputDrop(window, drop_data); + // Check if the drop is from this application + // draggingSource returns the source object if the drag started in this application + bool from_self = ([sender draggingSource] != nil); + _glfwInputDrop(window, drop_data, from_self); // Note: drop_data is NOT freed here - application must call glfwFinishDrop return YES; } @@ -1685,6 +1784,8 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co (void)session; (void)screenPoint; (void)operation; + // Clean up all pending drag source data + cleanup_all_ns_pending_drag_source_data(window); // Notify the application that the drag source is closed _glfwInputDragSourceRequest(window, NULL, NULL); // Clean up source MIME types @@ -3908,6 +4009,8 @@ void _glfwCocoaPostEmptyEvent(void) { } void _glfwPlatformCancelDrag(_GLFWwindow* window) { + // Clean up all pending drag source data + cleanup_all_ns_pending_drag_source_data(window); // Clean up source MIME types for (int i = 0; i < window->ns.sourceMimeCount; i++) { free(window->ns.sourceMimes[i]); @@ -4056,11 +4159,13 @@ ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* d state->completionHandler = nil; } - // Clean up - [state->fileHandle release]; - state->fileHandle = nil; - [state->destinationURL release]; - state->destinationURL = nil; + // Remove from pending list and clean up + _GLFWwindow* window = _glfwWindowForId(source_data->window_id); + if (window) { + remove_ns_pending_drag_source_data(window, source_data); + } + // source_data->finished is true, so cleanup_ns_drag_source_data won't call completionHandler again + cleanup_ns_drag_source_data(source_data); return 0; } @@ -4086,11 +4191,13 @@ ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* d state->completionHandler = nil; } - // Clean up - [state->fileHandle release]; - state->fileHandle = nil; - [state->destinationURL release]; - state->destinationURL = nil; + // Remove from pending list and clean up + _GLFWwindow* window = _glfwWindowForId(source_data->window_id); + if (window) { + remove_ns_pending_drag_source_data(window, source_data); + } + // source_data->finished is true, so cleanup_ns_drag_source_data won't call completionHandler again + cleanup_ns_drag_source_data(source_data); return 0; } diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 01bc953a4..3031dfedb 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1800,6 +1800,12 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * responsible for calling glfwFinishDrop when it has finished reading * the dropped data, even if reading fails or is not needed. * + * @param[in] window The window that received the drop. + * @param[in] drop Opaque drop data pointer (heap-allocated). + * @param[in] from_self true if the drop originated from this application + * (i.e., the application is both the drag source and drop target), false + * if the drop came from an external application. + * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * @sa @ref glfwGetDropMimeTypes @@ -1807,10 +1813,11 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * @sa @ref glfwFinishDrop * * @since Changed in version 4.0 to receive opaque drop data pointer. + * @since Changed in version 4.0 to receive from_self parameter. * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*, GLFWDropData*); +typedef void (* GLFWdropfun)(GLFWwindow*, GLFWDropData*, bool from_self); /*! @brief Drag event types. * diff --git a/glfw/input.c b/glfw/input.c index 86667c544..b5b1a8564 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -404,10 +404,10 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered) // Notifies shared code of files or directories dropped on a window // -void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop) +void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop, bool from_self) { if (window->callbacks.drop) - window->callbacks.drop((GLFWwindow*) window, drop); + window->callbacks.drop((GLFWwindow*) window, drop, from_self); } // Notifies shared code of a drag event diff --git a/glfw/internal.h b/glfw/internal.h index a946b34aa..5ae8321c3 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -857,7 +857,7 @@ void _glfwInputScroll(_GLFWwindow* window, const GLFWScrollEvent *ev); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); -void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop); +void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop, bool from_self); int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count); void _glfwInputDragSourceRequest(_GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 87a5b37df..755728425 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -415,7 +415,9 @@ typedef struct _GLFWlibraryWayland char** mimes; // Array of MIME type strings int mime_count; // Number of MIME types GLFWid window_id; // Window that initiated the drag - GLFWDragSourceData* current_request; // Current data request being processed + GLFWDragSourceData** pending_requests; // Array of pending data requests + int pending_request_count; // Number of pending requests + int pending_request_capacity; // Capacity of the pending requests array struct wl_surface *drag_icon; struct wp_viewport *drag_viewport; } drag; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 4afae0ae6..696703bf8 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2554,7 +2554,9 @@ drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { destroy_data_offer(offer); drop_data->eof_reached = false; memset(offer, 0, sizeof(offer[0])); - _glfwInputDrop(window, drop_data); + // Check if the drop is from this application + bool from_self = (_glfw.wl.drag.window_id != 0); + _glfwInputDrop(window, drop_data, from_self); // Note: drop_data is NOT freed here - application must call glfwFinishDrop break; } @@ -3007,7 +3009,7 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) { // Drag operation implementation static void -cleanup_drag_source_data(GLFWDragSourceData* data) { +cleanup_wl_drag_source_data(GLFWDragSourceData* data) { if (!data) return; if (data->write_fd >= 0) { close(data->write_fd); @@ -3017,17 +3019,74 @@ cleanup_drag_source_data(GLFWDragSourceData* data) { free(data); } +// Remove a finished request from the pending requests array +static void +remove_wl_pending_request(int index) { + if (index < 0 || index >= _glfw.wl.drag.pending_request_count) return; + + cleanup_wl_drag_source_data(_glfw.wl.drag.pending_requests[index]); + + // Shift remaining elements + for (int i = index; i < _glfw.wl.drag.pending_request_count - 1; i++) { + _glfw.wl.drag.pending_requests[i] = _glfw.wl.drag.pending_requests[i + 1]; + } + _glfw.wl.drag.pending_request_count--; +} + +// Clean up all finished requests from the pending requests array +static void +cleanup_wl_finished_requests(void) { + for (int i = _glfw.wl.drag.pending_request_count - 1; i >= 0; i--) { + if (_glfw.wl.drag.pending_requests[i]->finished) { + remove_wl_pending_request(i); + } + } +} + +// Clean up all pending requests +static void +cleanup_all_wl_pending_requests(void) { + for (int i = 0; i < _glfw.wl.drag.pending_request_count; i++) { + cleanup_wl_drag_source_data(_glfw.wl.drag.pending_requests[i]); + } + free(_glfw.wl.drag.pending_requests); + _glfw.wl.drag.pending_requests = NULL; + _glfw.wl.drag.pending_request_count = 0; + _glfw.wl.drag.pending_request_capacity = 0; +} + +// Add a request to the pending requests array +static bool +add_wl_pending_request(GLFWDragSourceData* request) { + // First, clean up any finished requests to make room + cleanup_wl_finished_requests(); + + // Grow the array if necessary + if (_glfw.wl.drag.pending_request_count >= _glfw.wl.drag.pending_request_capacity) { + // Cap maximum capacity to prevent excessive memory use + if (_glfw.wl.drag.pending_request_capacity >= 512) { + return false; + } + int new_capacity = _glfw.wl.drag.pending_request_capacity ? _glfw.wl.drag.pending_request_capacity * 2 : 4; + GLFWDragSourceData** new_array = realloc(_glfw.wl.drag.pending_requests, + new_capacity * sizeof(GLFWDragSourceData*)); + if (!new_array) return false; + _glfw.wl.drag.pending_requests = new_array; + _glfw.wl.drag.pending_request_capacity = new_capacity; + } + + _glfw.wl.drag.pending_requests[_glfw.wl.drag.pending_request_count++] = request; + return true; +} + static void cleanup_drag(struct wl_data_source *source) { // Notify the application that the drag source is closed _GLFWwindow *window = _glfwWindowForId(_glfw.wl.drag.window_id); if (window && window->callbacks.dragSource) _glfwInputDragSourceRequest(window, NULL, NULL); - // Clean up any pending data request - if (_glfw.wl.drag.current_request) { - cleanup_drag_source_data(_glfw.wl.drag.current_request); - _glfw.wl.drag.current_request = NULL; - } + // Clean up all pending data requests + cleanup_all_wl_pending_requests(); // Clean up MIME type strings for (int i = 0; i < _glfw.wl.drag.mime_count; i++) free(_glfw.wl.drag.mimes[i]); @@ -3065,13 +3124,15 @@ drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const request->error_code = 0; if (!request->mime_type) { - cleanup_drag_source_data(request); + cleanup_wl_drag_source_data(request); return; } - // Store as current request - if (_glfw.wl.drag.current_request) cleanup_drag_source_data(_glfw.wl.drag.current_request); - _glfw.wl.drag.current_request = request; + // Add to pending requests array + if (!add_wl_pending_request(request)) { + cleanup_wl_drag_source_data(request); + return; + } // Notify the application via callback _glfwInputDragSourceRequest(window, mime_type, request); @@ -3210,6 +3271,8 @@ _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, siz source_data->finished = true; close(source_data->write_fd); source_data->write_fd = -1; + // Clean up this and any other finished requests + cleanup_wl_finished_requests(); return 0; } @@ -3219,6 +3282,8 @@ _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, siz source_data->error_code = (int)size; close(source_data->write_fd); source_data->write_fd = -1; + // Clean up this and any other finished requests + cleanup_wl_finished_requests(); return 0; } @@ -3238,6 +3303,8 @@ _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, siz source_data->error_code = errno; close(source_data->write_fd); source_data->write_fd = -1; + // Clean up this and any other finished requests + cleanup_wl_finished_requests(); return -errno; } diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index 462a11c58..2fa08c036 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -400,7 +400,9 @@ typedef struct _GLFWlibraryX11 Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink bool active; _GLFWwindow* window; // Window that initiated the drag - GLFWDragSourceData* current_request; // Current data request being processed + GLFWDragSourceData** pending_requests; // Array of pending data requests + int pending_request_count; // Number of pending requests + int pending_request_capacity; // Capacity of the pending requests array } drag; struct { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 91c765e6c..019fcafc7 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -88,6 +88,10 @@ x11_cancel_momentum_scroll_timer(void) { #define _GLFW_XDND_VERSION 5 +// Forward declarations for drag source data handling +static void cleanup_x11_drag_source_data(GLFWDragSourceData* data); +static bool add_x11_pending_request(GLFWDragSourceData* request); + // Wait for data to arrive using poll // This avoids blocking other threads via the per-display Xlib lock that also // covers GLX functions @@ -1101,13 +1105,97 @@ static void handleSelectionRequest(XEvent* event) const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent reply = { SelectionNotify }; - reply.xselection.property = writeTargetToProperty(request); reply.xselection.display = request->display; reply.xselection.requestor = request->requestor; reply.xselection.selection = request->selection; reply.xselection.target = request->target; reply.xselection.time = request->time; + // Handle XdndSelection (drag and drop) specially + if (request->selection == _glfw.x11.XdndSelection && _glfw.x11.drag.active && _glfw.x11.drag.window) { + // Handle TARGETS request for XdndSelection + if (request->target == _glfw.x11.TARGETS) { + // Return the list of supported MIME type atoms + Atom *targets = calloc(_glfw.x11.drag.mime_count + 2, sizeof(Atom)); + if (targets) { + targets[0] = _glfw.x11.TARGETS; + targets[1] = _glfw.x11.MULTIPLE; + for (int i = 0; i < _glfw.x11.drag.mime_count; i++) { + targets[i + 2] = _glfw.x11.drag.type_atoms[i]; + } + XChangeProperty(_glfw.x11.display, + request->requestor, + request->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)targets, + _glfw.x11.drag.mime_count + 2); + free(targets); + reply.xselection.property = request->property; + } else { + reply.xselection.property = None; + } + } else { + // Find the matching MIME type for the requested target + const char* mime_type = NULL; + for (int i = 0; i < _glfw.x11.drag.mime_count; i++) { + if (_glfw.x11.drag.type_atoms[i] == request->target) { + mime_type = _glfw.x11.drag.mimes[i]; + break; + } + } + + if (mime_type) { + // Create a drag source data request + GLFWDragSourceData* source_data = calloc(1, sizeof(GLFWDragSourceData)); + if (source_data) { + source_data->window_id = _glfw.x11.drag.window->id; + source_data->mime_type = _glfw_strdup(mime_type); + source_data->write_fd = -1; + source_data->finished = false; + source_data->error_code = 0; + // Store request info in platform_data for later use + // We'll use a simple struct to hold the X11-specific data + struct { + Window requestor; + Atom property; + Atom target; + } *x11_data = malloc(sizeof(*x11_data)); + if (x11_data && source_data->mime_type) { + x11_data->requestor = request->requestor; + x11_data->property = request->property; + x11_data->target = request->target; + source_data->platform_data = x11_data; + + if (add_x11_pending_request(source_data)) { + // Notify the application via callback + _glfwInputDragSourceRequest(_glfw.x11.drag.window, mime_type, source_data); + reply.xselection.property = request->property; + } else { + free(x11_data); + free(source_data->mime_type); + free(source_data); + reply.xselection.property = None; + } + } else { + free(x11_data); + free(source_data->mime_type); + free(source_data); + reply.xselection.property = None; + } + } else { + reply.xselection.property = None; + } + } else { + reply.xselection.property = None; + } + } + } else { + // Handle regular clipboard/primary selection + reply.xselection.property = writeTargetToProperty(request); + } + XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } @@ -1974,7 +2062,10 @@ static void processEvent(XEvent *event) drop_data->x11_source = _glfw.x11.xdnd.source; drop_data->x11_version = _glfw.x11.xdnd.version; - _glfwInputDrop(window, drop_data); + // Check if the drop is from this application + bool from_self = (_glfw.x11.drag.source_window != None && + _glfw.x11.xdnd.source == _glfw.x11.drag.source_window); + _glfwInputDrop(window, drop_data, from_self); // Note: drop_data is NOT freed here - application must call glfwFinishDrop } @@ -3717,21 +3808,79 @@ static void cleanup_x11_drag_source_data(GLFWDragSourceData* data) { close(data->write_fd); data->write_fd = -1; } + free(data->platform_data); free(data->mime_type); free(data); } +// Remove a finished request from the pending requests array +static void +remove_x11_pending_request(int index) { + if (index < 0 || index >= _glfw.x11.drag.pending_request_count) return; + + cleanup_x11_drag_source_data(_glfw.x11.drag.pending_requests[index]); + + // Shift remaining elements + for (int i = index; i < _glfw.x11.drag.pending_request_count - 1; i++) { + _glfw.x11.drag.pending_requests[i] = _glfw.x11.drag.pending_requests[i + 1]; + } + _glfw.x11.drag.pending_request_count--; +} + +// Clean up all finished requests from the pending requests array +static void +cleanup_x11_finished_requests(void) { + for (int i = _glfw.x11.drag.pending_request_count - 1; i >= 0; i--) { + if (_glfw.x11.drag.pending_requests[i]->finished) { + remove_x11_pending_request(i); + } + } +} + +// Clean up all pending requests +static void +cleanup_all_x11_pending_requests(void) { + for (int i = 0; i < _glfw.x11.drag.pending_request_count; i++) { + cleanup_x11_drag_source_data(_glfw.x11.drag.pending_requests[i]); + } + free(_glfw.x11.drag.pending_requests); + _glfw.x11.drag.pending_requests = NULL; + _glfw.x11.drag.pending_request_count = 0; + _glfw.x11.drag.pending_request_capacity = 0; +} + +// Add a request to the pending requests array +static bool +add_x11_pending_request(GLFWDragSourceData* request) { + // First, clean up any finished requests to make room + cleanup_x11_finished_requests(); + + // Grow the array if necessary + if (_glfw.x11.drag.pending_request_count >= _glfw.x11.drag.pending_request_capacity) { + // Cap maximum capacity to prevent excessive memory use + if (_glfw.x11.drag.pending_request_capacity >= 512) { + return false; + } + int new_capacity = _glfw.x11.drag.pending_request_capacity ? _glfw.x11.drag.pending_request_capacity * 2 : 4; + GLFWDragSourceData** new_array = realloc(_glfw.x11.drag.pending_requests, + new_capacity * sizeof(GLFWDragSourceData*)); + if (!new_array) return false; + _glfw.x11.drag.pending_requests = new_array; + _glfw.x11.drag.pending_request_capacity = new_capacity; + } + + _glfw.x11.drag.pending_requests[_glfw.x11.drag.pending_request_count++] = request; + return true; +} + static void cleanupDragSource(void) { // Notify the application that the drag source is closed if (_glfw.x11.drag.window && _glfw.x11.drag.window->callbacks.dragSource) { _glfwInputDragSourceRequest(_glfw.x11.drag.window, NULL, NULL); } - // Clean up any pending data request - if (_glfw.x11.drag.current_request) { - cleanup_x11_drag_source_data(_glfw.x11.drag.current_request); - _glfw.x11.drag.current_request = NULL; - } + // Clean up all pending data requests + cleanup_all_x11_pending_requests(); // Clean up MIME type strings and atoms for (int i = 0; i < _glfw.x11.drag.mime_count; i++) { @@ -3834,8 +3983,7 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) { if (!source_data || source_data->finished) return -EINVAL; - // For X11, we typically set properties for SelectionRequest events - // The write_fd is used if we set up a pipe-based transfer + // For X11, we set properties via XChangeProperty in response to SelectionRequest // End of data: NULL data pointer and size zero if (!data && size == 0) { @@ -3844,6 +3992,8 @@ ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* d close(source_data->write_fd); source_data->write_fd = -1; } + // Clean up this and any other finished requests + cleanup_x11_finished_requests(); return 0; } @@ -3855,12 +4005,36 @@ ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* d close(source_data->write_fd); source_data->write_fd = -1; } + // Clean up this and any other finished requests + cleanup_x11_finished_requests(); return 0; } - // For X11, data is typically set via XChangeProperty in response to SelectionRequest - // Store the data in platform_data for the SelectionRequest handler - // Non-blocking write - retry on EINTR, return 0 on would-block + // For X11, use XChangeProperty to set the data on the requestor window + if (source_data->platform_data) { + struct { + Window requestor; + Atom property; + Atom target; + } *x11_data = source_data->platform_data; + + XChangeProperty(_glfw.x11.display, + x11_data->requestor, + x11_data->property, + x11_data->target, + 8, + PropModeReplace, + (unsigned char*)data, + size); + XFlush(_glfw.x11.display); + + // Mark as finished after sending data + source_data->finished = true; + cleanup_x11_finished_requests(); + return (ssize_t)size; + } + + // Fallback: Non-blocking write if we have an fd if (source_data->write_fd >= 0) { ssize_t written; do { @@ -3876,13 +4050,15 @@ ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* d source_data->error_code = errno; close(source_data->write_fd); source_data->write_fd = -1; + // Clean up this and any other finished requests + cleanup_x11_finished_requests(); return -errno; } return written; } - // No fd available, all data is accepted (buffered by platform_data) - return (ssize_t)size; + // No valid mechanism to send data + return -EINVAL; } void _glfwPlatformUpdateDragState(_GLFWwindow* window) { diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index a48ff16dc..bae5f4b43 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1535,10 +1535,11 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * @sa @ref glfwFinishDrop * * @since Changed in version 4.0 to receive opaque drop data pointer. + * @since Changed in version 4.0 to receive from_self parameter. * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*, GLFWDropData*); +typedef void (* GLFWdropfun)(GLFWwindow*, GLFWDropData*, bool from_self); /*! @brief Drag event types. * diff --git a/kitty/glfw.c b/kitty/glfw.c index b607ff8d3..74944e2f2 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -742,11 +742,20 @@ get_mime_data(GLFWDropData *drop, const char **mimes, int mime_count, PyObject * } static void -drop_callback(GLFWwindow *w, GLFWDropData *drop) { +drop_callback(GLFWwindow *w, GLFWDropData *drop, bool from_self) { int num_mimes; const char** mimes = glfwGetDropMimeTypes(drop, &num_mimes); RAII_PyObject(ans, PyDict_New()); - get_mime_data(drop, mimes, num_mimes, ans); + if (from_self && global_state.drag_source.drag_data) { + // For self-drops, copy data directly from drag_source.drag_data + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next(global_state.drag_source.drag_data, &pos, &key, &value)) { + if (PyDict_SetItem(ans, key, value) != 0) break; + } + } else { + get_mime_data(drop, mimes, num_mimes, ans); + } RAII_PyObject(exc, PyErr_GetRaisedException()); glfwFinishDrop(drop, GLFW_DRAG_OPERATION_COPY, true); if (!set_callback_window(w)) return;