Simplify new drag/drop API

Fixes #9466
This commit is contained in:
copilot-swe-agent[bot] 2026-02-03 15:24:44 +00:00 committed by Kovid Goyal
parent c0bb8ae2a0
commit fac4420804
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
13 changed files with 365 additions and 154 deletions

View file

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

View file

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

57
glfw/glfw3.h vendored
View file

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

11
glfw/input.c vendored
View file

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

6
glfw/internal.h vendored
View file

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

2
glfw/null_window.c vendored
View file

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

89
glfw/wl_window.c vendored
View file

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

11
glfw/x11_init.c vendored
View file

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

3
glfw/x11_platform.h vendored
View file

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

132
glfw/x11_window.c vendored
View file

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

3
kitty/glfw-wrapper.c generated
View file

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

45
kitty/glfw-wrapper.h generated
View file

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

View file

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