From e5eb63fcd026b7e2330c719323e5ea807b802715 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Feb 2026 10:03:33 +0530 Subject: [PATCH] Refactor drag source API to be async Wayland implementation done. Other two backends remain. --- glfw/cocoa_window.m | 20 +-- glfw/glfw3.h | 167 +++---------------- glfw/input.c | 84 +++++----- glfw/internal.h | 35 ++-- glfw/wl_platform.h | 16 +- glfw/wl_window.c | 380 ++++++++++++++++++++----------------------- glfw/x11_platform.h | 8 +- glfw/x11_window.c | 336 ++------------------------------------ kitty/data-types.h | 1 + kitty/glfw-wrapper.c | 3 - kitty/glfw-wrapper.h | 77 +++------ kitty/glfw.c | 86 +++++----- kitty/state.c | 4 +- kitty/state.h | 10 +- 14 files changed, 351 insertions(+), 876 deletions(-) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 9af15c3bf..c487210d4 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -4099,20 +4099,11 @@ void _glfwCocoaPostEmptyEvent(void) { [NSApp postEvent:event atStart:YES]; } -void _glfwPlatformCancelDrag(_GLFWwindow* window) { - // 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); -} - -int _glfwPlatformStartDrag(_GLFWwindow* window, - const char* const* mime_types, - int mime_count, - const GLFWimage* thumbnail, - int operations) { +int +_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWDragSourceItem *items, int item_count, const GLFWimage* thumbnail, int operations) { // cleanup stored data from previous drag cleanup_all_ns_pending_drag_source_data(window); + if (!items || !item_count) { cleanup_all_ns_pending_drag_source_data(window); return 0; } // Store the operations for the dragging source callback window->ns.dragOperations = operations; @@ -4168,11 +4159,6 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, [dragItems addObject:dragItem]; } - if (dragItems.count == 0) { - _glfwPlatformCancelDrag(window); - return EINVAL; - } - // Start the drag session - try current event first, then create a synthetic one NSEvent* event = [NSApp currentEvent]; if (!event || ([event type] != NSEventTypeLeftMouseDown && diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 2df1899a6..44b8fdea0 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1811,57 +1811,30 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,const GLFWScrollEvent*); */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief Drag event types. - * - * These constants are used to identify the type of drag event. - * - * @ingroup input - */ typedef enum { - /*! The drag operation entered the window. */ - GLFW_DRAG_ENTER = 1, - /*! The drag operation moved within the window. */ - GLFW_DRAG_MOVE = 2, - /*! The drag operation left the window. */ - GLFW_DRAG_LEAVE = 3, - /*! Async status update request (xpos/ypos are invalid). */ - GLFW_DRAG_STATUS_UPDATE = 4 + GLFW_DRAG_DATA_REQUEST, + GLFW_DRAG_CANCELLED, + GLFW_DRAG_FINSHED, + GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified + GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType + GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet } GLFWDragEventType; -/*! @brief Opaque drag source data handle. - * - * This is an opaque handle to a heap-allocated object that represents - * data being requested from a drag source. The lifetime is managed by - * the GLFW backend - it is freed on end of data, error, drag source - * cancellation, or at exit. - * - * @since Added in version 4.0. - * - * @ingroup input - */ -typedef struct GLFWDragSourceData GLFWDragSourceData; +typedef struct GLFWDragSourceItem { + const char *mime_type; + // Can be on null to provide data when the drag is started should be used only when the data is relatively small + const char *optional_data; + size_t data_size; +} GLFWDragSourceItem; -/*! @brief The function pointer type for drag source data request callbacks. - * - * This is the function pointer type for callbacks invoked when the OS - * requests data for a specific MIME type from the active drag source. - * The callback is called on the GUI thread. - * - * @param[in] window The window that initiated the drag. - * @param[in] mime_type The MIME type being requested, or NULL if the OS - * has closed the drag source. - * @param[in] source_data Opaque pointer to a heap-allocated object. Use this - * pointer when calling @ref glfwSendDragData to send data chunks. - * - * @sa @ref glfwStartDrag - * @sa @ref glfwSendDragData - * @sa @ref glfwSetDragSourceCallback - * - * @since Added in version 4.0. - * - * @ingroup input - */ -typedef void (* GLFWdragsourcefun)(GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data); +typedef struct GLFWDragEvent { + GLFWDragEventType type; + const char *mime_type; + const char *data; size_t data_sz; int err_num; + GLFWDragOperationType action; // can be 0 indicating no action +} GLFWDragEvent; + +typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev); /*! @brief The function pointer type for drag event callbacks. * @@ -4999,106 +4972,8 @@ GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow *window, GLFWdropev GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window); // ask for update before GLFW_DROP_DROP happens GLFWAPI int glfwRequestDropData(GLFWwindow *window, const char *mime); GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op); - -/*! @brief Sets the drag source data request callback. - * - * This function sets the callback that is invoked when the OS requests data - * for a specific MIME type from the currently active drag source. The callback - * receives the MIME type and an opaque pointer to a heap-allocated object. - * The application should call @ref glfwSendDragData with chunks of data. - * - * If the callback is called with a NULL mime_type, the OS has closed the - * drag source. - * - * @param[in] window The window whose callback to set. - * @param[in] callback The new callback, or `NULL` to remove the currently set - * callback. - * @return The previously set callback, or `NULL` if no callback was set or the - * library had not been [initialized](@ref intro_init). - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref glfwStartDrag - * @sa @ref glfwSendDragData - * - * @since Added in version 4.0. - * - * @ingroup input - */ GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* window, GLFWdragsourcefun callback); - -/*! @brief Starts a drag operation with lazy data loading. - * - * This function starts a drag operation from the specified window. Data for - * each MIME type is loaded on demand when the OS requests it via the drag - * source callback set with @ref glfwSetDragSourceCallback. - * - * Calling with NULL mime_types or mime_count of 0 cancels the currently - * active drag source, if any. Similarly, when called with mime types, any - * currently active drag is canceled and replaced. - * - * @param[in] window The window initiating the drag. - * @param[in] mime_types Array of MIME type strings. - * @param[in] mime_count Number of MIME types in the array. - * @param[in] thumbnail Optional thumbnail/icon image to display during the - * drag operation, or `NULL` for no thumbnail. The image data is copied. - * @param[in] operations A bitfield containing ORed values from - * @ref GLFWDragOperationType specifying allowed operations. - * - * @return Zero on success, or a POSIX error code such as EINVAL or EIO on - * failure. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref drag_start - * @sa @ref glfwSetDragSourceCallback - * @sa @ref glfwSendDragData - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI int glfwStartDrag(GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations); - -/*! @brief Sends a chunk of drag data. - * - * This function is called by the application on the GUI thread to send chunks - * of data for a drag operation. Call this in response to the drag source - * callback. This function is non-blocking and may return before all data - * is written to the destination. - * - * End of data is indicated by calling with NULL data pointer and size zero. - * If an error occurs while reading data, call with NULL data pointer and - * size set to a POSIX error code. - * - * @param[in] source_data The opaque pointer received in the drag source callback. - * @param[in] data Pointer to the data chunk, or NULL to signal end of data or error. - * @param[in] size Size of the data chunk in bytes, or 0 for end of data, - * or a POSIX error code when data is NULL. - * - * @return The number of bytes sent (which may be less than size if the - * operation would block), or a negative POSIX error code on failure. - * For end-of-data or error signaling (NULL data), returns 0 on success - * or a negative error code. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref glfwStartDrag - * @sa @ref glfwSetDragSourceCallback - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size); +GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWDragSourceItem *items, size_t mime_count, const GLFWimage* thumbnail, int operations); /*! @brief Returns whether the specified joystick is present. * diff --git a/glfw/input.c b/glfw/input.c index c29c93d8d..e26e4df18 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -402,15 +402,6 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered) window->callbacks.cursorEnter((GLFWwindow*) window, entered); } -// Notifies shared code of a drag event -// -int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count) -{ - if (window->callbacks.drag) - return window->callbacks.drag((GLFWwindow*) window, event, xpos, ypos, mime_types, mime_count); - return 0; -} - // Notifies shared code of a drop event size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self) { if (!window->callbacks.drop_event) return 0; @@ -425,10 +416,9 @@ size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double x // Notifies shared code that the OS wants data for a MIME type from the drag source // -void _glfwInputDragSourceRequest(_GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data) -{ - if (window->callbacks.dragSource) - window->callbacks.dragSource((GLFWwindow*) window, mime_type, source_data); +void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev) { + if (window->callbacks.drag_source) + window->callbacks.drag_source((GLFWwindow*) window, ev); } // Notifies shared code of a joystick connection or disconnection @@ -1134,16 +1124,6 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle, return cbfun; } -GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* handle, GLFWdragfun cbfun) -{ - _GLFWwindow* window = (_GLFWwindow*) handle; - assert(window != NULL); - - _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - _GLFW_SWAP_POINTERS(window->callbacks.drag, cbfun); - return cbfun; -} - GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow* handle, GLFWdropeventfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; @@ -1161,31 +1141,51 @@ GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* handle, GLFWdrag assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - _GLFW_SWAP_POINTERS(window->callbacks.dragSource, cbfun); + _GLFW_SWAP_POINTERS(window->callbacks.drag_source, cbfun); return cbfun; } -GLFWAPI int glfwStartDrag(GLFWwindow* handle, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations) -{ - _GLFWwindow* window = (_GLFWwindow*) handle; - assert(window != NULL); - - _GLFW_REQUIRE_INIT_OR_RETURN(EINVAL); - - // If no mime types, cancel any existing drag - if (!mime_types || mime_count <= 0) { - _glfwPlatformCancelDrag(window); - return 0; +void +_glfwFreeDragSourceData(void) { + _glfwPlatformFreeDragSourceData(); + if (_glfw.drag.items) { + for (size_t i = 0; i < _glfw.drag.item_count; i++) { + free((void*)_glfw.drag.items[i].mime_type); + free((void*)_glfw.drag.items[i].optional_data); + } + free(_glfw.drag.items); } - - return _glfwPlatformStartDrag(window, mime_types, mime_count, thumbnail, operations); + memset(&_glfw.drag, 0, sizeof(_glfw.drag)); } -GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) -{ - if (!source_data) return -EINVAL; - _GLFW_REQUIRE_INIT_OR_RETURN(-EINVAL); - return _glfwPlatformSendDragData(source_data, data, size); +GLFWAPI int +glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_count, const GLFWimage* thumbnail, int operations) { + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + _GLFW_REQUIRE_INIT_OR_RETURN(EINVAL); + if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type); + _glfwFreeDragSourceData(); + if (!items || !item_count) return 0; + _glfw.drag.items = calloc(item_count, sizeof(_glfw.drag.items[0])); + if (!_glfw.drag.items) return ENOMEM; + _glfw.drag.item_count = item_count; + for (size_t i = 0; i < item_count; i++) { + if (!items[i].mime_type || !items[i].mime_type[0]) { + _glfwFreeDragSourceData(); return EINVAL; + } + _glfw.drag.items[i].mime_type = _glfw_strdup(items[i].mime_type); + if (!_glfw.drag.items[i].mime_type) { _glfwFreeDragSourceData(); return ENOMEM; } + if (items[i].optional_data) { + _glfw.drag.items[i].optional_data = malloc(items[i].data_size); + if (!_glfw.drag.items[i].optional_data) { _glfwFreeDragSourceData(); return ENOMEM; } + memcpy((void*)_glfw.drag.items[i].optional_data, items[i].optional_data, items[i].data_size); + } + _glfw.drag.items[i].data_size = items[i].data_size; + } + _glfw.drag.window_id = window->id; + int ans = _glfwPlatformStartDrag(window, thumbnail, operations); + if (ans != 0) _glfwFreeDragSourceData(); + return ans; } GLFWAPI int glfwJoystickPresent(int jid) diff --git a/glfw/internal.h b/glfw/internal.h index 7cdbb5966..624d09784 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -85,17 +85,6 @@ typedef struct _GLFWjoystick _GLFWjoystick; typedef struct _GLFWtls _GLFWtls; typedef struct _GLFWmutex _GLFWmutex; -// Drag source data structure for chunked writing of drag data -// Lifetime is managed by the backend - freed on end of data, error, drag cancellation, or exit -struct GLFWDragSourceData { - GLFWid window_id; // ID of window that initiated the drag (use _glfwWindowForId to get pointer) - char* mime_type; // MIME type being sent (owned, copied from request) - int write_fd; // File descriptor for writing data (Wayland/X11), -1 if not used - bool finished; // Whether data sending is complete (EOF or error) - int error_code; // POSIX error code if an error occurred, 0 otherwise - void* platform_data; // Platform-specific data -}; - typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*); typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*); typedef void (* _GLFWswapintervalfun)(int); @@ -491,9 +480,7 @@ struct _GLFWwindow GLFWkeyboardfun keyboard; GLFWliveresizefun liveResize; - GLFWdragfun drag; - GLFWdragsourcefun dragSource; - + GLFWdragsourcefun drag_source; GLFWdropeventfun drop_event; } callbacks; @@ -677,6 +664,11 @@ struct _GLFWlibrary _GLFWlibraryEGL egl; // This is defined in osmesa_context.h _GLFWlibraryOSMesa osmesa; + + struct { + GLFWDragSourceItem *items; size_t item_count; + GLFWid window_id; + } drag; }; // Global state shared between compilation units of GLFW @@ -781,10 +773,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev); void _glfwPlatformChangeCursorTheme(void); -int _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations); -ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size); -void _glfwPlatformCancelDrag(_GLFWwindow* window); - void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); void _glfwPlatformWaitEventsTimeout(monotonic_t timeout); @@ -837,16 +825,19 @@ 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); - -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); // Platform functions for drop data reading - void _glfwPlatformRequestDropUpdate(_GLFWwindow* window); size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self); ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz); void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op); int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime); +// Platform functions for drag source +int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations); +void _glfwFreeDragSourceData(void); +void _glfwPlatformFreeDragSourceData(void); +void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev); +int _glfwPlatformDragDataReady(const char *mime_type); + void _glfwInputColorScheme(GLFWColorScheme, bool); void _glfwPlatformInputColorScheme(GLFWColorScheme); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index c2a83a0e8..8accfbe20 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -406,17 +406,19 @@ typedef struct _GLFWlibraryWayland bool has_preferred_buffer_scale; char *compositor_name; - // Drag operation state + // Drag source state struct { struct wl_data_source* source; - char** mimes; // Array of MIME type strings - int mime_count; // Number of MIME types - GLFWid window_id; // Window that initiated the drag - 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; + struct { + const char *mime_type; + int fd; + GLFWid watch_id; + char *pending_data; + size_t sz, offset; + } *data_requests; + size_t count, capacity; } drag; } _GLFWlibraryWayland; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 9c6378823..5ce601a63 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2299,7 +2299,7 @@ static void primary_selection_source_canceled(void *data UNUSED, struct zwp_prim static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) { } -static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) { +static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, unsigned int dnd_action UNUSED) { } static const struct wl_data_source_listener data_source_listener = { @@ -2483,7 +2483,7 @@ drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; mark_data_offer(offer, id); if (!offer->id) return; - offer->is_self_offer = (_glfw.wl.drag.window_id != 0); + offer->is_self_offer = (_glfw.drag.window_id != 0); offer->surface = surface; offer->serial = serial; offer->drag_accepted = false; @@ -3057,158 +3057,188 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) { return true; } -// Drag operation implementation - +// Drag source {{{ static void -cleanup_wl_drag_source_data(GLFWDragSourceData* data) { - if (!data) return; - if (data->write_fd >= 0) { - close(data->write_fd); - data->write_fd = -1; +drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + GLFWDragEvent ev = {.type=GLFW_DRAG_CANCELLED}; + _glfwInputDragSourceRequest(window, &ev); } - free(data->mime_type); - free(data); + _glfwFreeDragSourceData(); } -// Remove a finished request from the pending requests array +#define dr _glfw.wl.drag.data_requests[i] + 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--; +finish_drag_write(size_t i) { + if (dr.watch_id) removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); + dr.watch_id = 0; + if (dr.fd > -1) safe_close(dr.fd); + dr.fd = -1; + free((void*)dr.mime_type); dr.mime_type = NULL; +} + +static ssize_t +write_as_much_as_possible(int fd, const char *data, size_t sz) { + size_t ans = 0; + while (ans < sz) { + ssize_t ret = write(fd, data + ans, sz - ans); + if (ret < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN) return ans; + return ret; + } + ans += ret; + } + return ans; } -// 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); +send_drag_data(_GLFWwindow *window, size_t i) { + ssize_t ret; + bool has_preset_data = _glfw.drag.items[i].data_size > 0; +#define on_fail _glfwInputError(\ + GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \ + drag_source_cancelled(NULL, NULL) + + if (dr.sz > dr.offset) { + ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset); + if (ret < 0) { on_fail; } else { + dr.offset += ret; + if (dr.offset >= dr.sz) { + free(dr.pending_data); dr.sz = 0; dr.offset = 0; + if (has_preset_data) finish_drag_write(i); + } + } + } else if (has_preset_data) { + do { ret = write(dr.fd, _glfw.drag.items[i].optional_data, _glfw.drag.items[i].data_size); } while (ret < 0 && errno == EINTR); + if (ret < 0) { + on_fail; + } else { + if ((size_t)ret >= _glfw.drag.items[i].data_size) { + finish_drag_write(i); + } else { + void *pending = malloc(_glfw.drag.items[i].data_size - ret); + if (!pending) { on_fail; } else { + dr.pending_data = pending; dr.sz = _glfw.drag.items[i].data_size - ret; dr.offset = 0; + } + } + } + } else { + GLFWDragEvent ev = {.type=GLFW_DRAG_DATA_REQUEST, .mime_type=dr.mime_type}; + _glfwInputDragSourceRequest(window, &ev); + if (ev.err_num) { + if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; } + else drag_source_cancelled(NULL, NULL); + } else { + if (ev.data_sz) { + ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz); + if (ret >= 0) { + if ((size_t)ret < ev.data_sz) { + void *pending = malloc(ev.data_sz - ret); + if (!pending) { on_fail; } else { + dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0; + memcpy(pending, ev.data + ret, dr.sz); + } + } + } + _glfwInputDragSourceRequest(window, &ev); + if (ret < 0) { on_fail; } + } else finish_drag_write(i); + } + } +#undef on_fail +} + +static void +ready_for_drag_data(int fd, int events UNUSED, void *data UNUSED) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (!window) { _glfwFreeDragSourceData(); return; } + for (size_t i = 0; i < _glfw.wl.drag.count; i++) { + if (dr.fd == fd) { + send_drag_data(window, i); + break; } } } -// 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; +static GLFWid +add_drag_watch(int fd) { + return addWatch( + &_glfw.wl.eventLoopData, "drag_source", fd, POLLOUT | POLLERR | POLLHUP, true, ready_for_drag_data, NULL); } -// 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 +_glfwPlatformDragDataReady(const char *mime_type) { + for (size_t i = 0; i < _glfw.wl.drag.count; i++) { + if (strcmp(dr.mime_type, mime_type) == 0) { + if (!dr.watch_id) dr.watch_id = add_drag_watch(dr.fd); } - 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; + return 0; } 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); +drag_source_send(void *data, struct wl_data_source *source, const char *mime_type, int fd) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); +#define abort() safe_close(fd); drag_source_cancelled(data, source); return + if (!window) { abort(); } + mime_type = _glfw_strdup(mime_type); + if (!mime_type) { abort(); } + if (!_glfw.wl.drag.data_requests || _glfw.wl.drag.capacity <= _glfw.wl.drag.count + 1) { + _glfw.wl.drag.capacity = MAX(8u, _glfw.wl.drag.capacity * 2); + if (!(_glfw.wl.drag.data_requests = realloc(_glfw.wl.drag.data_requests, sizeof(_glfw.wl.drag.data_requests[0]) * _glfw.wl.drag.capacity))) { abort(); } + } + const size_t i = _glfw.wl.drag.count++; + memset(&dr, 0, sizeof(_glfw.wl.drag.data_requests[0])); + dr.mime_type = mime_type; dr.fd = fd; + dr.watch_id = add_drag_watch(fd); +#undef abort +} - // 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]); - free(_glfw.wl.drag.mimes); - _glfw.wl.drag.mimes = NULL; - _glfw.wl.drag.mime_count = 0; - _glfw.wl.drag.window_id = 0; - if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport); - if (_glfw.wl.drag.drag_icon) wl_surface_destroy(_glfw.wl.drag.drag_icon); - _glfw.wl.drag.drag_icon = NULL; _glfw.wl.drag.drag_viewport = NULL; - if (_glfw.wl.drag.source && _glfw.wl.drag.source != source) wl_data_source_destroy(_glfw.wl.drag.source); - _glfw.wl.drag.source = NULL; - if (source) wl_data_source_destroy(source); +#undef dr +static void +drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + GLFWDragEvent ev = {.type=GLFW_DRAG_ACCEPTED, .mime_type=mime_type}; + _glfwInputDragSourceRequest(window, &ev); + } else drag_source_cancelled(data, source); } static void -drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type, int fd) { - _GLFWwindow *window = _glfwWindowForId(_glfw.wl.drag.window_id); - if (!window) { - close(fd); - return; - } - - // Create a new drag source data request - GLFWDragSourceData* request = calloc(1, sizeof(GLFWDragSourceData)); - if (!request) { - close(fd); - return; - } - - request->window_id = _glfw.wl.drag.window_id; - request->mime_type = _glfw_strdup(mime_type); - request->write_fd = fd; - request->finished = false; - request->error_code = 0; - - if (!request->mime_type) { - cleanup_wl_drag_source_data(request); - return; - } - - // 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); -} - -static void -drag_source_cancelled(void *data UNUSED, struct wl_data_source *source) { - cleanup_drag(source); -} - -static void -drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type UNUSED) { -} - -static void -drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action UNUSED) { +drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + GLFWDragOperationType op = GLFW_DRAG_OPERATION_GENERIC; + switch (dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: op = 0; break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: op = GLFW_DRAG_OPERATION_COPY; break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: op = GLFW_DRAG_OPERATION_MOVE; break; + } + GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED, .action=op}; + _glfwInputDragSourceRequest(window, &ev); + } else drag_source_cancelled(data, source); } static void drag_source_dnd_drop_performed(void *data UNUSED, struct wl_data_source *source UNUSED) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED}; + _glfwInputDragSourceRequest(window, &ev); + } else drag_source_cancelled(data, source); } static void -drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source) { - drag_source_cancelled(data, source); +drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source UNUSED) { + _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); + if (window) { + GLFWDragEvent ev = {.type=GLFW_DRAG_FINSHED}; + _glfwInputDragSourceRequest(window, &ev); + } + _glfwFreeDragSourceData(); } static const struct wl_data_source_listener drag_source_listener = { @@ -3221,30 +3251,39 @@ static const struct wl_data_source_listener drag_source_listener = { }; void -_glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) { - cleanup_drag(_glfw.wl.drag.source); +_glfwPlatformFreeDragSourceData(void) { + if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport); + if (_glfw.wl.drag.drag_icon) wl_surface_destroy(_glfw.wl.drag.drag_icon); + if (_glfw.wl.drag.source) wl_data_source_destroy(_glfw.wl.drag.source); + if (_glfw.wl.drag.data_requests) { + for (size_t i = 0; i < _glfw.wl.drag.count; i++) { + free((void*)_glfw.wl.drag.data_requests[i].mime_type); + free(_glfw.wl.drag.data_requests[i].pending_data); + if (_glfw.wl.drag.data_requests[i].watch_id) removeWatch(&_glfw.wl.eventLoopData, _glfw.wl.drag.data_requests[i].watch_id); + if (_glfw.wl.drag.data_requests[i].fd > -1) { safe_close(_glfw.wl.drag.data_requests[i].fd); } + } + free(_glfw.wl.drag.data_requests); + } + memset(&_glfw.wl.drag, 0, sizeof(_glfw.wl.drag)); } int -_glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations) { +_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) { if (!_glfw.wl.dataDeviceManager) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device manager not available"); - return EIO; + return ENOTSUP; } if (!_glfw.wl.dataDevice) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device not available"); - return EIO; + return ENOTSUP; } - // Clean up any existing drag operation - _glfwPlatformCancelDrag(window); - // Create the data source _glfw.wl.drag.source = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager); if (!_glfw.wl.drag.source) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to create data source for drag"); - return EIO; + return ENOMEM; } // Set the DND action based on operation type (bitfield) @@ -3257,28 +3296,7 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int m wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; wl_data_source_set_actions(_glfw.wl.drag.source, wl_actions); - // Allocate storage for MIME types - _glfw.wl.drag.mimes = calloc(mime_count, sizeof(char*)); - _glfw.wl.drag.mime_count = mime_count; - _glfw.wl.drag.window_id = window->id; - - if (!_glfw.wl.drag.mimes) { - _glfwPlatformCancelDrag(window); - _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag MIME types"); - return ENOMEM; - } - - // Copy MIME types and offer them - for (int i = 0; i < mime_count; i++) { - _glfw.wl.drag.mimes[i] = _glfw_strdup(mime_types[i]); - if (!_glfw.wl.drag.mimes[i]) { - _glfwPlatformCancelDrag(window); - _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag MIME type"); - return ENOMEM; - } - wl_data_source_offer(_glfw.wl.drag.source, mime_types[i]); - } - + for (size_t i = 0; i < _glfw.drag.item_count; i++) wl_data_source_offer(_glfw.wl.drag.source, _glfw.drag.items[i].mime_type); wl_data_source_add_listener(_glfw.wl.drag.source, &drag_source_listener, NULL); // Set up the drag icon surface if thumbnail is provided @@ -3311,54 +3329,4 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int m return 0; } - -ssize_t -_glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) { - if (!source_data || source_data->finished) return -EINVAL; - if (source_data->write_fd < 0) return -EIO; - - // End of data: NULL data pointer and size zero - if (!data && size == 0) { - 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; - } - - // Error from application: NULL data pointer and size is error code - if (!data && size > 0) { - source_data->finished = true; - 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; - } - - // Non-blocking write - retry on EINTR, return 0 on would-block - ssize_t written; - do { - written = write(source_data->write_fd, data, size); - } while (written < 0 && errno == EINTR); - - if (written < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // Would block, return 0 bytes written - return 0; - } - // Actual error - source_data->finished = true; - 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; - } - - return written; -} - +// }}} diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index bd5210059..70a8541f3 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -403,15 +403,9 @@ typedef struct _GLFWlibraryX11 // Drag source state struct { Window source_window; - char** mimes; // Array of MIME type strings - int mime_count; // Number of MIME types Atom* type_atoms; // Atoms for each MIME type + size_t type_count; Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink - bool active; - _GLFWwindow* window; // Window that initiated the drag - 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 24e98fe85..c9741945e 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -88,10 +88,6 @@ 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 @@ -1100,8 +1096,8 @@ static void handleSelectionClear(XEvent* event) } } -static void handleSelectionRequest(XEvent* event) -{ +static void +handleSelectionRequest(XEvent* event) { const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent reply = { SelectionNotify }; @@ -1110,19 +1106,18 @@ static void handleSelectionRequest(XEvent* event) reply.xselection.selection = request->selection; reply.xselection.target = request->target; reply.xselection.time = request->time; + reply.xselection.property = None; // Handle XdndSelection (drag and drop) specially - if (request->selection == _glfw.x11.XdndSelection && _glfw.x11.drag.active && _glfw.x11.drag.window) { + if (request->selection == _glfw.x11.XdndSelection && _glfw.drag.window_id) { // 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)); + Atom *targets = calloc(_glfw.x11.drag.type_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]; - } + for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) targets[i + 2] = _glfw.x11.drag.type_atoms[i]; XChangeProperty(_glfw.x11.display, request->requestor, request->property, @@ -1130,72 +1125,27 @@ static void handleSelectionRequest(XEvent* event) 32, PropModeReplace, (unsigned char*)targets, - _glfw.x11.drag.mime_count + 2); + _glfw.x11.drag.type_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++) { + for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) { if (_glfw.x11.drag.type_atoms[i] == request->target) { - mime_type = _glfw.x11.drag.mimes[i]; + mime_type = _glfw.drag.items[i].mime_type; 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; + // TODO: Create a pending request and send GLFW_DRAG_DATA_REQUEST to application } } } else { // Handle regular clipboard/primary selection reply.xselection.property = writeTargetToProperty(request); } - XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } @@ -3863,262 +3813,12 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc); } -// Helper function to clean up drag source data -static void cleanup_x11_drag_source_data(GLFWDragSourceData* data) { - if (!data) return; - if (data->write_fd >= 0) { - 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 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++) { - free(_glfw.x11.drag.mimes[i]); - } - free(_glfw.x11.drag.mimes); - free(_glfw.x11.drag.type_atoms); - _glfw.x11.drag.mimes = NULL; - _glfw.x11.drag.type_atoms = NULL; - _glfw.x11.drag.mime_count = 0; - _glfw.x11.drag.source_window = None; - _glfw.x11.drag.active = false; - _glfw.x11.drag.window = NULL; -} - -void _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) { - cleanupDragSource(); -} - -int _glfwPlatformStartDrag(_GLFWwindow* window, - const char* const* mime_types, - int mime_count, - const GLFWimage* thumbnail UNUSED, - int operations) { - // Clean up any existing drag operation - cleanupDragSource(); - - // Set the drag action based on operation type (bitfield) - // Default to copy, prefer move if specified - if (operations & GLFW_DRAG_OPERATION_MOVE) { - _glfw.x11.drag.action_atom = _glfw.x11.XdndActionMove; - } else if (operations & GLFW_DRAG_OPERATION_COPY) { - _glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy; - } else { - _glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy; - } - - // Allocate storage for MIME types - _glfw.x11.drag.mimes = calloc(mime_count, sizeof(char*)); - _glfw.x11.drag.type_atoms = calloc(mime_count, sizeof(Atom)); - _glfw.x11.drag.mime_count = mime_count; - _glfw.x11.drag.source_window = window->x11.handle; - _glfw.x11.drag.window = window; - - if (!_glfw.x11.drag.mimes || !_glfw.x11.drag.type_atoms) { - cleanupDragSource(); - _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag data"); - return ENOMEM; - } - - // Copy MIME types and create atoms - for (int i = 0; i < mime_count; i++) { - _glfw.x11.drag.mimes[i] = _glfw_strdup(mime_types[i]); - if (!_glfw.x11.drag.mimes[i]) { - cleanupDragSource(); - _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag MIME type"); - return ENOMEM; - } - _glfw.x11.drag.type_atoms[i] = XInternAtom(_glfw.x11.display, mime_types[i], False); - } - - // Set up XdndTypeList property if we have more than 3 types - if (mime_count > 3) { - XChangeProperty(_glfw.x11.display, window->x11.handle, - _glfw.x11.XdndTypeList, XA_ATOM, 32, PropModeReplace, - (unsigned char*)_glfw.x11.drag.type_atoms, mime_count); - } - - // Take ownership of XdndSelection - XSetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection, - window->x11.handle, CurrentTime); - - if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection) != window->x11.handle) { - cleanupDragSource(); - _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to acquire XdndSelection ownership"); - return EIO; - } - - _glfw.x11.drag.active = true; - - // Note: The actual drag operation in X11 requires grabbing the pointer and tracking - // mouse movement to send XdndEnter/Position/Leave/Drop messages to target windows. - // This is a complex state machine that requires: - // 1. Grabbing the pointer with XGrabPointer - // 2. Tracking mouse movement - // 3. Finding window under cursor with XTranslateCoordinates - // 4. Sending XdndEnter when entering a new window - // 5. Sending XdndPosition as the cursor moves - // 6. Sending XdndLeave when leaving a window - // 7. Sending XdndDrop on button release - // 8. Responding to SelectionRequest events with the drag data - // - // For a complete implementation, this would need to be integrated with the - // event loop. For now, we set up the data source so the application can - // handle its own drag tracking if needed. - - return 0; -} - -ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) { - if (!source_data || source_data->finished) return -EINVAL; - - // For X11, we set properties via XChangeProperty in response to SelectionRequest - - // End of data: NULL data pointer and size zero - if (!data && size == 0) { - source_data->finished = true; - if (source_data->write_fd >= 0) { - close(source_data->write_fd); - source_data->write_fd = -1; - } - // Clean up this and any other finished requests - cleanup_x11_finished_requests(); - return 0; - } - - // Error from application: NULL data pointer and size is error code - if (!data && size > 0) { - source_data->finished = true; - source_data->error_code = (int)size; - if (source_data->write_fd >= 0) { - 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, 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 { - written = write(source_data->write_fd, data, size); - } while (written < 0 && errno == EINTR); - - if (written < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // Would block, return 0 bytes written - return 0; - } - source_data->finished = true; - 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 valid mechanism to send data - return -EINVAL; +// Drag source {{{ +int +_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) { + (void)window; (void)thumbnail; (void)operations; + return ENOTSUP; } +void _glfwPlatformFreeDragSourceData(void) {} +int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; } +// }}} diff --git a/kitty/data-types.h b/kitty/data-types.h index 8c49ac585..467cdbb9b 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -333,6 +333,7 @@ void focus_in_event(void); void scroll_event(const GLFWScrollEvent *ev); void on_key_input(const GLFWkeyevent *ev); void request_window_attention(id_type, bool); +void free_drag_source(void); locale_t get_c_locale(void); #ifndef __APPLE__ void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name); diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 79a05696b..588a1745f 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -371,9 +371,6 @@ load_glfw(const char* path) { *(void **) (&glfwStartDrag_impl) = dlsym(handle, "glfwStartDrag"); if (glfwStartDrag_impl == NULL) fail("Failed to load glfw function glfwStartDrag with error: %s", dlerror()); - *(void **) (&glfwSendDragData_impl) = dlsym(handle, "glfwSendDragData"); - if (glfwSendDragData_impl == NULL) fail("Failed to load glfw function glfwSendDragData with error: %s", dlerror()); - *(void **) (&glfwJoystickPresent_impl) = dlsym(handle, "glfwJoystickPresent"); if (glfwJoystickPresent_impl == NULL) fail("Failed to load glfw function glfwJoystickPresent with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index bdbc37be1..d8d1cf90c 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1131,7 +1131,9 @@ typedef enum { typedef struct GLFWDropEvent { GLFWDropEventType type; const char **mimes; size_t num_mimes; - double xpos, ypos; // Only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE + // Positions are only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE. + // They are in window co-ordinates same as for mouse events + double xpos, ypos; bool from_self; // Only valid upto GLFW_DROP_DROP ssize_t (*read_data)(GLFWwindow *w, struct GLFWDropEvent* ev, char *buffer, size_t sz); // Only valid for GLFW_DROP_DATA_AVAILABLE void (*finish_drop)(GLFWwindow *w, GLFWDragOperationType op); // Only valid for GLFW_DROP_DROP and GLFW_DROP_DATA_AVAILABLE @@ -1537,57 +1539,30 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,const GLFWScrollEvent*); */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief Drag event types. - * - * These constants are used to identify the type of drag event. - * - * @ingroup input - */ typedef enum { - /*! The drag operation entered the window. */ - GLFW_DRAG_ENTER = 1, - /*! The drag operation moved within the window. */ - GLFW_DRAG_MOVE = 2, - /*! The drag operation left the window. */ - GLFW_DRAG_LEAVE = 3, - /*! Async status update request (xpos/ypos are invalid). */ - GLFW_DRAG_STATUS_UPDATE = 4 + GLFW_DRAG_DATA_REQUEST, + GLFW_DRAG_CANCELLED, + GLFW_DRAG_FINSHED, + GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified + GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType + GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet } GLFWDragEventType; -/*! @brief Opaque drag source data handle. - * - * This is an opaque handle to a heap-allocated object that represents - * data being requested from a drag source. The lifetime is managed by - * the GLFW backend - it is freed on end of data, error, drag source - * cancellation, or at exit. - * - * @since Added in version 4.0. - * - * @ingroup input - */ -typedef struct GLFWDragSourceData GLFWDragSourceData; +typedef struct GLFWDragSourceItem { + const char *mime_type; + // Can be on null to provide data when the drag is started should be used only when the data is relatively small + const char *optional_data; + size_t data_size; +} GLFWDragSourceItem; -/*! @brief The function pointer type for drag source data request callbacks. - * - * This is the function pointer type for callbacks invoked when the OS - * requests data for a specific MIME type from the active drag source. - * The callback is called on the GUI thread. - * - * @param[in] window The window that initiated the drag. - * @param[in] mime_type The MIME type being requested, or NULL if the OS - * has closed the drag source. - * @param[in] source_data Opaque pointer to a heap-allocated object. Use this - * pointer when calling @ref glfwSendDragData to send data chunks. - * - * @sa @ref glfwStartDrag - * @sa @ref glfwSendDragData - * @sa @ref glfwSetDragSourceCallback - * - * @since Added in version 4.0. - * - * @ingroup input - */ -typedef void (* GLFWdragsourcefun)(GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data); +typedef struct GLFWDragEvent { + GLFWDragEventType type; + const char *mime_type; + const char *data; size_t data_sz; int err_num; + GLFWDragOperationType action; // can be 0 indicating no action +} GLFWDragEvent; + +typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev); /*! @brief The function pointer type for drag event callbacks. * @@ -2320,14 +2295,10 @@ typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdra GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl; #define glfwSetDragSourceCallback glfwSetDragSourceCallback_impl -typedef int (*glfwStartDrag_func)(GLFWwindow*, const char* const*, int, const GLFWimage*, int); +typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWDragSourceItem*, size_t, const GLFWimage*, int); GFW_EXTERN glfwStartDrag_func glfwStartDrag_impl; #define glfwStartDrag glfwStartDrag_impl -typedef ssize_t (*glfwSendDragData_func)(GLFWDragSourceData*, const void*, size_t); -GFW_EXTERN glfwSendDragData_func glfwSendDragData_impl; -#define glfwSendDragData glfwSendDragData_impl - typedef int (*glfwJoystickPresent_func)(int); GFW_EXTERN glfwJoystickPresent_func glfwJoystickPresent_impl; #define glfwJoystickPresent glfwJoystickPresent_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 846e51900..581069c82 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -772,48 +772,38 @@ application_close_requested_callback(int flags) { } #define ds (global_state.drag_source) -static void -try_sending_drag_source_data(id_type timer_id UNUSED, void *callback_data UNUSED) { - bool incomplete = false; - for (size_t i = 0; i < ds.num_ongoing_transfers; i++) { -#define t ds.ongoing_transfers[i] - size_t sz = PyBytes_GET_SIZE(t.weakref_to_data_object); - ssize_t ret; - if (sz > t.offset) { - const char *data = PyBytes_AS_STRING(t.weakref_to_data_object); - ret = glfwSendDragData(t.platform_data, data + t.offset, sz - t.offset); - } else ret = glfwSendDragData(t.platform_data, NULL, 0); - if (ret >= 0) { - t.offset += ret; - if (t.offset < sz) incomplete = true; - else glfwSendDragData(t.platform_data, NULL, 0); // tell glfw transfer is complete - } else { - log_error("Failed to send data from drag source with error: %s", strerror(-ret)); - t.offset = sz; - } -#undef t - } - if (incomplete) add_main_loop_timer(ms_double_to_monotonic_t(2), false, try_sending_drag_source_data, NULL, NULL); + +void +free_drag_source(void) { + if (ds.accepted_mime_type) free(ds.accepted_mime_type); + Py_CLEAR(ds.drag_data); + zero_at_ptr(&ds); } static void -drag_source_callback(GLFWwindow *window UNUSED, const char* mime_type, GLFWDragSourceData* source_data) { - PyObject *data = NULL; - if (mime_type == NULL) { - ds.is_active = false; - Py_CLEAR(ds.drag_data); - return; +drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) { + switch (ev->type) { + case GLFW_DRAG_DATA_REQUEST: // we currently pre-provide all data so this should never happen + if (ev->data_sz) { + // previously returned data is consumed, free it + } else { + ev->err_num = ENOENT; + } + break; + case GLFW_DRAG_ACCEPTED: + free(ds.accepted_mime_type); + ds.accepted_mime_type = ev->mime_type ? strdup(ev->mime_type) : NULL; + break; + case GLFW_DRAG_ACTION_CHANGED: ds.action = ev->action; break; + case GLFW_DRAG_DROPPED: ds.was_dropped = true; break; + case GLFW_DRAG_CANCELLED: + ds.was_canceled = true; + /* fallthrough */ + case GLFW_DRAG_FINSHED: + ds.is_active = false; + free_drag_source(); + break; } - if (!ds.is_active || !ds.drag_data || !(data = PyDict_GetItemString(ds.drag_data, mime_type))) { - glfwSendDragData(source_data, NULL, EINVAL); - return; - } - ensure_space_for(&ds, ongoing_transfers, ds.ongoing_transfers[0], ds.num_ongoing_transfers + 1, ongoing_transfers_capacity, 8, true); - ds.ongoing_transfers[ds.num_ongoing_transfers].platform_data = source_data; - ds.ongoing_transfers[ds.num_ongoing_transfers].weakref_to_data_object = data; - ds.ongoing_transfers[ds.num_ongoing_transfers].offset = 0; - ds.num_ongoing_transfers++; - try_sending_drag_source_data(0, NULL); } #undef ds @@ -2761,19 +2751,25 @@ start_drag_with_data(PyObject *self UNUSED, PyObject *args, PyObject *kw) { &os_window_id, &PyDict_Type, &data_map, &thumbnail_data, &thumbnail_sz, &width, &height, &operations)) return NULL; OSWindow *w = os_window_for_id(os_window_id); if (!w || !w->handle) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist"); return NULL; } - RAII_ALLOC(const char*, mime_types, calloc(PyDict_Size(data_map), sizeof(const char*))); - if (!mime_types) { PyErr_NoMemory(); return NULL; } - PyObject *key, *value; Py_ssize_t pos = 0; int num = 0; + RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(const char*))); + if (!items) { PyErr_NoMemory(); return NULL; } + PyObject *key, *value; Py_ssize_t pos = 0; size_t num = 0; while (PyDict_Next(data_map, &pos, &key, &value)) { if (!PyUnicode_Check(key)) { PyErr_SetString(PyExc_TypeError, "data_map must have string keys"); return NULL; } if (!PyBytes_Check(value)) { PyErr_SetString(PyExc_TypeError, "data_map must have bytes values"); return NULL; } - mime_types[num++] = PyUnicode_AsUTF8(key); + items[num].mime_type = PyUnicode_AsUTF8(key); + items[num].optional_data = PyBytes_AS_STRING(value); items[num].data_size = PyBytes_GET_SIZE(value); + num++; } GLFWimage thumbnail = {.pixels=thumbnail_data, .width=width, .height=height}; + free_drag_source(); global_state.drag_source.is_active = true; - Py_CLEAR(global_state.drag_source.drag_data); global_state.drag_source.drag_data = Py_NewRef(data_map); - global_state.drag_source.num_ongoing_transfers = 0; - glfwStartDrag(w->handle, mime_types, num, thumbnail_data ? &thumbnail : NULL, operations); + global_state.drag_source.drag_data = Py_NewRef(data_map); + errno = glfwStartDrag(w->handle, items, num, thumbnail_data ? &thumbnail : NULL, operations); + if (errno != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } Py_RETURN_NONE; } diff --git a/kitty/state.c b/kitty/state.c index 6f502fe6f..81276f5b7 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1581,9 +1581,7 @@ finalize(void) { free_bgimage(&global_state.bgimage, false); free_window_logo_table(&global_state.all_window_logos); global_state.bgimage = NULL; - free(global_state.drag_source.ongoing_transfers); - Py_CLEAR(global_state.drag_source.drag_data); - zero_at_ptr(&global_state.drag_source); + free_drag_source(); Py_CLEAR(global_state.drop_dest.data); zero_at_ptr(&global_state.drop_dest); diff --git a/kitty/state.h b/kitty/state.h index b603cf30e..122106a5d 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -383,14 +383,10 @@ typedef struct GlobalState { } drop_dest; struct { - bool is_active; + bool is_active, was_dropped, was_canceled; + char *accepted_mime_type; + int action; PyObject *drag_data; - struct { - void *platform_data; - size_t offset; - PyObject *weakref_to_data_object; - } *ongoing_transfers; - size_t num_ongoing_transfers, ongoing_transfers_capacity; } drag_source; } GlobalState;