Allow changing drag acceptance asynchronously

Fixes #9465
This commit is contained in:
copilot-swe-agent[bot]
2026-02-03 08:27:45 +00:00
committed by Kovid Goyal
parent 6744183027
commit 9b35c1b535
10 changed files with 169 additions and 16 deletions

View File

@@ -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 <NSDraggingInfo>)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 <NSDraggingInfo>)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.
}

41
glfw/glfw3.h vendored
View File

@@ -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.

9
glfw/input.c vendored
View File

@@ -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;

1
glfw/internal.h vendored
View File

@@ -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);

5
glfw/null_window.c vendored
View File

@@ -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);

2
glfw/wl_platform.h vendored
View File

@@ -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

48
glfw/wl_window.c vendored
View File

@@ -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;
}
}
}

1
glfw/x11_platform.h vendored
View File

@@ -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

40
glfw/x11_window.c vendored
View File

@@ -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);
}

View File

@@ -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