diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 6f53c32e8..b59c89e48 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1343,6 +1343,13 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m _glfwInputScroll(window, &ev); } +// Return YES to receive periodic dragging updates even when the mouse hasn't moved. +// This allows the application to update acceptance status asynchronously. +- (BOOL)wantsPeriodicDraggingUpdates +{ + return YES; +} + - (NSDragOperation)draggingEntered:(id )sender { const NSRect contentRect = [window->ns.view frame]; @@ -1414,9 +1421,12 @@ 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 - _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); - return NSDragOperationGeneric; + // Call drag move callback and return acceptance status + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + + if (accepted) + return NSDragOperationGeneric; + return NSDragOperationNone; } - (void)draggingExited:(id )sender @@ -3797,3 +3807,9 @@ 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. +} + diff --git a/glfw/glfw3.h b/glfw/glfw3.h index c300cb129..4cb368272 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1852,11 +1852,14 @@ typedef struct GLFWdragitem { * 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. + * @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 glfwSetDragAcceptance * * @since Added in version 4.0. * @@ -5067,6 +5070,40 @@ 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. + * + * 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. + * + * 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. + * + * @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. + * + * @remark This function has no effect if there is no active drag operation + * 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. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref drag_events + * @sa @ref glfwSetDragCallback + * + * @since Added in version 4.0. + * + * @ingroup input + */ +GLFWAPI void glfwSetDragAcceptance(GLFWwindow* window, bool accepted); + /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. diff --git a/glfw/input.c b/glfw/input.c index 5c19d3174..21c5717fe 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -1141,6 +1141,15 @@ 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) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT(); + _glfwPlatformSetDragAcceptance(window, accepted); +} + GLFWAPI int glfwJoystickPresent(int jid) { _GLFWjoystick* js; diff --git a/glfw/internal.h b/glfw/internal.h index 90abe42e0..d7789f070 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -768,6 +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 _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); diff --git a/glfw/null_window.c b/glfw/null_window.c index 9c7e20bc3..0dd4ce258 100644 --- a/glfw/null_window.c +++ b/glfw/null_window.c @@ -539,6 +539,11 @@ int _glfwPlatformStartDrag(_GLFWwindow* window UNUSED, return false; } +void _glfwPlatformSetDragAcceptance(_GLFWwindow* window UNUSED, bool accepted UNUSED) +{ + // No-op for null platform +} + void _glfwPlatformSetClipboardString(const char* string) { char* copy = _glfw_strdup(string); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 62aafdc89..8baa710bf 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -317,6 +317,8 @@ typedef struct _GLFWWaylandDataOffer struct wl_surface *surface; const char **mimes; size_t mimes_capacity, mimes_count; + bool drag_accepted; + uint32_t serial; } _GLFWWaylandDataOffer; // Wayland-specific global data diff --git a/glfw/wl_window.c b/glfw/wl_window.c index bcbbcd0ba..9ffcfde88 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2492,6 +2492,8 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device if (d->id == id) { d->offer_type = DRAG_AND_DROP; d->surface = surface; + d->serial = serial; + d->drag_accepted = false; _GLFWwindow* window = _glfw.windowListHead; int format_priority = 0; while (window) @@ -2504,6 +2506,7 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device // 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) { @@ -2569,16 +2572,43 @@ 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++) { - if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { + _GLFWWaylandDataOffer *d = &_glfw.wl.dataOffers[i]; + if (d->offer_type == DRAG_AND_DROP) { _GLFWwindow* window = _glfw.windowListHead; while (window) { - if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { + if (window->wl.surface == d->surface) { double xpos = wl_fixed_to_double(x); double ypos = wl_fixed_to_double(y); - _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + + // Update acceptance status based on callback return value + update_drag_acceptance(d, window, accepted); break; } window = window->next; @@ -3157,3 +3187,15 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_ return true; } +void +_glfwPlatformSetDragAcceptance(_GLFWwindow* window, bool accepted) { + // Find the active drag offer for this window and update its acceptance status + 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); + return; + } + } +} + diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index 1a89ed807..c74fc7d36 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -385,6 +385,7 @@ typedef struct _GLFWlibraryX11 char format[128]; int format_priority; Window target_window; // For drag events: the window being dragged over + bool drag_accepted; // Whether the current drag is accepted } xdnd; // Drag source state diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 43cf0a3aa..45f8f5d04 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -1836,6 +1836,7 @@ static void processEvent(XEvent *event) _glfw.x11.xdnd.target_window = window->x11.handle; memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format)); _glfw.x11.xdnd.format_priority = 0; + _glfw.x11.xdnd.drag_accepted = false; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; @@ -1869,6 +1870,7 @@ static void processEvent(XEvent *event) 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]) { @@ -1957,8 +1959,9 @@ static void processEvent(XEvent *event) _glfwInputCursorPos(window, xpos, ypos); - // Call the drag move callback - _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + // Call the drag move callback and update acceptance status + int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0); + _glfw.x11.xdnd.drag_accepted = accepted; XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; @@ -1968,7 +1971,7 @@ static void processEvent(XEvent *event) reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; - if (_glfw.x11.xdnd.format_priority > 0) + if (_glfw.x11.xdnd.format_priority > 0 && _glfw.x11.xdnd.drag_accepted) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle @@ -3758,3 +3761,34 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, return true; } +void _glfwPlatformSetDragAcceptance(_GLFWwindow* window, bool accepted) { + // 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 + _glfw.x11.xdnd.drag_accepted = accepted; + + // Send an XdndStatus message to update the drag source + XEvent reply = { ClientMessage }; + reply.xclient.window = _glfw.x11.xdnd.source; + reply.xclient.message_type = _glfw.x11.XdndStatus; + reply.xclient.format = 32; + reply.xclient.data.l[0] = window->x11.handle; + reply.xclient.data.l[2] = 0; // Specify an empty rectangle + reply.xclient.data.l[3] = 0; + + if (_glfw.x11.xdnd.format_priority > 0 && accepted) { + // Reply that we are ready to copy the dragged data + reply.xclient.data.l[1] = 1; // Accept with no rectangle + if (_glfw.x11.xdnd.version >= 2) + reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; + } + + XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, + False, NoEventMask, &reply); + XFlush(_glfw.x11.display); +} + diff --git a/kitty/glfw.c b/kitty/glfw.c index 777122b4c..2a0adfb56 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -653,12 +653,18 @@ static int 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; - if (event == GLFW_DRAG_ENTER) { - for (int i = 0; i < mime_count; i++) { - if (is_droppable_mime(mime_types[i])) return 1; - } + 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; } + } + break; + case GLFW_DRAG_MOVE: ret = 1; + case GLFW_DRAG_LEAVE: break; } - return 0; + global_state.callback_os_window = NULL; + return ret; } static int