Accept drags based on mime types in drag event callback

Fixes #9456
This commit is contained in:
copilot-swe-agent[bot] 2026-02-02 15:24:46 +00:00 committed by Kovid Goyal
parent fa6c76d3e3
commit 6744183027
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 118 additions and 36 deletions

View file

@ -39,6 +39,10 @@
#define debug debug_rendering
// Macro and forward declaration needed before draggingEntered: (uti_to_mime is defined in Clipboard section)
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
static const char* uti_to_mime(NSString *uti);
static const char*
polymorphic_string_as_utf8(id string) {
if (string == nil) return "(nil)";
@ -1346,8 +1350,58 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
// Call drag enter callback and check if accepted
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos);
// Get MIME types from the dragging pasteboard
NSPasteboard* pasteboard = [sender draggingPasteboard];
// Count total types across all pasteboard items plus 2 for uri-list and text/plain
size_t max_types = 2;
for (NSPasteboardItem* item in pasteboard.pasteboardItems) {
max_types += [item.types count];
}
// 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);
return accepted ? NSDragOperationGeneric : NSDragOperationNone;
}
int mime_count = 0;
// Check for common types first
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
if ([pasteboard canReadObjectForClasses:@[[NSURL class]] options:options]) {
mime_array[mime_count++] = "text/uri-list";
}
if ([pasteboard canReadObjectForClasses:@[[NSString class]] options:nil]) {
mime_array[mime_count++] = "text/plain";
}
// Get additional types from pasteboard items
for (NSPasteboardItem* item in pasteboard.pasteboardItems) {
for (NSPasteboardType type in item.types) {
const char* mime = uti_to_mime(type);
if (mime && mime[0]) {
// Check for duplicates
bool duplicate = false;
for (int i = 0; i < mime_count; i++) {
if (strcmp(mime_array[i], mime) == 0) {
duplicate = true;
break;
}
}
if (!duplicate) {
mime_array[mime_count++] = mime;
}
}
}
}
// Call drag enter callback with MIME types
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, mime_array, mime_count);
free(mime_array);
if (accepted)
return NSDragOperationGeneric;
return NSDragOperationNone;
@ -1361,7 +1415,7 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m
double ypos = contentRect.size.height - pos.y;
// Call drag move callback
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos);
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0);
return NSDragOperationGeneric;
}
@ -1369,7 +1423,7 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m
{
(void)sender;
// Call drag leave callback
_glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0);
_glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
@ -3025,8 +3079,6 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
// Clipboard {{{
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
static NSString*
mime_to_uti(const char *mime) {
if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString;

16
glfw/glfw3.h vendored
View file

@ -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)
* 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,6 +1846,12 @@ 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.
*
@ -1856,7 +1862,7 @@ typedef struct GLFWdragitem {
*
* @ingroup input
*/
typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos);
typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int mime_count);
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);
@ -4995,6 +5001,10 @@ GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWlive
* callback to accept or reject incoming drag operations and track drag
* position.
*
* For @ref GLFW_DRAG_ENTER events, the callback receives an array of MIME types
* available from the drag source. The application can use this information to
* decide whether to accept or reject the drag operation.
*
* @param[in] window The window whose callback to set.
* @param[in] callback The new callback, or `NULL` to remove the currently set
* callback.
@ -5003,7 +5013,7 @@ GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWlive
*
* @callback_signature
* @code
* int function_name(GLFWwindow* window, int event, double xpos, double ypos)
* int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int mime_count)
* @endcode
* For more information about the callback parameters, see the
* [function pointer type](@ref GLFWdragfun).

4
glfw/input.c vendored
View file

@ -412,10 +412,10 @@ int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size
// Notifies shared code of a drag event
//
int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos)
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);
return window->callbacks.drag((GLFWwindow*) window, event, xpos, ypos, mime_types, mime_count);
return 0;
}

2
glfw/internal.h vendored
View file

@ -822,7 +822,7 @@ 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);
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);

8
glfw/wl_window.c vendored
View file

@ -2497,10 +2497,10 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device
while (window)
{
if (window->wl.surface == surface) {
// Call drag enter callback
// Call drag enter callback with MIME types
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos);
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, d->mimes, (int)d->mimes_count);
// If accepted, check MIME type priorities
if (accepted) {
@ -2531,7 +2531,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);
_glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0);
break;
}
window = window->next;
@ -2578,7 +2578,7 @@ static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUS
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
double xpos = wl_fixed_to_double(x);
double ypos = wl_fixed_to_double(y);
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos);
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0);
break;
}
window = window->next;

27
glfw/x11_window.c vendored
View file

@ -1840,10 +1840,7 @@ static void processEvent(XEvent *event)
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return;
// Call the drag enter callback first
// Position is not known yet at enter time, will be updated with XdndPosition
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, 0, 0);
// Get the MIME types before calling the callback
if (list)
{
count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source,
@ -1856,10 +1853,22 @@ static void processEvent(XEvent *event)
count = 3;
formats = (Atom*) event->xclient.data.l + 2;
}
char **atom_names = calloc(count, sizeof(char*));
if (atom_names && accepted) {
get_atom_names(formats, count, atom_names);
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
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) {
for (i = 0; i < count; i++)
{
if (atom_names[i]) {
@ -1920,7 +1929,7 @@ 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);
_glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, 0);
_glfw.x11.xdnd.source = None;
_glfw.x11.xdnd.target_window = None;
}
@ -1949,7 +1958,7 @@ static void processEvent(XEvent *event)
_glfwInputCursorPos(window, xpos, ypos);
// Call the drag move callback
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos);
_glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, NULL, 0);
XEvent reply = { ClientMessage };
reply.xclient.window = _glfw.x11.xdnd.source;

10
kitty/glfw-wrapper.h generated
View file

@ -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)
* 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,6 +1584,12 @@ 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.
*
@ -1594,7 +1600,7 @@ typedef struct GLFWdragitem {
*
* @ingroup input
*/
typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos);
typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int mime_count);
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);

View file

@ -642,11 +642,21 @@ window_focus_callback(GLFWwindow *w, int focused) {
}
static int
drag_callback(GLFWwindow *w, GLFWDragEventType event, double xpos, double ypos) {
(void)event; (void)xpos; (void)ypos;
is_droppable_mime(const char *mime) {
if (strcmp(mime, "text/uri-list") == 0) return 3;
if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2;
if (strcmp(mime, "text/plain") == 0) return 1;
return 0;
}
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) {
return 1;
for (int i = 0; i < mime_count; i++) {
if (is_droppable_mime(mime_types[i])) return 1;
}
}
return 0;
}
@ -655,12 +665,7 @@ static int
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) {
if (strcmp(mime, "text/uri-list") == 0) RETURN(3);
if (strcmp(mime, "text/plain;charset=utf-8") == 0) RETURN(2);
if (strcmp(mime, "text/plain") == 0) RETURN(1);
RETURN(0);
}
if (!data) return is_droppable_mime(mime);
WINDOW_CALLBACK(on_drop, "sy#", mime, data, (Py_ssize_t)sz);
request_tick_callback();
RETURN(0);