diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 4303d80e7..fc6e98ba6 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -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; diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index fd6c53572..768f3b545 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1458,8 +1458,10 @@ - (BOOL)wantsPeriodicDraggingUpdates 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 @@ - (BOOL)wantsPeriodicDraggingUpdates } 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 )sender @@ -1546,14 +1560,17 @@ - (NSDragOperation)draggingEntered:(id )sender 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 )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 @@ - (NSDragOperation)draggingUpdated:(id )sender 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 )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 )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; } diff --git a/glfw/input.c b/glfw/input.c index ba84a54b6..d35f11ba6 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -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 = { diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 4781c802b..e9ae1533e 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -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 { diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 3a3feeac9..1ebeeea83 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -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); } } diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index a74dae830..a8bcdaf2f 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -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; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index ec05354be..31d44f44e 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -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