Fix GLFW drop API to always present full original MIME type list to callbacks

Fixes #9633
This commit is contained in:
copilot-swe-agent[bot] 2026-03-10 10:09:47 +00:00 committed by Kovid Goyal
parent 06b074d68d
commit 4706243380
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 142 additions and 68 deletions

View file

@ -117,8 +117,11 @@ typedef UInt8 (*PFN_LMGetKbdType)(void);
#define LMGetKbdType _glfw.ns.tis.GetKbdType
typedef struct _GLFWDropData {
const char **mimes;
const char **mimes; // Original MIME list; strings are owned here, never reordered
size_t mimes_count;
const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
size_t copy_mimes_count; // Accepted count after last callback
bool drag_accepted;
id pasteboard;
id data_mapping;
id file_promise_mapping;

View file

@ -1458,8 +1458,10 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m
static void
free_drop_data(_GLFWwindow *window) {
if (window->ns.drop_data.mimes) {
for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free(window->ns.drop_data.mimes + i);
for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free((void*)window->ns.drop_data.mimes[i]);
free(window->ns.drop_data.mimes);
}
free(window->ns.drop_data.copy_mimes); // pointer array only; strings owned by mimes[]
if (window->ns.drop_data.pasteboard) [window->ns.drop_data.pasteboard release];
if (window->ns.drop_data.data_mapping) [window->ns.drop_data.data_mapping release];
if (window->ns.drop_data.file_promise_mapping) {
@ -1476,12 +1478,24 @@ free_drop_data(_GLFWwindow *window) {
}
static void
update_drop_state(_GLFWwindow *window, size_t mime_count) {
update_drop_state(_GLFWwindow *window, size_t accepted_count) {
_GLFWDropData *d = &window->ns.drop_data;
for (size_t i = mime_count; i < d->mimes_count; i++) {
if (d->mimes[i]) { free((void*)d->mimes[i]); d->mimes[i] = NULL; }
d->copy_mimes_count = accepted_count;
d->drag_accepted = accepted_count > 0;
}
// Reset the working copy of mimes so the next callback sees the full original
// list. Returns false on allocation failure.
static bool
reset_drop_copy_mimes(_GLFWDropData *d) {
if (d->mimes_count == 0) { d->copy_mimes_count = 0; return true; }
if (!d->copy_mimes) {
d->copy_mimes = malloc(d->mimes_count * sizeof(const char*));
if (!d->copy_mimes) return false;
}
d->mimes_count = mime_count;
memcpy(d->copy_mimes, d->mimes, d->mimes_count * sizeof(const char*));
d->copy_mimes_count = d->mimes_count;
return true;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
@ -1546,14 +1560,17 @@ update_drop_state(_GLFWwindow *window, size_t mime_count) {
window->ns.drop_data.mimes = mime_array;
window->ns.drop_data.mimes_count = mime_count;
bool from_self = ([sender draggingSource] != nil);
mime_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, mime_array, mime_count, from_self);
update_drop_state(window, mime_count);
return mime_count ? NSDragOperationGeneric :NSDragOperationNone;
_GLFWDropData *d = &window->ns.drop_data;
if (reset_drop_copy_mimes(d)) {
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
update_drop_state(window, accepted_count);
}
return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
if (!window->ns.drop_data.mimes_count) return NSDragOperationNone;
if (!window->ns.drop_data.drag_accepted) return NSDragOperationNone;
const NSRect contentRect = [window->ns.view frame];
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
@ -1561,35 +1578,40 @@ update_drop_state(_GLFWwindow *window, size_t mime_count) {
bool from_self = ([sender draggingSource] != nil);
_GLFWDropData *d = &window->ns.drop_data;
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->mimes, d->mimes_count, from_self);
update_drop_state(window, mime_count);
return mime_count ? NSDragOperationGeneric :NSDragOperationNone;
if (reset_drop_copy_mimes(d)) {
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
update_drop_state(window, accepted_count);
}
return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
bool from_self = ([sender draggingSource] != nil);
_GLFWDropData *d = &window->ns.drop_data;
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->mimes, d->mimes_count, from_self);
update_drop_state(window, mime_count);
if (reset_drop_copy_mimes(d)) {
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->copy_mimes, d->copy_mimes_count, from_self);
update_drop_state(window, accepted_count);
}
free_drop_data(window);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
if (!window->ns.drop_data.mimes_count) return NO;
if (!window->ns.drop_data.drag_accepted) return NO;
const NSRect contentRect = [window->ns.view frame];
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
bool from_self = ([sender draggingSource] != nil);
_GLFWDropData *d = &window->ns.drop_data;
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->mimes, d->mimes_count, from_self);
if (d->mimes) {
update_drop_state(window, mime_count);
if (!reset_drop_copy_mimes(d)) return NO;
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
if (d->copy_mimes) {
update_drop_state(window, num_accepted);
window->ns.drop_data.pasteboard = [[sender draggingPasteboard] retain];
for (size_t i = 0; i < d->mimes_count; i++)
_glfwPlatformRequestDropData(window, d->mimes[i]);
for (size_t i = 0; i < num_accepted; i++)
_glfwPlatformRequestDropData(window, d->copy_mimes[i]);
}
return YES;
}

8
glfw/input.c vendored
View file

@ -402,7 +402,13 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered)
window->callbacks.cursorEnter((GLFWwindow*) window, entered);
}
// Notifies shared code of a drop event
// Notifies shared code of a drop event.
// The caller is responsible for passing a mutable working-copy of the mimes
// array (reset to the full original list before each call) so that the
// callback can sort/filter in-place without touching the backend's canonical
// storage. The return value is ev.num_mimes after the callback returns,
// i.e. the number of accepted (possibly reordered) mimes starting at
// mimes[0].
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;
GLFWDropEvent ev = {

2
glfw/wl_platform.h vendored
View file

@ -310,6 +310,8 @@ typedef struct _GLFWWaylandDataOffer
struct wl_surface *surface;
const char **mimes;
size_t mimes_capacity, mimes_count;
const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
size_t copy_mimes_count; // Count of entries in copy_mimes (accepted count after callback)
bool drag_accepted, dropped;
uint32_t serial;
struct {

58
glfw/wl_window.c vendored
View file

@ -2364,10 +2364,25 @@ destroy_data_offer(_GLFWWaylandDataOffer *offer) {
for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]);
free(offer->mimes);
}
free(offer->copy_mimes); // pointer array only; strings are owned by mimes[]
if (offer->requested_drop_data) destroy_drop_data(offer);
memset(offer, 0, sizeof(offer[0]));
}
// Reset the working copy of mimes so the next callback sees the full original
// list. Returns false on allocation failure.
static bool
reset_copy_mimes(_GLFWWaylandDataOffer *offer) {
if (offer->mimes_count == 0) { offer->copy_mimes_count = 0; return true; }
if (!offer->copy_mimes) {
offer->copy_mimes = malloc(offer->mimes_count * sizeof(const char*));
if (!offer->copy_mimes) return false;
}
memcpy(offer->copy_mimes, offer->mimes, offer->mimes_count * sizeof(const char*));
offer->copy_mimes_count = offer->mimes_count;
return true;
}
static void
mark_data_offer(_GLFWWaylandDataOffer *ans, void *id) {
if (ans->id) destroy_data_offer(ans);
@ -2480,15 +2495,12 @@ static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary
// Helper function to update drop state from callback results
static void
update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t mime_count) {
for (size_t i = mime_count; i < d->mimes_count; i++) {
if (d->mimes[i]) { free((void*)d->mimes[i]); d->mimes[i] = NULL; }
}
d->mimes_count = mime_count;
bool accepted = mime_count > 0;
update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t accepted_count) {
d->copy_mimes_count = accepted_count;
bool accepted = accepted_count > 0;
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;
// The first entry in the accepted (sorted) copy is the preferred MIME.
const char* new_preferred_mime = (accepted && d->copy_mimes) ? d->copy_mimes[0] : NULL;
bool mime_changed = false;
// Check if the preferred MIME changed
@ -2522,10 +2534,12 @@ drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint
if (window->wl.surface == surface) {
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
size_t mime_count = _glfwInputDropEvent(
window, GLFW_DROP_ENTER, xpos, ypos,
offer->mimes, offer->mimes_count, offer->is_self_offer);
update_drop_state(offer, window, mime_count);
if (reset_copy_mimes(offer)) {
size_t mime_count = _glfwInputDropEvent(
window, GLFW_DROP_ENTER, xpos, ypos,
offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
update_drop_state(offer, window, mime_count);
}
break;
}
window = window->next;
@ -2643,9 +2657,11 @@ drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
_GLFWwindow* window = _glfw.windowListHead;
while (window) {
if (window->wl.surface == offer->surface) {
size_t num_mimes = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, offer->mimes, offer->mimes_count, offer->is_self_offer);
if (!offer->mimes) { destroy_data_offer(offer); return; }
for (size_t i = 0; i < num_mimes; i++) request_drop_data(offer, offer->mimes[i]);
if (reset_copy_mimes(offer)) {
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
if (!offer->copy_mimes) { destroy_data_offer(offer); return; }
for (size_t i = 0; i < num_accepted; i++) request_drop_data(offer, offer->copy_mimes[i]);
}
break;
}
window = window->next;
@ -2661,9 +2677,11 @@ motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t
if (window->wl.surface == offer->surface) {
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
size_t mime_count = _glfwInputDropEvent(
window, GLFW_DROP_MOVE, xpos, ypos, offer->mimes, offer->mimes_count, offer->is_self_offer);
update_drop_state(offer, window, mime_count);
if (reset_copy_mimes(offer)) {
size_t mime_count = _glfwInputDropEvent(
window, GLFW_DROP_MOVE, xpos, ypos, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
update_drop_state(offer, window, mime_count);
}
break;
}
window = window->next;
@ -2673,8 +2691,8 @@ motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t
void
_glfwPlatformRequestDropUpdate(_GLFWwindow* window) {
_GLFWWaylandDataOffer *d = &_glfw.wl.drop_data_offer;
if (d->id) {
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, d->mimes, d->mimes_count, d->is_self_offer);
if (d->id && reset_copy_mimes(d)) {
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, d->copy_mimes, d->copy_mimes_count, d->is_self_offer);
update_drop_state(d, window, mime_count);
}
}

7
glfw/x11_platform.h vendored
View file

@ -393,8 +393,11 @@ typedef struct _GLFWlibraryX11
char format[256];
int format_priority;
Window target_window; // For drag events: the window being dragged over
const char** mimes; // Cached MIME types from drag enter
size_t mimes_count; // Current count of MIME types (may be reduced by callback)
const char** mimes; // Cached MIME types from drag enter (original, never reordered)
size_t mimes_count; // Count of MIME types (full original list, never reduced)
const char** copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
size_t copy_mimes_count; // Accepted count after last callback
bool drag_accepted; // Whether the callback accepted at least one MIME type
bool from_self, dropped;
Time drop_time;
XdndSelectionRequest *selection_requests;

68
glfw/x11_window.c vendored
View file

@ -1498,7 +1498,7 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
static void
end_drop(_GLFWwindow *window, GLFWDragOperationType op) {
bool accepted = dnd.mimes_count > 0 || dnd.dropped;
bool accepted = dnd.drag_accepted || dnd.dropped;
XEvent reply = { ClientMessage };
reply.xclient.window = dnd.source;
reply.xclient.message_type = _glfw.x11.XdndFinished;
@ -1520,14 +1520,12 @@ end_drop(_GLFWwindow *window, GLFWDragOperationType op) {
static void
update_drop_state(_GLFWwindow* window, size_t mime_count) {
for (size_t i = mime_count; i < dnd.mimes_count; i++) {
if (dnd.mimes[i]) { XFree((void*)dnd.mimes[i]); dnd.mimes[i] = NULL; }
}
dnd.mimes_count = mime_count;
bool accepted = mime_count > 0;
// The first MIME in the sorted list is the preferred one for drop
const char* new_preferred_mime = (accepted && mime_count > 0) ? dnd.mimes[0] : NULL;
update_drop_state(_GLFWwindow* window, size_t accepted_count) {
dnd.copy_mimes_count = accepted_count;
bool accepted = accepted_count > 0;
dnd.drag_accepted = accepted;
// The first entry in the accepted (sorted) copy is the preferred MIME.
const char* new_preferred_mime = (accepted && dnd.copy_mimes) ? dnd.copy_mimes[0] : NULL;
// Check if the preferred MIME changed
bool mime_changed = strncmp(new_preferred_mime ? new_preferred_mime : "", dnd.format, arraysz(dnd.format)) != 0;
if (mime_changed) {
@ -1566,12 +1564,30 @@ free_dnd_mimes(void) {
dnd.mimes = NULL;
dnd.mimes_count = 0;
}
free(dnd.copy_mimes); // pointer array only; strings are owned by mimes[]
dnd.copy_mimes = NULL;
dnd.copy_mimes_count = 0;
}
// Reset the working copy of mimes so the next callback sees the full original
// list. Returns false on allocation failure.
static bool
reset_dnd_copy_mimes(void) {
if (dnd.mimes_count == 0) { dnd.copy_mimes_count = 0; return true; }
if (!dnd.copy_mimes) {
dnd.copy_mimes = malloc(dnd.mimes_count * sizeof(const char*));
if (!dnd.copy_mimes) return false;
}
memcpy(dnd.copy_mimes, dnd.mimes, dnd.mimes_count * sizeof(const char*));
dnd.copy_mimes_count = dnd.mimes_count;
return true;
}
void
free_dnd_data(void) {
dnd.source = None;
dnd.target_window = None;
dnd.drag_accepted = false;
free_dnd_mimes();
if (dnd.selection_requests) {
for (size_t i = 0; i < dnd.selection_requests_count; i++) {
@ -1640,14 +1656,13 @@ drop_start(_GLFWwindow *window, XEvent *event) {
update_dnd_mimes(event);
dnd.from_self = _glfw.x11.drag.source_window != None && dnd.source == _glfw.x11.drag.source_window;
// Position is not known yet at enter time, will be updated with XdndPosition
size_t mimes_count = _glfwInputDropEvent(
window, GLFW_DROP_ENTER, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
update_drop_state(window, mimes_count);
// Update cached mime count with callback result
if (dnd.mimes_count > 0) {
// The first MIME type in the reordered list is the preferred one
strncpy(dnd.format, dnd.mimes[0], arraysz(dnd.format) - 1);
dnd.format_priority = 1;
if (reset_dnd_copy_mimes()) {
size_t accepted_count = _glfwInputDropEvent(
window, GLFW_DROP_ENTER, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
update_drop_state(window, accepted_count);
// Set format_priority when the callback accepted at least one MIME type.
// dnd.format has already been updated by update_drop_state.
if (accepted_count > 0) dnd.format_priority = 1;
}
}
@ -1674,8 +1689,10 @@ drop_move(_GLFWwindow *window, XEvent *event) {
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position");
_glfwInputCursorPos(window, xpos, ypos);
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, dnd.mimes, dnd.mimes_count, dnd.from_self);
update_drop_state(window, mimes_count);
if (reset_dnd_copy_mimes()) {
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
update_drop_state(window, mimes_count);
}
}
void
@ -1684,8 +1701,10 @@ _glfwPlatformRequestDropUpdate(_GLFWwindow* window) {
if (dnd.source == None || dnd.target_window != window->x11.handle) return;
// Call the drag callback with STATUS_UPDATE event to get updated state
// Position values are not valid for this event type
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
update_drop_state(window, mimes_count);
if (reset_dnd_copy_mimes()) {
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
update_drop_state(window, mimes_count);
}
}
@ -1695,9 +1714,10 @@ drop(_GLFWwindow *window, XEvent *event) {
if (dnd.version > _GLFW_XDND_VERSION || dnd.version < 2) return;
dnd.dropped = true;
dnd.drop_time = (unsigned long)event->xclient.data.l[2];
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
if (!dnd.mimes) return;
for (size_t i = 0; i < mimes_count; i++) _glfwPlatformRequestDropData(window, dnd.mimes[i]);
if (!reset_dnd_copy_mimes()) return;
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
if (!dnd.copy_mimes) return;
for (size_t i = 0; i < num_accepted; i++) _glfwPlatformRequestDropData(window, dnd.copy_mimes[i]);
}
static void