diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 8303c72cd..6f53c32e8 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -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 )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; diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 5ddb6b69c..c300cb129 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -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). diff --git a/glfw/input.c b/glfw/input.c index 1e3307ee9..5c19d3174 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -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; } diff --git a/glfw/internal.h b/glfw/internal.h index c7147f2c0..90abe42e0 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -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); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 93c36d823..bcbbcd0ba 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -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; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index ecf04fca3..43cf0a3aa 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -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; diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index f2ee2fa58..80013c9d3 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -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); diff --git a/kitty/glfw.c b/kitty/glfw.c index 57eb77b03..777122b4c 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -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);