diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index d149b2bd9..da19415ff 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -170,6 +170,11 @@ typedef struct _GLFWwindowNS // Current drag operation type for NSDraggingSource GLFWDragOperationType dragOperationType; + + // Cached MIME types from drag enter (for move events) + const char** dragMimes; + int dragMimeCount; // Current count of MIME types (may be reduced by callback) + int dragMimeArraySize; // Original array size for proper cleanup } _GLFWwindowNS; // Cocoa-specific global data diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index b59c89e48..57261fb21 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1350,6 +1350,33 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m return YES; } +// Helper function to free dragged MIME types array +static void freeDragMimes(_GLFWwindow* window) { + if (window->ns.dragMimes) { + // Free based on array size, not count (callback may have reduced count) + for (int i = 0; i < window->ns.dragMimeArraySize; i++) { + if (window->ns.dragMimes[i]) + free((char*)window->ns.dragMimes[i]); + } + free(window->ns.dragMimes); + window->ns.dragMimes = NULL; + window->ns.dragMimeCount = 0; + window->ns.dragMimeArraySize = 0; + } +} + +// Helper to free entries that were filtered out by callback +static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_count) { + if (window->ns.dragMimes && new_count < old_count) { + for (int i = new_count; i < old_count; i++) { + if (window->ns.dragMimes[i]) { + free((char*)window->ns.dragMimes[i]); + window->ns.dragMimes[i] = NULL; + } + } + } +} + - (NSDragOperation)draggingEntered:(id )sender { const NSRect contentRect = [window->ns.view frame]; @@ -1366,22 +1393,25 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m max_types += [item.types count]; } + // Free any previously cached MIME types + freeDragMimes(window); + // Pre-allocate C array for MIME types const char** mime_array = (const char**)calloc(max_types, sizeof(const char*)); if (!mime_array) { - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, NULL, 0); + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, NULL, NULL); return accepted ? NSDragOperationGeneric : NSDragOperationNone; } int mime_count = 0; - // Check for common types first + // Check for common types first (use _glfw_strdup since we need to own the strings) NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; if ([pasteboard canReadObjectForClasses:@[[NSURL class]] options:options]) { - mime_array[mime_count++] = "text/uri-list"; + mime_array[mime_count++] = _glfw_strdup("text/uri-list"); } if ([pasteboard canReadObjectForClasses:@[[NSString class]] options:nil]) { - mime_array[mime_count++] = "text/plain"; + mime_array[mime_count++] = _glfw_strdup("text/plain"); } // Get additional types from pasteboard items @@ -1398,16 +1428,27 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m } } if (!duplicate) { - mime_array[mime_count++] = mime; + // Use _glfw_strdup since uti_to_mime returns strings from autoreleased objects + mime_array[mime_count++] = _glfw_strdup(mime); } } } } - // Call drag enter callback with MIME types - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, mime_array, mime_count); + // Store MIME types for later use in move events + window->ns.dragMimes = mime_array; + window->ns.dragMimeCount = mime_count; + window->ns.dragMimeArraySize = mime_count; - free(mime_array); + // Call drag enter callback with writable MIME types array + int old_count = mime_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, mime_array, &mime_count); + + // Free any entries that were filtered out by the callback + freeFilteredDragMimes(window, old_count, mime_count); + + // Update cached mime count with callback result + window->ns.dragMimeCount = mime_count; if (accepted) return NSDragOperationGeneric; @@ -1421,8 +1462,16 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m double xpos = pos.x; double ypos = contentRect.size.height - pos.y; - // Call drag move callback and return acceptance status - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + // Call drag move callback with cached MIME types + int old_count = window->ns.dragMimeCount; + int mime_count = old_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, window->ns.dragMimes, &mime_count); + + // Free any entries that were filtered out by the callback + freeFilteredDragMimes(window, old_count, mime_count); + + // Update cached mime count with callback result + window->ns.dragMimeCount = mime_count; if (accepted) return NSDragOperationGeneric; @@ -1433,7 +1482,10 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m { (void)sender; // Call drag leave callback - _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0); + _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, NULL); + + // Free cached MIME types + freeDragMimes(window); } - (BOOL)performDragOperation:(id )sender @@ -3807,9 +3859,16 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, } } -void _glfwPlatformSetDragAcceptance(_GLFWwindow* window UNUSED, bool accepted UNUSED) { - // No-op on macOS: The system uses periodic dragging updates via - // wantsPeriodicDraggingUpdates returning YES. The application should - // return the updated acceptance status from the drag callback instead. +void _glfwPlatformUpdateDragState(_GLFWwindow* window) { + // Call the drag callback with STATUS_UPDATE to get updated acceptance and MIME list + // Position values are not valid for this event type + if (window->ns.dragMimes) { + int old_count = window->ns.dragMimeCount; + int mime_count = old_count; + _glfwInputDragEvent(window, GLFW_DRAG_STATUS_UPDATE, 0, 0, window->ns.dragMimes, &mime_count); + // Free any entries that were filtered out by the callback + freeFilteredDragMimes(window, old_count, mime_count); + window->ns.dragMimeCount = mime_count; + } } diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 4cb368272..8ca6c2a4f 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1761,17 +1761,15 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code - * int function_name(GLFWwindow* window, const char* mime, const char* text) + * void function_name(GLFWwindow* window, const char* mime, const char* data, size_t sz) * @endcode * * @param[in] window The window that received the event. * @param[in] mime The UTF-8 encoded drop mime-type - * @param[in] data The dropped data or NULL for drag enter events + * @param[in] data The dropped data. * @param[in] sz The size of the dropped data - * @return For drag events should return the priority for the specified mime type. A priority of zero - * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * - * @pointer_lifetime The text is valid until the + * @pointer_lifetime The data is valid until the * callback function returns. * * @sa @ref path_drop @@ -1781,7 +1779,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * * @ingroup input */ -typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); +typedef void (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); /*! @brief Drag event types. * @@ -1795,7 +1793,9 @@ typedef enum { /*! The drag operation moved within the window. */ GLFW_DRAG_MOVE = 2, /*! The drag operation left the window. */ - GLFW_DRAG_LEAVE = 3 + GLFW_DRAG_LEAVE = 3, + /*! Async status update request (xpos/ypos are invalid). */ + GLFW_DRAG_STATUS_UPDATE = 4 } GLFWDragEventType; /*! @brief Drag operation types. @@ -1838,7 +1838,7 @@ typedef struct GLFWdragitem { * This is the function pointer type for drag event callbacks. A drag event * callback function has the following signature: * @code - * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int mime_count) + * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count) * @endcode * * @param[in] window The window that received the drag event. @@ -1846,12 +1846,16 @@ typedef struct GLFWdragitem { * or @ref GLFW_DRAG_LEAVE. * @param[in] xpos The x-coordinate of the drag position in window coordinates. * @param[in] ypos The y-coordinate of the drag position in window coordinates. - * @param[in] mime_types Array of MIME type strings available from the drag source. - * For @ref GLFW_DRAG_ENTER events this contains all available MIME types. - * For other events this may be `NULL`. The strings are only valid for the - * duration of the callback; if you need to store them, make copies. - * @param[in] mime_count Number of MIME types in the array. Zero if no MIME types - * are available or for non-enter events. + * @param[in,out] mime_types A writable array of MIME type strings available from the drag source. + * For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events this is non-NULL and contains all + * available MIME types. The callback is responsible for sorting this list by priority and + * keeping only the MIME types it wants to accept. The first MIME type in the sorted list + * will be used for the drop operation. The strings are only valid for the duration of the + * callback; if you need to store them, make copies. For @ref GLFW_DRAG_LEAVE events this + * is `NULL`. + * @param[in,out] mime_count Pointer to the number of MIME types in the array. The callback + * should update this to reflect the new count after sorting and filtering. For + * @ref GLFW_DRAG_LEAVE events this is `NULL`. * @return For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events, return non-zero * to accept the drag or zero to reject it. This allows the application to * dynamically accept or reject the drag based on the current position. @@ -1859,13 +1863,13 @@ typedef struct GLFWdragitem { * * @sa @ref drag_events * @sa @ref glfwSetDragCallback - * @sa @ref glfwSetDragAcceptance + * @sa @ref glfwUpdateDragState * * @since Added in version 4.0. * * @ingroup input */ -typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int mime_count); +typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int* mime_count); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); @@ -5070,19 +5074,17 @@ GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* window, GLFWdragfun callback */ GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation); -/*! @brief Sets the acceptance status of the current drag operation. +/*! @brief Schedules a call to the drag callback to update drag state. * - * This function allows the application to asynchronously change whether - * the current drag operation is accepted or rejected. This is useful when - * the acceptance decision cannot be made synchronously in the drag callback, - * for example when waiting for user input or network responses. + * This function schedules a call to the drag callback to get updated + * acceptance status and MIME type list. Use this when the application + * needs to update its drag state asynchronously. * - * The acceptance status affects the visual feedback shown to the user - * (e.g., cursor changes) and whether a drop will be accepted if the user - * releases the mouse button. + * On Wayland and X11, this will immediately call the drag callback with + * the current drag state. On macOS this is a no-op since the drag callback + * is called periodically anyway. * * @param[in] window The window receiving the drag operation. - * @param[in] accepted `true` to accept the drag, `false` to reject it. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * @@ -5090,8 +5092,7 @@ GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWdragitem* items, int ite * over the specified window. * * @remark On macOS, this function is a no-op as the system uses periodic - * dragging updates. The application should return the updated acceptance - * status from the drag callback instead. + * dragging updates via the drag callback. * * @thread_safety This function must only be called from the main thread. * @@ -5102,7 +5103,7 @@ GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWdragitem* items, int ite * * @ingroup input */ -GLFWAPI void glfwSetDragAcceptance(GLFWwindow* window, bool accepted); +GLFWAPI void glfwUpdateDragState(GLFWwindow* window); /*! @brief Returns whether the specified joystick is present. * diff --git a/glfw/input.c b/glfw/input.c index 21c5717fe..e82a20a41 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -403,16 +403,15 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered) // Notifies shared code of files or directories dropped on a window // -int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz) +void _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz) { if (window->callbacks.drop) - return window->callbacks.drop((GLFWwindow*) window, mime, text, sz); - return 0; + window->callbacks.drop((GLFWwindow*) window, mime, text, sz); } // Notifies shared code of a drag event // -int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int mime_count) +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); @@ -1141,13 +1140,13 @@ GLFWAPI int glfwStartDrag(GLFWwindow* handle, const GLFWdragitem* items, int ite return _glfwPlatformStartDrag(window, items, item_count, thumbnail, operation); } -GLFWAPI void glfwSetDragAcceptance(GLFWwindow* handle, bool accepted) +GLFWAPI void glfwUpdateDragState(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); - _glfwPlatformSetDragAcceptance(window, accepted); + _glfwPlatformUpdateDragState(window); } GLFWAPI int glfwJoystickPresent(int jid) diff --git a/glfw/internal.h b/glfw/internal.h index d7789f070..eb5761a15 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -768,7 +768,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev); void _glfwPlatformChangeCursorTheme(void); int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation); -void _glfwPlatformSetDragAcceptance(_GLFWwindow* window, bool accepted); +void _glfwPlatformUpdateDragState(_GLFWwindow* window); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); @@ -822,8 +822,8 @@ 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 _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz); -int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int mime_count); +void _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz); +int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count); void _glfwInputColorScheme(GLFWColorScheme, bool); void _glfwPlatformInputColorScheme(GLFWColorScheme); void _glfwInputJoystick(_GLFWjoystick* js, int event); diff --git a/glfw/null_window.c b/glfw/null_window.c index 0dd4ce258..c4d04d477 100644 --- a/glfw/null_window.c +++ b/glfw/null_window.c @@ -539,7 +539,7 @@ int _glfwPlatformStartDrag(_GLFWwindow* window UNUSED, return false; } -void _glfwPlatformSetDragAcceptance(_GLFWwindow* window UNUSED, bool accepted UNUSED) +void _glfwPlatformUpdateDragState(_GLFWwindow* window UNUSED) { // No-op for null platform } diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 9ffcfde88..c4b1344c1 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2486,6 +2486,29 @@ static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL); } +// Helper function to update drag state from callback results +static void update_drag_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, bool accepted, int mime_count) { + bool acceptance_changed = (accepted != d->drag_accepted); + // The first MIME in the sorted list is the preferred one for drop + const char* new_preferred_mime = (accepted && mime_count > 0) ? d->mimes[0] : NULL; + bool mime_changed = false; + + // Check if the preferred MIME changed + if (d->mime_for_drop == NULL && new_preferred_mime != NULL) { + mime_changed = true; + } else if (d->mime_for_drop != NULL && new_preferred_mime == NULL) { + mime_changed = true; + } else if (d->mime_for_drop != NULL && new_preferred_mime != NULL) { + mime_changed = (strcmp(d->mime_for_drop, new_preferred_mime) != 0); + } + + if (acceptance_changed || mime_changed) { + d->drag_accepted = accepted; + d->mime_for_drop = new_preferred_mime; + wl_data_offer_accept(d->id, d->serial, d->mime_for_drop); + } +} + static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; @@ -2494,32 +2517,23 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device d->surface = surface; d->serial = serial; d->drag_accepted = false; + d->mime_for_drop = NULL; _GLFWwindow* window = _glfw.windowListHead; - int format_priority = 0; while (window) { if (window->wl.surface == surface) { - // Call drag enter callback with MIME types + // Call drag enter callback with writable MIME types array double xpos = wl_fixed_to_double(x); double ypos = wl_fixed_to_double(y); - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, d->mimes, (int)d->mimes_count); + int mime_count = (int)d->mimes_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, d->mimes, &mime_count); - // If accepted, check MIME type priorities - if (accepted) { - d->drag_accepted = true; - for (size_t j = 0; j < d->mimes_count; j++) { - int prio = _glfwInputDrop(window, d->mimes[j], NULL, 0); - if (prio > format_priority) { - format_priority = prio; - d->mime_for_drop = d->mimes[j]; - } - } - } + // Update drag state based on callback results + update_drag_state(d, window, accepted, mime_count); break; } window = window->next; } - wl_data_offer_accept(id, serial, d->mime_for_drop); } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer } @@ -2534,7 +2548,7 @@ static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { - _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0); + _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, NULL); break; } window = window->next; @@ -2572,29 +2586,6 @@ static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED } } -// Helper function to update drag acceptance status and notify compositor -static void update_drag_acceptance(_GLFWWaylandDataOffer *d, _GLFWwindow* window, bool accepted) { - if (accepted != d->drag_accepted) { - d->drag_accepted = accepted; - // If acceptance changed, update MIME selection and notify compositor - if (accepted) { - // Re-select best MIME type if now accepting - int format_priority = 0; - d->mime_for_drop = NULL; - for (size_t j = 0; j < d->mimes_count; j++) { - int prio = _glfwInputDrop(window, d->mimes[j], NULL, 0); - if (prio > format_priority) { - format_priority = prio; - d->mime_for_drop = d->mimes[j]; - } - } - } else { - d->mime_for_drop = NULL; - } - wl_data_offer_accept(d->id, d->serial, d->mime_for_drop); - } -} - static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t time UNUSED, wl_fixed_t x, wl_fixed_t y) { // Find the current drag offer and send motion events for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { @@ -2605,10 +2596,12 @@ static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUS if (window->wl.surface == d->surface) { double xpos = wl_fixed_to_double(x); double ypos = wl_fixed_to_double(y); - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + // Pass the MIME types array for move events + int mime_count = (int)d->mimes_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, d->mimes, &mime_count); - // Update acceptance status based on callback return value - update_drag_acceptance(d, window, accepted); + // Update drag state based on callback results + update_drag_state(d, window, accepted, mime_count); break; } window = window->next; @@ -3188,12 +3181,18 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_ } void -_glfwPlatformSetDragAcceptance(_GLFWwindow* window, bool accepted) { - // Find the active drag offer for this window and update its acceptance status +_glfwPlatformUpdateDragState(_GLFWwindow* window) { + // Find the active drag offer for this window and call the drag callback immediately for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { _GLFWWaylandDataOffer *d = &_glfw.wl.dataOffers[i]; if (d->offer_type == DRAG_AND_DROP && window->wl.surface == d->surface) { - update_drag_acceptance(d, window, accepted); + // Call the drag callback with STATUS_UPDATE event to get updated state + // Position values are not valid for this event type + int mime_count = (int)d->mimes_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_STATUS_UPDATE, 0, 0, d->mimes, &mime_count); + + // Update drag state based on callback results + update_drag_state(d, window, accepted, mime_count); return; } } diff --git a/glfw/x11_init.c b/glfw/x11_init.c index 3a5db8c6e..7ea23ff0b 100644 --- a/glfw/x11_init.c +++ b/glfw/x11_init.c @@ -819,6 +819,17 @@ void _glfwPlatformTerminate(void) if (_glfw.x11.clipboard_atoms.array) { free(_glfw.x11.clipboard_atoms.array); } if (_glfw.x11.primary_atoms.array) { free(_glfw.x11.primary_atoms.array); } + // Free cached drag MIME types (use array size, not count) + if (_glfw.x11.xdnd.mimes) { + for (int i = 0; i < _glfw.x11.xdnd.mimes_array_size; i++) { + if (_glfw.x11.xdnd.mimes[i]) XFree(_glfw.x11.xdnd.mimes[i]); + } + free(_glfw.x11.xdnd.mimes); + _glfw.x11.xdnd.mimes = NULL; + _glfw.x11.xdnd.mimes_count = 0; + _glfw.x11.xdnd.mimes_array_size = 0; + } + if (_glfw.x11.display) { XCloseDisplay(_glfw.x11.display); diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index c74fc7d36..6971939a6 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -386,6 +386,9 @@ typedef struct _GLFWlibraryX11 int format_priority; Window target_window; // For drag events: the window being dragged over bool drag_accepted; // Whether the current drag is accepted + char** mimes; // Cached MIME types from drag enter + int mimes_count; // Current count of MIME types (may be reduced by callback) + int mimes_array_size; // Original array size for proper cleanup } xdnd; // Drag source state diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 45f8f5d04..fa136aaac 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -1399,6 +1399,18 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) { } } +// Helper to free entries that were filtered out by drag callback +static void freeFilteredXdndMimes(int old_count, int new_count) { + if (_glfw.x11.xdnd.mimes && new_count < old_count) { + for (int i = new_count; i < old_count; i++) { + if (_glfw.x11.xdnd.mimes[i]) { + XFree(_glfw.x11.xdnd.mimes[i]); + _glfw.x11.xdnd.mimes[i] = NULL; + } + } + } +} + // Process the specified X event // @@ -1831,6 +1843,17 @@ static void processEvent(XEvent *event) Atom* formats = NULL; const bool list = event->xclient.data.l[1] & 1; + // Free any previously cached MIME types + if (_glfw.x11.xdnd.mimes) { + for (int j = 0; j < _glfw.x11.xdnd.mimes_array_size; j++) { + if (_glfw.x11.xdnd.mimes[j]) XFree(_glfw.x11.xdnd.mimes[j]); + } + free(_glfw.x11.xdnd.mimes); + _glfw.x11.xdnd.mimes = NULL; + _glfw.x11.xdnd.mimes_count = 0; + _glfw.x11.xdnd.mimes_array_size = 0; + } + _glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; _glfw.x11.xdnd.target_window = window->x11.handle; @@ -1855,38 +1878,43 @@ static void processEvent(XEvent *event) formats = (Atom*) event->xclient.data.l + 2; } + // Get atom names and store them in the xdnd structure char **atom_names = calloc(count, sizeof(char*)); int valid_mime_count = 0; if (atom_names) { get_atom_names(formats, count, atom_names); - // Count valid MIME types + // Compact the array to only valid MIME types for (i = 0; i < count; i++) { - if (atom_names[i]) valid_mime_count++; - } - } - - // Call the drag enter callback with the MIME types - // Position is not known yet at enter time, will be updated with XdndPosition - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, 0, 0, (const char**)atom_names, valid_mime_count); - - if (atom_names && accepted) { - _glfw.x11.xdnd.drag_accepted = true; - for (i = 0; i < count; i++) - { if (atom_names[i]) { - int prio = _glfwInputDrop(window, atom_names[i], NULL, 0); - if (prio > _glfw.x11.xdnd.format_priority) { - _glfw.x11.xdnd.format_priority = prio; - strncpy(_glfw.x11.xdnd.format, atom_names[i], arraysz(_glfw.x11.xdnd.format) - 1); + if (valid_mime_count != (int)i) { + atom_names[valid_mime_count] = atom_names[i]; + atom_names[i] = NULL; } - XFree(atom_names[i]); + valid_mime_count++; } } - free(atom_names); - } else if (atom_names) { - for (i = 0; i < count; i++) - if (atom_names[i]) XFree(atom_names[i]); - free(atom_names); + // Store the MIME types for later use + _glfw.x11.xdnd.mimes = atom_names; + _glfw.x11.xdnd.mimes_count = valid_mime_count; + _glfw.x11.xdnd.mimes_array_size = valid_mime_count; + } + + // Call the drag enter callback with writable MIME types array + // Position is not known yet at enter time, will be updated with XdndPosition + int mime_count = valid_mime_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, 0, 0, (const char**)_glfw.x11.xdnd.mimes, &mime_count); + + // Free any entries that were filtered out by the callback + freeFilteredXdndMimes(valid_mime_count, mime_count); + + // Update state based on callback results + _glfw.x11.xdnd.drag_accepted = accepted; + // Update cached mime count with callback result + _glfw.x11.xdnd.mimes_count = mime_count; + if (accepted && mime_count > 0) { + // The first MIME type in the reordered list is the preferred one + strncpy(_glfw.x11.xdnd.format, _glfw.x11.xdnd.mimes[0], arraysz(_glfw.x11.xdnd.format) - 1); + _glfw.x11.xdnd.format_priority = 1; } if (list && formats) @@ -1931,7 +1959,19 @@ static void processEvent(XEvent *event) else if (event->xclient.message_type == _glfw.x11.XdndLeave) { // The drag operation has left the window - _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0); + _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, NULL); + + // Free cached MIME types (use array size, not count) + if (_glfw.x11.xdnd.mimes) { + for (int j = 0; j < _glfw.x11.xdnd.mimes_array_size; j++) { + if (_glfw.x11.xdnd.mimes[j]) XFree(_glfw.x11.xdnd.mimes[j]); + } + free(_glfw.x11.xdnd.mimes); + _glfw.x11.xdnd.mimes = NULL; + _glfw.x11.xdnd.mimes_count = 0; + _glfw.x11.xdnd.mimes_array_size = 0; + } + _glfw.x11.xdnd.source = None; _glfw.x11.xdnd.target_window = None; } @@ -1959,10 +1999,27 @@ static void processEvent(XEvent *event) _glfwInputCursorPos(window, xpos, ypos); - // Call the drag move callback and update acceptance status - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + // Call the drag move callback with MIME types and update acceptance status + int old_count = _glfw.x11.xdnd.mimes_count; + int mime_count = old_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, (const char**)_glfw.x11.xdnd.mimes, &mime_count); _glfw.x11.xdnd.drag_accepted = accepted; + // Free any entries that were filtered out by the callback + freeFilteredXdndMimes(old_count, mime_count); + + // Update cached mime count with callback result + _glfw.x11.xdnd.mimes_count = mime_count; + + // Update the preferred format based on reordered list + if (accepted && mime_count > 0) { + strncpy(_glfw.x11.xdnd.format, _glfw.x11.xdnd.mimes[0], arraysz(_glfw.x11.xdnd.format) - 1); + _glfw.x11.xdnd.format_priority = 1; + } else { + memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format)); + _glfw.x11.xdnd.format_priority = 0; + } + XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndStatus; @@ -3761,16 +3818,35 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, return true; } -void _glfwPlatformSetDragAcceptance(_GLFWwindow* window, bool accepted) { +void _glfwPlatformUpdateDragState(_GLFWwindow* window) { // Check if there's an active drag over this window if (_glfw.x11.xdnd.source == None || _glfw.x11.xdnd.target_window != window->x11.handle) { return; } - // Update acceptance status + // Call the drag callback with STATUS_UPDATE event to get updated state + // Position values are not valid for this event type + int old_count = _glfw.x11.xdnd.mimes_count; + int mime_count = old_count; + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_STATUS_UPDATE, 0, 0, (const char**)_glfw.x11.xdnd.mimes, &mime_count); _glfw.x11.xdnd.drag_accepted = accepted; + // Free any entries that were filtered out by the callback + freeFilteredXdndMimes(old_count, mime_count); + + // Update cached mime count with callback result + _glfw.x11.xdnd.mimes_count = mime_count; + + // Update the preferred format based on reordered list + if (accepted && mime_count > 0) { + strncpy(_glfw.x11.xdnd.format, _glfw.x11.xdnd.mimes[0], arraysz(_glfw.x11.xdnd.format) - 1); + _glfw.x11.xdnd.format_priority = 1; + } else { + memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format)); + _glfw.x11.xdnd.format_priority = 0; + } + // Send an XdndStatus message to update the drag source XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 3a5d5d064..dc1174a12 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -365,6 +365,9 @@ 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 **) (&glfwUpdateDragState_impl) = dlsym(handle, "glfwUpdateDragState"); + if (glfwUpdateDragState_impl == NULL) fail("Failed to load glfw function glfwUpdateDragState 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 80013c9d3..bb0ca3504 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1499,17 +1499,15 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code - * int function_name(GLFWwindow* window, const char* mime, const char* text) + * void function_name(GLFWwindow* window, const char* mime, const char* data, size_t sz) * @endcode * * @param[in] window The window that received the event. * @param[in] mime The UTF-8 encoded drop mime-type - * @param[in] data The dropped data or NULL for drag enter events + * @param[in] data The dropped data. * @param[in] sz The size of the dropped data - * @return For drag events should return the priority for the specified mime type. A priority of zero - * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * - * @pointer_lifetime The text is valid until the + * @pointer_lifetime The data is valid until the * callback function returns. * * @sa @ref path_drop @@ -1519,7 +1517,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * * @ingroup input */ -typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); +typedef void (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); /*! @brief Drag event types. * @@ -1533,7 +1531,9 @@ typedef enum { /*! The drag operation moved within the window. */ GLFW_DRAG_MOVE = 2, /*! The drag operation left the window. */ - GLFW_DRAG_LEAVE = 3 + GLFW_DRAG_LEAVE = 3, + /*! Async status update request (xpos/ypos are invalid). */ + GLFW_DRAG_STATUS_UPDATE = 4 } GLFWDragEventType; /*! @brief Drag operation types. @@ -1576,7 +1576,7 @@ typedef struct GLFWdragitem { * This is the function pointer type for drag event callbacks. A drag event * callback function has the following signature: * @code - * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int mime_count) + * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count) * @endcode * * @param[in] window The window that received the drag event. @@ -1584,23 +1584,30 @@ typedef struct GLFWdragitem { * or @ref GLFW_DRAG_LEAVE. * @param[in] xpos The x-coordinate of the drag position in window coordinates. * @param[in] ypos The y-coordinate of the drag position in window coordinates. - * @param[in] mime_types Array of MIME type strings available from the drag source. - * For @ref GLFW_DRAG_ENTER events this contains all available MIME types. - * For other events this may be `NULL`. The strings are only valid for the - * duration of the callback; if you need to store them, make copies. - * @param[in] mime_count Number of MIME types in the array. Zero if no MIME types - * are available or for non-enter events. - * @return For @ref GLFW_DRAG_ENTER events, return non-zero to accept the drag - * or zero to reject it. Return value is ignored for other event types. + * @param[in,out] mime_types A writable array of MIME type strings available from the drag source. + * For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events this is non-NULL and contains all + * available MIME types. The callback is responsible for sorting this list by priority and + * keeping only the MIME types it wants to accept. The first MIME type in the sorted list + * will be used for the drop operation. The strings are only valid for the duration of the + * callback; if you need to store them, make copies. For @ref GLFW_DRAG_LEAVE events this + * is `NULL`. + * @param[in,out] mime_count Pointer to the number of MIME types in the array. The callback + * should update this to reflect the new count after sorting and filtering. For + * @ref GLFW_DRAG_LEAVE events this is `NULL`. + * @return For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events, return non-zero + * to accept the drag or zero to reject it. This allows the application to + * dynamically accept or reject the drag based on the current position. + * Return value is ignored for @ref GLFW_DRAG_LEAVE events. * * @sa @ref drag_events * @sa @ref glfwSetDragCallback + * @sa @ref glfwUpdateDragState * * @since Added in version 4.0. * * @ingroup input */ -typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int mime_count); +typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int* mime_count); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); @@ -2291,6 +2298,10 @@ typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWdragitem*, int, const G GFW_EXTERN glfwStartDrag_func glfwStartDrag_impl; #define glfwStartDrag glfwStartDrag_impl +typedef void (*glfwUpdateDragState_func)(GLFWwindow*); +GFW_EXTERN glfwUpdateDragState_func glfwUpdateDragState_impl; +#define glfwUpdateDragState glfwUpdateDragState_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 2a0adfb56..2cb9234bd 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -650,32 +650,76 @@ is_droppable_mime(const char *mime) { } static int -drag_callback(GLFWwindow *w, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int mime_count) { +drag_callback(GLFWwindow *w, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int* mime_count) { (void)xpos; (void)ypos; if (!set_callback_window(w)) return 0; int ret = 0; switch (event) { case GLFW_DRAG_ENTER: - for (int i = 0; i < mime_count; i++) { - if (is_droppable_mime(mime_types[i])) { ret = 1; break; } + case GLFW_DRAG_MOVE: + case GLFW_DRAG_STATUS_UPDATE: + if (mime_types && mime_count && *mime_count > 0) { + // Sort MIME types by priority (descending) and keep only accepted ones + // Use simple bubble sort since lists are typically small + int count = *mime_count; + int new_count = 0; + + // Use stack-allocated array for priorities (count is typically small) + int priorities[64]; + int* prio_arr = (count <= 64) ? priorities : (int*)malloc(count * sizeof(int)); + if (!prio_arr) { + global_state.callback_os_window = NULL; + return 0; + } + + // First pass: filter droppable MIME types and cache priorities + for (int i = 0; i < count; i++) { + int prio = is_droppable_mime(mime_types[i]); + if (prio > 0) { + // Move this mime to the new_count position + if (new_count != i) { + const char* temp = mime_types[new_count]; + mime_types[new_count] = mime_types[i]; + mime_types[i] = temp; + } + prio_arr[new_count] = prio; + new_count++; + } + } + + // Second pass: sort by cached priorities (descending) + for (int i = 0; i < new_count - 1; i++) { + for (int j = i + 1; j < new_count; j++) { + if (prio_arr[j] > prio_arr[i]) { + const char* temp = mime_types[i]; + mime_types[i] = mime_types[j]; + mime_types[j] = temp; + int temp_prio = prio_arr[i]; + prio_arr[i] = prio_arr[j]; + prio_arr[j] = temp_prio; + } + } + } + + if (prio_arr != priorities) free(prio_arr); + + *mime_count = new_count; + ret = (new_count > 0) ? 1 : 0; } break; - case GLFW_DRAG_MOVE: ret = 1; - case GLFW_DRAG_LEAVE: break; + case GLFW_DRAG_LEAVE: + break; } global_state.callback_os_window = NULL; return ret; } -static int +static void drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) { - if (!set_callback_window(w)) return 0; -#define RETURN(x) { global_state.callback_os_window = NULL; return x; } - if (!data) return is_droppable_mime(mime); + if (!set_callback_window(w)) return; WINDOW_CALLBACK(on_drop, "sy#", mime, data, (Py_ssize_t)sz); request_tick_callback(); - RETURN(0); -#undef RETURN + global_state.callback_os_window = NULL; } static void