diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index 889ab4e91..e3885bdf2 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -31,7 +31,6 @@ update_fds(EventLoopData *eld) { } } -static id_type watch_counter = 0; id_type addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) { @@ -39,6 +38,7 @@ addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, _glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added"); return 0; } + static id_type watch_counter = 0; Watch *w = eld->watches + eld->watches_count++; w->name = name; w->fd = fd; w->events = events; w->enabled = enabled; diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 3031dfedb..2c97d9f59 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1384,6 +1384,35 @@ typedef struct GLFWDBUSNotificationData { int32_t timeout; uint8_t urgency; uint32_t replaces; int muted; } GLFWDBUSNotificationData; +typedef enum { GLFW_DROP_ENTER, GLFW_DROP_MOVE, GLFW_DROP_LEAVE, GLFW_DROP_DROP, GLFW_DROP_STATUS_UPDATE, GLFW_DROP_DATA_AVAILABLE } GLFWDropEventType; + +/*! @brief Drag operation types. + * + * These constants specify the type of drag operation (copy, move, or generic). + * + * @ingroup input + */ +typedef enum { + /*! Move the dragged data to the destination. */ + GLFW_DRAG_OPERATION_MOVE = 1, + /*! Copy the dragged data to the destination. */ + GLFW_DRAG_OPERATION_COPY = 2, + /*! Generic drag operation (platform decides semantics). */ + GLFW_DRAG_OPERATION_GENERIC = 4 +} GLFWDragOperationType; + + +typedef struct GLFWDropEvent { + GLFWDropEventType type; + const char **mimes; size_t num_mimes; + double xpos, ypos; // Only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE + bool from_self; + ssize_t (*read_data)(GLFWwindow *w, struct GLFWDropEvent* ev, char *buffer, size_t sz); // Only valid for GLFW_DROP_DATA_AVAILABLE + void (*finish_drop)(GLFWwindow *w, GLFWDragOperationType op); // Only valid for GLFW_DROP_DROP and GLFW_DROP_DATA_AVAILABLE +} GLFWDropEvent; +typedef void (* GLFWdropeventfun)(GLFWwindow*, GLFWDropEvent *event); + + /*! @brief The function pointer type for error callbacks. * * This is the function pointer type for error callbacks. An error callback @@ -1836,21 +1865,6 @@ typedef enum { GLFW_DRAG_STATUS_UPDATE = 4 } GLFWDragEventType; -/*! @brief Drag operation types. - * - * These constants specify the type of drag operation (copy, move, or generic). - * - * @ingroup input - */ -typedef enum { - /*! Move the dragged data to the destination. */ - GLFW_DRAG_OPERATION_MOVE = 1, - /*! Copy the dragged data to the destination. */ - GLFW_DRAG_OPERATION_COPY = 2, - /*! Generic drag operation (platform decides semantics). */ - GLFW_DRAG_OPERATION_GENERIC = 4 -} GLFWDragOperationType; - /*! @brief Opaque drag source data handle. * * This is an opaque handle to a heap-allocated object that represents @@ -5054,42 +5068,10 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun ca GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun callback); GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun callback); -/*! @brief Sets the drag event callback. - * - * This function sets the callback for drag events. The callback is invoked - * when a drag operation enters, moves within, or leaves the window. Use this - * 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. - * @return The previously set callback, or `NULL` if no callback was set or the - * library had not been [initialized](@ref intro_init). - * - * @callback_signature - * @code - * 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). - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref drag_events - * @sa @ref glfwStartDrag - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* window, GLFWdragfun callback); +GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow *window, GLFWdropeventfun callback); +GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window); // ask for update before GLFW_DROP_DROP happens +GLFWAPI int glfwRequestDropData(GLFWwindow *window, const char *mime); +GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op); /*! @brief Sets the drag source data request callback. * @@ -5191,126 +5173,6 @@ GLFWAPI int glfwStartDrag(GLFWwindow* window, const char* const* mime_types, int */ GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size); -/*! @brief Schedules a call to the drag callback to update drag state. - * - * 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. - * - * 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. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @remark This function has no effect if there is no active drag operation - * over the specified window. - * - * @remark On macOS, this function is a no-op as the system uses periodic - * dragging updates via the drag callback. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref drag_events - * @sa @ref glfwSetDragCallback - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI void glfwUpdateDragState(GLFWwindow* window); - -/*! @brief Gets the list of available MIME types from drop data. - * - * This function returns an array of MIME type strings available from - * the dropped data. The array and strings are owned by GLFW and should - * not be freed by the application. They are only valid until the drop - * callback returns. - * - * @param[in] drop The drop data object from the drop callback. - * @param[out] count The number of MIME types in the returned array. - * @return An array of MIME type strings, or `NULL` if the drop data is invalid. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref path_drop - * @sa @ref glfwSetDropCallback - * @sa @ref glfwReadDropData - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI const char** glfwGetDropMimeTypes(GLFWDropData* drop, int* count); - -/*! @brief Reads a chunk of dropped data for a specified MIME type. - * - * This function reads a chunk of the dropped data of the specified MIME type - * into the provided buffer. Repeated calls read subsequent chunks of data. - * - * If this function is called for a different MIME type than the previous call, - * resources associated with the previous MIME type are automatically freed. - * - * @param[in] drop The drop data object from the drop callback. - * @param[in] mime The MIME type to read data for. - * @param[out] buffer The buffer to read data into. - * @param[in] capacity The size of the buffer in bytes. - * @param[in] timeout The maximum time to wait for data, as a monotonic time value. - * Use 0 for non-blocking, or a positive value for the timeout duration. - * @return The number of bytes written to the buffer on success. Returns 0 when - * all data has been read (end of data). Returns a negative value on error: - * `-ENOENT` if the MIME type is not available, `-EIO` for an I/O error, - * `-ETIME` if the timeout expires before data is available. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @remark The returned byte count may be less than the buffer capacity even - * when more data is available. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref path_drop - * @sa @ref glfwSetDropCallback - * @sa @ref glfwGetDropMimeTypes - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI ssize_t glfwReadDropData(GLFWDropData* drop, const char* mime, void* buffer, size_t capacity, monotonic_t timeout); - -/*! @brief Finishes a drop operation and frees associated resources. - * - * This function finishes the drop operation, informs the drag source of the - * result, and frees the heap-allocated drop data object. After calling this - * function, the drop data object must not be used anymore. - * - * The application MUST call this function when it has finished reading the - * dropped data. Failure to do so will result in a memory leak and may cause - * the drag source to hang waiting for the drop operation to complete. - * - * @param[in] drop The drop data object to finish and free. - * @param[in] operation The type of operation that was performed (copy, move, etc). - * @param[in] success Whether the drop operation was successful. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref path_drop - * @sa @ref glfwSetDropCallback - * @sa @ref glfwReadDropData - * - * @since Added in version 4.0. - * - * @ingroup input - */ -GLFWAPI void glfwFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation, bool success); - /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. diff --git a/glfw/input.c b/glfw/input.c index b5b1a8564..7e601a340 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -419,6 +419,18 @@ int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos return 0; } +// Notifies shared code of a drop event +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 = { + .mimes=mimes, .type=type, .xpos=xpos, .ypos=ypos, .num_mimes=num_mimes, .from_self=from_self, + .read_data=type == GLFW_DROP_DATA_AVAILABLE ? _glfwPlatformReadAvailableDropData : NULL, + .finish_drop=type == GLFW_DROP_DATA_AVAILABLE || type == GLFW_DROP_DROP ? _glfwPlatformEndDrop : NULL, + }; + window->callbacks.drop_event((GLFWwindow*)window, &ev); + return ev.num_mimes; +} + // Notifies shared code that the OS wants data for a MIME type from the drag source // void _glfwInputDragSourceRequest(_GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data) @@ -693,6 +705,18 @@ void _glfwCenterCursorInContentArea(_GLFWwindow* window) ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// +GLFWAPI int glfwRequestDropData(GLFWwindow *window, const char *mime) { + return _glfwPlatformRequestDropData((_GLFWwindow*)window, mime); +} + +GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op) { + _glfwPlatformEndDrop(window, op); +} + +GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window) { + _glfwPlatformRequestDropUpdate((_GLFWwindow*)window); +} + GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void) { return _glfw.ignoreOSKeyboardProcessing; } @@ -1138,6 +1162,17 @@ GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* handle, GLFWdragfun cbfun) return cbfun; } +GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow* handle, GLFWdropeventfun cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(window->callbacks.drop_event, cbfun); + return cbfun; +} + + GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* handle, GLFWdragsourcefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; @@ -1171,31 +1206,6 @@ GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* da return _glfwPlatformSendDragData(source_data, data, size); } -GLFWAPI void glfwUpdateDragState(GLFWwindow* handle) -{ - _GLFWwindow* window = (_GLFWwindow*) handle; - assert(window != NULL); - - _GLFW_REQUIRE_INIT(); - _glfwPlatformUpdateDragState(window); -} - -GLFWAPI const char** glfwGetDropMimeTypes(GLFWDropData* drop, int* count) -{ - assert(drop != NULL); - assert(count != NULL); - - _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - return _glfwPlatformGetDropMimeTypes(drop, count); -} - -GLFWAPI ssize_t glfwReadDropData(GLFWDropData* drop, const char* mime, void* buffer, size_t capacity, monotonic_t timeout) -{ - if (drop == NULL || mime == NULL || buffer == NULL || capacity < 1) return -EINVAL; - _GLFW_REQUIRE_INIT_OR_RETURN(-1); - return _glfwPlatformReadDropData(drop, mime, buffer, capacity, timeout); -} - GLFWAPI void glfwFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation, bool success) { assert(drop != NULL); diff --git a/glfw/internal.h b/glfw/internal.h index 9b2bb4311..ba604214e 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -517,10 +517,13 @@ struct _GLFWwindow GLFWcursorenterfun cursorEnter; GLFWscrollfun scroll; GLFWkeyboardfun keyboard; - GLFWdropfun drop; GLFWliveresizefun liveResize; + + GLFWdropfun drop; GLFWdragfun drag; GLFWdragsourcefun dragSource; + + GLFWdropeventfun drop_event; } callbacks; // This is defined in the window API's platform.h @@ -810,7 +813,6 @@ void _glfwPlatformChangeCursorTheme(void); int _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations); ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size); void _glfwPlatformCancelDrag(_GLFWwindow* window); -void _glfwPlatformUpdateDragState(_GLFWwindow* window); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); @@ -864,14 +866,20 @@ 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); -void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop, bool from_self); + int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count); void _glfwInputDragSourceRequest(_GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data); - // Platform functions for drop data reading const char** _glfwPlatformGetDropMimeTypes(GLFWDropData* drop, int* count); ssize_t _glfwPlatformReadDropData(GLFWDropData* drop, const char* mime, void* buffer, size_t capacity, monotonic_t timeout); void _glfwPlatformFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation, bool success); + +void _glfwPlatformRequestDropUpdate(_GLFWwindow* window); +size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self); +ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz); +void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op); +int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime); + void _glfwInputColorScheme(GLFWColorScheme, bool); void _glfwPlatformInputColorScheme(GLFWColorScheme); void _glfwInputJoystick(_GLFWjoystick* js, int event); @@ -952,3 +960,4 @@ void _glfw_free_clipboard_data(_GLFWClipboardData *cd); #define debug_rendering(...) if (_glfw.hints.init.debugRendering) { timed_debug_print(__VA_ARGS__); } #define debug_input(...) if (_glfw.hints.init.debugKeyboard) { timed_debug_print(__VA_ARGS__); } +#define safe_close(fd) do { errno = 0; close(fd); } while(errno == EINTR) diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 76c19bac9..98b012dbc 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -935,11 +935,14 @@ void _glfwPlatformTerminate(void) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection); - for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) { - if (_glfw.wl.dataOffers[doi].id) { - destroy_data_offer(&_glfw.wl.dataOffers[doi]); + for (size_t doi=0; doi < arraysz(_glfw.wl.untyped_data_offers); doi++) { + if (_glfw.wl.untyped_data_offers[doi].id) { + destroy_data_offer(&_glfw.wl.untyped_data_offers[doi]); } } + if (_glfw.wl.primary_data_offer.id) destroy_data_offer(&_glfw.wl.primary_data_offer); + if (_glfw.wl.clipboard_data_offer.id) destroy_data_offer(&_glfw.wl.clipboard_data_offer); + if (_glfw.wl.drop_data_offer.id) destroy_data_offer(&_glfw.wl.drop_data_offer); if (_glfw.wl.dataDevice) wl_data_device_destroy(_glfw.wl.dataDevice); if (_glfw.wl.dataDeviceManager) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 755728425..c2a83a0e8 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -296,19 +296,9 @@ typedef struct _GLFWwindowWayland struct zwp_keyboard_shortcuts_inhibitor_v1 *keyboard_shortcuts_inhibitor; } _GLFWwindowWayland; -typedef enum _GLFWWaylandOfferType -{ - EXPIRED, - CLIPBOARD, - DRAG_AND_DROP, - PRIMARY_SELECTION -}_GLFWWaylandOfferType ; - typedef struct _GLFWWaylandDataOffer { void *id; - _GLFWWaylandOfferType offer_type; - size_t idx; bool is_self_offer; bool is_primary; const char *mime_for_drop; @@ -317,8 +307,14 @@ typedef struct _GLFWWaylandDataOffer struct wl_surface *surface; const char **mimes; size_t mimes_capacity, mimes_count; - bool drag_accepted; + bool drag_accepted, dropped; uint32_t serial; + struct { + id_type watch_id; + int fd; + char *mime; + } *requested_drop_data; + size_t dd_capacity, dd_count; } _GLFWWaylandDataOffer; // Wayland-specific global data @@ -404,8 +400,9 @@ typedef struct _GLFWlibraryWayland } activation_requests; EventLoopData eventLoopData; - size_t dataOffersCounter; - _GLFWWaylandDataOffer dataOffers[8]; + _GLFWWaylandDataOffer untyped_data_offers[8]; + _GLFWWaylandDataOffer clipboard_data_offer, primary_data_offer, drop_data_offer; + bool has_preferred_buffer_scale; char *compositor_name; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 696703bf8..361ffde92 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2314,6 +2314,17 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s .cancelled = primary_selection_source_canceled, }; +// Getting data from clipboard and drops {{{ +static void +destroy_drop_data(_GLFWWaylandDataOffer *offer) { + for (size_t i = 0; i < offer->dd_count; i++) { + if (offer->requested_drop_data[i].mime) free(offer->requested_drop_data[i].mime); + if (offer->requested_drop_data[i].fd > -1) { safe_close(offer->requested_drop_data[i].fd); } + if (offer->requested_drop_data[i].watch_id) removeWatch(&_glfw.wl.eventLoopData, offer->requested_drop_data[i].watch_id); + } + free(offer->requested_drop_data); +} + void destroy_data_offer(_GLFWWaylandDataOffer *offer) { if (offer->id) { @@ -2324,47 +2335,41 @@ destroy_data_offer(_GLFWWaylandDataOffer *offer) { for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]); free(offer->mimes); } - memset(offer, 0, sizeof(_GLFWWaylandDataOffer)); -} - -static void prune_unclaimed_data_offers(void) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { - destroy_data_offer(&_glfw.wl.dataOffers[i]); - } - } -} - -static void mark_selection_offer(void *data UNUSED, struct wl_data_device *data_device UNUSED, struct wl_data_offer *data_offer) -{ - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == data_offer) { - _glfw.wl.dataOffers[i].offer_type = CLIPBOARD; - } else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) { - _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer - } - } - prune_unclaimed_data_offers(); -} - -static void mark_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1* primary_selection_device UNUSED, - struct zwp_primary_selection_offer_v1 *primary_selection_offer) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == primary_selection_offer) { - _glfw.wl.dataOffers[i].offer_type = PRIMARY_SELECTION; - } else if (_glfw.wl.dataOffers[i].offer_type == PRIMARY_SELECTION) { - _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer - } - } - prune_unclaimed_data_offers(); + if (offer->requested_drop_data) destroy_drop_data(offer); + memset(offer, 0, sizeof(offer[0])); } static void -set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) { +mark_data_offer(_GLFWWaylandDataOffer *ans, void *id) { + if (ans->id) destroy_data_offer(ans); + for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { + _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; + if (offer->id == id) { + *ans = *offer; + memset(offer, 0, sizeof(offer[0])); + break; + } + } +} + +static void +mark_selection_offer(void *data UNUSED, struct wl_data_device *data_device UNUSED, struct wl_data_offer *data_offer) { + mark_data_offer(&_glfw.wl.clipboard_data_offer, data_offer); +} + +static void +mark_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1* primary_selection_device UNUSED, + struct zwp_primary_selection_offer_v1 *primary_selection_offer) { + mark_data_offer(&_glfw.wl.primary_data_offer, primary_selection_offer); +} + +static void +add_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime, bool is_self_offer) { + if (is_self_offer) offer->is_self_offer = is_self_offer; if (strcmp(mime, clipboard_mime()) == 0) { offer->is_self_offer = true; } - if (!offer->mimes || offer->mimes_count >= offer->mimes_capacity - 1) { + if (!offer->mimes || offer->mimes_count + 1 >= offer->mimes_capacity) { offer->mimes = realloc(offer->mimes, sizeof(char*) * (offer->mimes_capacity + 64)); if (offer->mimes) offer->mimes_capacity += 64; else return; @@ -2372,43 +2377,42 @@ set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) { offer->mimes[offer->mimes_count++] = _glfw_strdup(mime); } -static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == id) { - set_offer_mimetype(&_glfw.wl.dataOffers[i], mime); - break; - } +static _GLFWWaylandDataOffer* +data_offer_for_id(void *id) { + for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { + _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; + if (offer->id == id) return offer; } + if (_glfw.wl.clipboard_data_offer.id == id) return &_glfw.wl.clipboard_data_offer; + if (_glfw.wl.primary_data_offer.id == id) return &_glfw.wl.primary_data_offer; + if (_glfw.wl.drop_data_offer.id == id) return &_glfw.wl.drop_data_offer; + return NULL; +} + +static void +add_generic_offer_mimetype(void *id, const char *mime, bool is_self_offer) { + _GLFWWaylandDataOffer *offer = data_offer_for_id(id); + if (offer) add_offer_mimetype(offer, mime, is_self_offer); +} + +static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) { + add_generic_offer_mimetype(id, mime, strcmp(mime, clipboard_mime()) == 0); } static void handle_primary_selection_offer_mimetype(void *data UNUSED, struct zwp_primary_selection_offer_v1* id, const char *mime) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == id) { - set_offer_mimetype((_GLFWWaylandDataOffer*)&_glfw.wl.dataOffers[i], mime); - break; - } - } + add_generic_offer_mimetype(id, mime, strcmp(mime, clipboard_mime()) == 0); } static void data_offer_source_actions(void *data UNUSED, struct wl_data_offer* id, uint32_t actions) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == id) { - _glfw.wl.dataOffers[i].source_actions = actions; - break; - } - } + _GLFWWaylandDataOffer *offer = data_offer_for_id(id); + if (offer) offer->source_actions = actions; } static void data_offer_action(void *data UNUSED, struct wl_data_offer* id, uint32_t action) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == id) { - _glfw.wl.dataOffers[i].dnd_action = action; - break; - } - } + _GLFWWaylandDataOffer *offer = data_offer_for_id(id); + if (offer) offer->dnd_action = action; } - static const struct wl_data_offer_listener data_offer_listener = { .offer = handle_offer_mimetype, .source_actions = data_offer_source_actions, @@ -2419,25 +2423,20 @@ static const struct zwp_primary_selection_offer_v1_listener primary_selection_of .offer = handle_primary_selection_offer_mimetype, }; -static size_t +static void handle_data_offer_generic(void *id, bool is_primary) { - size_t smallest_idx = SIZE_MAX, pos = 0; - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) { - smallest_idx = _glfw.wl.dataOffers[i].idx; - pos = i; - } - if (_glfw.wl.dataOffers[i].id == NULL) { - pos = i; - goto end; + for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { + _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; + if (offer->id == NULL) { + offer->id = id; + offer->is_primary = is_primary; + return; } } - if (_glfw.wl.dataOffers[pos].id) destroy_data_offer(&_glfw.wl.dataOffers[pos]); -end: - _glfw.wl.dataOffers[pos].id = id; - _glfw.wl.dataOffers[pos].is_primary = is_primary; - _glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter; - return pos; + if (is_primary) zwp_primary_selection_offer_v1_destroy(id); + else wl_data_offer_destroy(id); + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: too many untyped data offers"); } static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) { @@ -2450,8 +2449,10 @@ 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) { +// Helper function to update drop state from callback results +static void +update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t mime_count) { + bool accepted = mime_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; @@ -2475,122 +2476,164 @@ static void update_drag_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUS 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; - if (d->id == id) { - d->offer_type = DRAG_AND_DROP; - d->surface = surface; - d->serial = serial; - d->drag_accepted = false; - d->mime_for_drop = NULL; - _GLFWwindow* window = _glfw.windowListHead; - while (window) - { - if (window->wl.surface == surface) { - // Call drag enter callback with writable MIME types array - double xpos = wl_fixed_to_double(x); - double ypos = wl_fixed_to_double(y); - double scale = _glfwWaylandWindowScale(window); - int mime_count = (int)d->mimes_count; - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, scale * xpos, scale * ypos, d->mimes, &mime_count); - - // Update drag state based on callback results - update_drag_state(d, window, accepted, mime_count); - break; - } - window = window->next; - } - } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { - _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + mark_data_offer(offer, id); + if (!offer->id) return; + offer->is_self_offer = (_glfw.wl.drag.window_id != 0); + offer->surface = surface; + offer->serial = serial; + offer->drag_accepted = false; + offer->mime_for_drop = NULL; + _GLFWwindow* window = _glfw.windowListHead; + while (window) { + if (window->wl.surface == surface) { + double xpos = wl_fixed_to_double(x); + double ypos = wl_fixed_to_double(y); + double scale = _glfwWaylandWindowScale(window); + size_t mime_count = _glfwInputDropEvent( + window, GLFW_DROP_ENTER, scale * xpos, scale * ypos, + offer->mimes, offer->mimes_count, offer->is_self_offer); + update_drop_state(offer, window, mime_count); + break; } + window = window->next; } - prune_unclaimed_data_offers(); } static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { - // Find the window for this offer and call the leave callback + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (offer->id) { + _GLFWwindow* window = _glfw.windowListHead; + while (window) { + if (window->wl.surface == _glfw.wl.drop_data_offer.surface) { + _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, NULL, 0, offer->is_self_offer); + break; + } + window = window->next; + } + if (!offer->dropped) destroy_data_offer(offer); + } +} + +void +_glfwPlatformEndDrop(GLFWwindow *w UNUSED, GLFWDragOperationType op UNUSED) { + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (offer->id) destroy_data_offer(offer); +} + +ssize_t +_glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz) { + _GLFWwindow *window = (_GLFWwindow*)w; + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (!offer->id || offer->surface != window->wl.surface) return -ENOENT; + int fd = (int)ev->xpos; + for (size_t o = 0; o < offer->dd_count; o++) { + if (offer->requested_drop_data[o].fd == fd) { + ssize_t ret; + do { ret = read(fd, buffer, sz); } while (ret < 0 && errno == EINTR); + return ret < 0 ? -errno : ret; + } + } + return -ENOENT; +} + +static void +drop_data_available(int fd, int events UNUSED, void *data UNUSED) { + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (!offer->id) return; + for (size_t o = 0; o < offer->dd_count; o++) { + if (offer->requested_drop_data[o].fd == fd) { _GLFWwindow* window = _glfw.windowListHead; while (window) { - if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { - _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, NULL); - break; + if (window->wl.surface == offer->surface) { + const char *mimes[1] = {offer->requested_drop_data[o].mime}; + _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, fd, 0, mimes, 1, offer->is_self_offer); + return; } window = window->next; } - destroy_data_offer(&_glfw.wl.dataOffers[i]); + destroy_data_offer(offer); } } } +static int +request_drop_data(_GLFWWaylandDataOffer *offer, const char *mime) { + int pipefd[2]; + if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) != 0) return errno; + wl_data_offer_receive(offer->id, mime, pipefd[1]); + safe_close(pipefd[1]); + // Flush to ensure the compositor processes the receive request + wl_display_flush(_glfw.wl.display); + id_type watch_id = addWatch(&_glfw.wl.eventLoopData, "drop_data", pipefd[0], POLLIN | POLLERR | POLLHUP, true, drop_data_available, NULL); + if (!watch_id) { + safe_close(pipefd[0]); + return ERANGE; + } + char *mt = _glfw_strdup(mime); + if (!mt) { + safe_close(pipefd[0]); + removeWatch(&_glfw.wl.eventLoopData, watch_id); + return ENOMEM; + } + if (!offer->requested_drop_data || offer->dd_count + 1 >= offer->dd_capacity) { + void *p = realloc(offer->requested_drop_data, sizeof(offer->requested_drop_data[0]) * (offer->dd_capacity + 8)); + if (!p) { + safe_close(pipefd[0]); + removeWatch(&_glfw.wl.eventLoopData, watch_id); + free(mt); + return ENOMEM; + } + offer->requested_drop_data = p; + offer->dd_capacity += 64; + } + offer->requested_drop_data[offer->dd_count].mime = mt; + offer->requested_drop_data[offer->dd_count].watch_id = watch_id; + offer->requested_drop_data[offer->dd_count].fd = pipefd[0]; + offer->dd_count++; + return 0; +} + +int +_glfwPlatformRequestDropData(_GLFWwindow *window UNUSED, const char *mime) { + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (offer->id) return request_drop_data(offer, mime); + return EINVAL; +} + static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { - _GLFWWaylandDataOffer *offer = &_glfw.wl.dataOffers[i]; - - _GLFWwindow* window = _glfw.windowListHead; - while (window) { - if (window->wl.surface == offer->surface) { - // Heap-allocate drop data structure for chunked reading - // The application is responsible for freeing this via glfwFinishDrop - GLFWDropData* drop_data = calloc(1, sizeof(GLFWDropData)); - if (!drop_data) { - _glfwInputError(GLFW_OUT_OF_MEMORY, "Wayland: Failed to allocate drop data"); - destroy_data_offer(offer); // Clean up the offer on allocation failure - break; - } - // Transfer ownership of mimes array from offer to drop object - drop_data->mime_types = offer->mimes; - drop_data->mime_count = (int)offer->mimes_count; - drop_data->mime_array_size = (int)offer->mimes_count; - drop_data->current_mime = NULL; - drop_data->read_fd = -1; - drop_data->bytes_read = 0; - drop_data->platform_data = offer->id; // Store the offer for later use - offer->mimes = NULL; offer->id = NULL; - destroy_data_offer(offer); - drop_data->eof_reached = false; - memset(offer, 0, sizeof(offer[0])); - // Check if the drop is from this application - bool from_self = (_glfw.wl.drag.window_id != 0); - _glfwInputDrop(window, drop_data, from_self); - // Note: drop_data is NOT freed here - application must call glfwFinishDrop - break; - } - window = window->next; - } + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (!offer->id) return; + offer->dropped = true; + _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); + for (size_t i = 0; i < num_mimes; i++) request_drop_data(offer, offer->mimes[i]); break; } + window = window->next; } } 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++) { - _GLFWWaylandDataOffer *d = &_glfw.wl.dataOffers[i]; - if (d->offer_type == DRAG_AND_DROP) { - _GLFWwindow* window = _glfw.windowListHead; - while (window) { - if (window->wl.surface == d->surface) { - double xpos = wl_fixed_to_double(x); - double ypos = wl_fixed_to_double(y); - double scale = _glfwWaylandWindowScale(window); - // Pass the MIME types array for move events - int mime_count = (int)d->mimes_count; - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, scale * xpos, scale * ypos, d->mimes, &mime_count); - - // Update drag state based on callback results - update_drag_state(d, window, accepted, mime_count); - break; - } - window = window->next; - } + _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; + if (!offer->id) return; + _GLFWwindow* window = _glfw.windowListHead; + while (window) { + if (window->wl.surface == offer->surface) { + double xpos = wl_fixed_to_double(x); + double ypos = wl_fixed_to_double(y); + double scale = _glfwWaylandWindowScale(window); + size_t mime_count = _glfwInputDropEvent( + window, GLFW_DROP_MOVE, scale * xpos, scale * ypos, offer->mimes, offer->mimes_count, offer->is_self_offer); + update_drop_state(offer, window, mime_count); break; } + window = window->next; } } @@ -2607,7 +2650,7 @@ static const struct zwp_primary_selection_device_v1_listener primary_selection_d .data_offer = handle_primary_selection_offer, .selection = mark_primary_selection_offer, }; - +// }}} void _glfwSetupWaylandDataDevice(void) { _glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat); @@ -2744,39 +2787,34 @@ plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) { void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { - _GLFWWaylandOfferType offer_type = clipboard_type == GLFW_PRIMARY_SELECTION ? PRIMARY_SELECTION : CLIPBOARD; - for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; - if (d->id && d->offer_type == offer_type) { - if (d->is_self_offer) { - write_data(object, NULL, 1); - return; - } - if (mime_type == NULL) { - bool ok = true; - for (size_t o = 0; o < d->mimes_count; o++) { - const char *q = d->mimes[o]; - if (strchr(d->mimes[0], '/')) { - if (strcmp(q, clipboard_mime()) == 0) continue; - if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain"; - } else { - if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain"; - } - if (ok) ok = write_data(object, q, strlen(q)); - } - return; - } - if (strcmp(mime_type, "text/plain") == 0) { - mime_type = plain_text_mime_for_offer(d); - if (!mime_type) return; - } - if (d->is_primary) { - read_primary_selection_offer(d->id, mime_type, write_data, object); + _GLFWWaylandDataOffer *d = clipboard_type == GLFW_PRIMARY_SELECTION ? &_glfw.wl.primary_data_offer : &_glfw.wl.clipboard_data_offer; + if (!d->id) return; + if (d->is_self_offer) { + write_data(object, NULL, 1); + return; + } + if (mime_type == NULL) { + bool ok = true; + for (size_t o = 0; o < d->mimes_count; o++) { + const char *q = d->mimes[o]; + if (strchr(d->mimes[0], '/')) { + if (strcmp(q, clipboard_mime()) == 0) continue; + if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain"; } else { - read_clipboard_data_offer(d->id, mime_type, write_data, object); + if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain"; } - break; + if (ok) ok = write_data(object, q, strlen(q)); } + return; + } + if (strcmp(mime_type, "text/plain") == 0) { + mime_type = plain_text_mime_for_offer(d); + if (!mime_type) return; + } + if (d->is_primary) { + read_primary_selection_offer(d->id, mime_type, write_data, object); + } else { + read_clipboard_data_offer(d->id, mime_type, write_data, object); } } @@ -3312,118 +3350,14 @@ _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, siz } void -_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) { - // 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; - } +_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); + update_drop_state(d, window, mime_count); } } -const char** -_glfwPlatformGetDropMimeTypes(GLFWDropData* drop, int* count) { - if (!drop || !count) return NULL; - *count = drop->mime_count; - return drop->mime_types; -} - -ssize_t -_glfwPlatformReadDropData(GLFWDropData* drop, const char* mime, void* buffer, size_t capacity, monotonic_t timeout) { - if (!drop->platform_data) return -EINVAL; - - // Check if the MIME type is available - bool mime_found = false; - for (int i = 0; i < drop->mime_count; i++) { - if (drop->mime_types[i] && strcmp(drop->mime_types[i], mime) == 0) { - mime_found = true; - break; - } - } - if (!mime_found) return -ENOENT; - - // If switching MIME types, close the previous file descriptor - if (drop->current_mime && strcmp(drop->current_mime, mime) != 0) { - if (drop->read_fd >= 0) { - close(drop->read_fd); - drop->read_fd = -1; - } - drop->bytes_read = 0; - drop->eof_reached = false; - free(drop->current_mime); drop->current_mime = NULL; - } - - // If we've reached EOF for this MIME type, return 0 - if (drop->eof_reached && drop->current_mime && strcmp(drop->current_mime, mime) == 0) return 0; - - // Open a new pipe if we don't have one for this MIME type - if (drop->read_fd < 0 || drop->current_mime == NULL) { - int pipefd[2]; - if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) != 0) return -EIO; - wl_data_offer_receive(drop->platform_data, mime, pipefd[1]); - close(pipefd[1]); - // Flush to ensure the compositor processes the receive request - wl_display_flush(_glfw.wl.display); - drop->read_fd = pipefd[0]; - // mime points to drop->mime_types entry, valid for duration of drop callback - drop->current_mime = _glfw_strdup(mime); - drop->bytes_read = 0; - drop->eof_reached = false; - } - - // Wait for data with timeout using poll - monotonic_t start = monotonic(); - while (true) { - struct pollfd pfd = { .fd = drop->read_fd, .events = POLLIN }; - - monotonic_t remaining = timeout - (monotonic() - start); - if (timeout > 0 && remaining <= 0) return -ETIME; - - int poll_timeout_ms = (timeout <= 0) ? 0 : monotonic_t_to_ms(remaining); - if (poll_timeout_ms < 1 && timeout > 0) poll_timeout_ms = 1; - - int ret = poll(&pfd, 1, poll_timeout_ms); - if (ret < 0) { - if (errno == EINTR) continue; - return -EIO; - } - if (ret == 0) { - if (timeout <= 0) { - // Non-blocking mode: no data available yet, try reading anyway - break; - } - return -ETIME; - } - break; // Data available - } - - // Read data from the pipe - ssize_t bytes_read = -1; - errno = EINTR; - while (errno == EINTR) { errno = 0; bytes_read = read(drop->read_fd, buffer, capacity); } - if (bytes_read < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - return -ETIME; // No data available yet - } - return -EIO; - } - if (bytes_read == 0) { - // EOF reached - drop->eof_reached = true; - return 0; - } - drop->bytes_read += bytes_read; - return bytes_read; -} - void _glfwPlatformFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation UNUSED, bool success UNUSED) { if (!drop) return; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 019fcafc7..a482a3471 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -2063,9 +2063,9 @@ static void processEvent(XEvent *event) drop_data->x11_version = _glfw.x11.xdnd.version; // Check if the drop is from this application - bool from_self = (_glfw.x11.drag.source_window != None && - _glfw.x11.xdnd.source == _glfw.x11.drag.source_window); - _glfwInputDrop(window, drop_data, from_self); + // bool from_self = (_glfw.x11.drag.source_window != None && + // _glfw.x11.xdnd.source == _glfw.x11.drag.source_window); + // _glfwInputDrop(window, drop_data, from_self); // Note: drop_data is NOT freed here - application must call glfwFinishDrop } @@ -2276,6 +2276,16 @@ static void processEvent(XEvent *event) ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// +ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz) { + (void) w; (void) ev; (void) buffer; (void)sz; return -EINVAL; +} +void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op) { + (void)w; (void)op; +} +int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime) { + (void)window; (void)mime; return EINVAL; +} +void _glfwPlatformRequestDropUpdate(_GLFWwindow* window) { (void)window; } // Retrieve a single window property of the specified type // Inspired by fghGetWindowProperty from freeglut // diff --git a/kitty/boss.py b/kitty/boss.py index 6e3fd0745..73244c76b 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1885,7 +1885,7 @@ class Boss: if tm is not None: tm.update_tab_bar_data() - def on_drop(self, os_window_id: int, drop: dict[str, bytes] | Exception, x: int, y: int) -> None: + def on_drop(self, os_window_id: int, drop: dict[str, bytes] | Exception, from_self: bool, x: int, y: int) -> None: if isinstance(drop, Exception): self.show_error(_('Drop failed'), str(drop)) return diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 40042820d..e81f67e24 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -359,8 +359,14 @@ load_glfw(const char* path) { *(void **) (&glfwSetLiveResizeCallback_impl) = dlsym(handle, "glfwSetLiveResizeCallback"); if (glfwSetLiveResizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetLiveResizeCallback with error: %s", dlerror()); - *(void **) (&glfwSetDragCallback_impl) = dlsym(handle, "glfwSetDragCallback"); - if (glfwSetDragCallback_impl == NULL) fail("Failed to load glfw function glfwSetDragCallback with error: %s", dlerror()); + *(void **) (&glfwSetDropEventCallback_impl) = dlsym(handle, "glfwSetDropEventCallback"); + if (glfwSetDropEventCallback_impl == NULL) fail("Failed to load glfw function glfwSetDropEventCallback with error: %s", dlerror()); + + *(void **) (&glfwRequestDropData_impl) = dlsym(handle, "glfwRequestDropData"); + if (glfwRequestDropData_impl == NULL) fail("Failed to load glfw function glfwRequestDropData with error: %s", dlerror()); + + *(void **) (&glfwEndDrop_impl) = dlsym(handle, "glfwEndDrop"); + if (glfwEndDrop_impl == NULL) fail("Failed to load glfw function glfwEndDrop with error: %s", dlerror()); *(void **) (&glfwSetDragSourceCallback_impl) = dlsym(handle, "glfwSetDragSourceCallback"); if (glfwSetDragSourceCallback_impl == NULL) fail("Failed to load glfw function glfwSetDragSourceCallback with error: %s", dlerror()); @@ -371,18 +377,6 @@ load_glfw(const char* path) { *(void **) (&glfwSendDragData_impl) = dlsym(handle, "glfwSendDragData"); if (glfwSendDragData_impl == NULL) fail("Failed to load glfw function glfwSendDragData 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 **) (&glfwGetDropMimeTypes_impl) = dlsym(handle, "glfwGetDropMimeTypes"); - if (glfwGetDropMimeTypes_impl == NULL) fail("Failed to load glfw function glfwGetDropMimeTypes with error: %s", dlerror()); - - *(void **) (&glfwReadDropData_impl) = dlsym(handle, "glfwReadDropData"); - if (glfwReadDropData_impl == NULL) fail("Failed to load glfw function glfwReadDropData with error: %s", dlerror()); - - *(void **) (&glfwFinishDrop_impl) = dlsym(handle, "glfwFinishDrop"); - if (glfwFinishDrop_impl == NULL) fail("Failed to load glfw function glfwFinishDrop 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()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index bae5f4b43..073474364 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1112,6 +1112,35 @@ typedef struct GLFWDBUSNotificationData { int32_t timeout; uint8_t urgency; uint32_t replaces; int muted; } GLFWDBUSNotificationData; +typedef enum { GLFW_DROP_ENTER, GLFW_DROP_MOVE, GLFW_DROP_LEAVE, GLFW_DROP_DROP, GLFW_DROP_STATUS_UPDATE, GLFW_DROP_DATA_AVAILABLE } GLFWDropEventType; + +/*! @brief Drag operation types. + * + * These constants specify the type of drag operation (copy, move, or generic). + * + * @ingroup input + */ +typedef enum { + /*! Move the dragged data to the destination. */ + GLFW_DRAG_OPERATION_MOVE = 1, + /*! Copy the dragged data to the destination. */ + GLFW_DRAG_OPERATION_COPY = 2, + /*! Generic drag operation (platform decides semantics). */ + GLFW_DRAG_OPERATION_GENERIC = 4 +} GLFWDragOperationType; + + +typedef struct GLFWDropEvent { + GLFWDropEventType type; + const char **mimes; size_t num_mimes; + double xpos, ypos; // Only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE + bool from_self; + ssize_t (*read_data)(GLFWwindow *w, struct GLFWDropEvent* ev, char *buffer, size_t sz); // Only valid for GLFW_DROP_DATA_AVAILABLE + void (*finish_drop)(GLFWwindow *w, GLFWDragOperationType op); // Only valid for GLFW_DROP_DROP and GLFW_DROP_DATA_AVAILABLE +} GLFWDropEvent; +typedef void (* GLFWdropeventfun)(GLFWwindow*, GLFWDropEvent *event); + + /*! @brief The function pointer type for error callbacks. * * This is the function pointer type for error callbacks. An error callback @@ -1528,6 +1557,12 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * responsible for calling glfwFinishDrop when it has finished reading * the dropped data, even if reading fails or is not needed. * + * @param[in] window The window that received the drop. + * @param[in] drop Opaque drop data pointer (heap-allocated). + * @param[in] from_self true if the drop originated from this application + * (i.e., the application is both the drag source and drop target), false + * if the drop came from an external application. + * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * @sa @ref glfwGetDropMimeTypes @@ -1558,21 +1593,6 @@ typedef enum { GLFW_DRAG_STATUS_UPDATE = 4 } GLFWDragEventType; -/*! @brief Drag operation types. - * - * These constants specify the type of drag operation (copy, move, or generic). - * - * @ingroup input - */ -typedef enum { - /*! Move the dragged data to the destination. */ - GLFW_DRAG_OPERATION_MOVE = 1, - /*! Copy the dragged data to the destination. */ - GLFW_DRAG_OPERATION_COPY = 2, - /*! Generic drag operation (platform decides semantics). */ - GLFW_DRAG_OPERATION_GENERIC = 4 -} GLFWDragOperationType; - /*! @brief Opaque drag source data handle. * * This is an opaque handle to a heap-allocated object that represents @@ -2327,9 +2347,17 @@ typedef GLFWliveresizefun (*glfwSetLiveResizeCallback_func)(GLFWwindow*, GLFWliv GFW_EXTERN glfwSetLiveResizeCallback_func glfwSetLiveResizeCallback_impl; #define glfwSetLiveResizeCallback glfwSetLiveResizeCallback_impl -typedef GLFWdragfun (*glfwSetDragCallback_func)(GLFWwindow*, GLFWdragfun); -GFW_EXTERN glfwSetDragCallback_func glfwSetDragCallback_impl; -#define glfwSetDragCallback glfwSetDragCallback_impl +typedef GLFWdropeventfun (*glfwSetDropEventCallback_func)(GLFWwindow*, GLFWdropeventfun); +GFW_EXTERN glfwSetDropEventCallback_func glfwSetDropEventCallback_impl; +#define glfwSetDropEventCallback glfwSetDropEventCallback_impl + +typedef int (*glfwRequestDropData_func)(GLFWwindow*, const char*); +GFW_EXTERN glfwRequestDropData_func glfwRequestDropData_impl; +#define glfwRequestDropData glfwRequestDropData_impl + +typedef void (*glfwEndDrop_func)(GLFWwindow*, GLFWDragOperationType); +GFW_EXTERN glfwEndDrop_func glfwEndDrop_impl; +#define glfwEndDrop glfwEndDrop_impl typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdragsourcefun); GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl; @@ -2343,22 +2371,6 @@ typedef ssize_t (*glfwSendDragData_func)(GLFWDragSourceData*, const void*, size_ GFW_EXTERN glfwSendDragData_func glfwSendDragData_impl; #define glfwSendDragData glfwSendDragData_impl -typedef void (*glfwUpdateDragState_func)(GLFWwindow*); -GFW_EXTERN glfwUpdateDragState_func glfwUpdateDragState_impl; -#define glfwUpdateDragState glfwUpdateDragState_impl - -typedef const char** (*glfwGetDropMimeTypes_func)(GLFWDropData*, int*); -GFW_EXTERN glfwGetDropMimeTypes_func glfwGetDropMimeTypes_impl; -#define glfwGetDropMimeTypes glfwGetDropMimeTypes_impl - -typedef ssize_t (*glfwReadDropData_func)(GLFWDropData*, const char*, void*, size_t, monotonic_t); -GFW_EXTERN glfwReadDropData_func glfwReadDropData_impl; -#define glfwReadDropData glfwReadDropData_impl - -typedef void (*glfwFinishDrop_func)(GLFWDropData*, GLFWDragOperationType, bool); -GFW_EXTERN glfwFinishDrop_func glfwFinishDrop_impl; -#define glfwFinishDrop glfwFinishDrop_impl - typedef int (*glfwJoystickPresent_func)(int); GFW_EXTERN glfwJoystickPresent_func glfwJoystickPresent_impl; #define glfwJoystickPresent glfwJoystickPresent_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index ae3a35149..6f79cdf80 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -649,113 +649,106 @@ is_droppable_mime(const char *mime) { 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; - int ret = 0; - switch (event) { - case GLFW_DRAG_ENTER: - case GLFW_DRAG_MOVE: - global_state.callback_os_window->last_drag_event.x = (int)xpos; - global_state.callback_os_window->last_drag_event.y = (int)ypos; +static void +update_allowed_mimes_for_drop(GLFWDropEvent *ev) { + if (ev->mimes && ev->num_mimes) { + // Sort MIME types by priority (descending) and keep only accepted ones + // Use simple bubble sort since lists are typically small + size_t new_count = 0; + // Use stack-allocated array for priorities (count is typically small) + int priorities[32]; + int* prio_arr = (ev->num_mimes <= (int)arraysz(priorities)) ? priorities : (int*)malloc(ev->num_mimes * sizeof(int)); + if (!prio_arr) return; + // First pass: filter droppable MIME types and cache priorities + for (size_t i = 0; i < ev->num_mimes; i++) { + int prio = is_droppable_mime(ev->mimes[i]); + if (prio > 0) { + // Move this mime to the new_count position + if (new_count != i) { SWAP(ev->mimes[i], ev->mimes[new_count]); } + prio_arr[new_count] = prio; + new_count++; + } + } + // Second pass: sort by cached priorities (descending) + for (size_t i = 0; i < new_count - 1; i++) { + for (size_t j = i + 1; j < new_count; j++) { + if (prio_arr[j] > prio_arr[i]) { + SWAP(ev->mimes[i], ev->mimes[j]); + SWAP(prio_arr[i], prio_arr[j]); + } + } + } + if (prio_arr != priorities) free(prio_arr); + ev->num_mimes = new_count; + } +} + +static void +read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) { + RAII_PyObject(chunk, PyBytes_FromStringAndSize(NULL, 8192)); +#define finish(ok) ev->finish_drop(window, ok ? GLFW_DRAG_OPERATION_COPY : GLFW_DRAG_OPERATION_GENERIC); Py_CLEAR(global_state.drop_dest.data) + if (!chunk) { finish(false); return; } + ssize_t ret = ev->read_data(window, ev, PyBytes_AS_STRING(chunk), PyBytes_GET_SIZE(chunk)); + if (ret == 0) { + global_state.drop_dest.num_left--; + if (!global_state.drop_dest.num_left) { + WINDOW_CALLBACK(on_drop, "OOii", global_state.drop_dest.data, Py_False, + global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); + finish(true); + } + } else if (ret > 0) { + _PyBytes_Resize(&chunk, ret); + PyObject *data = chunk; + RAII_PyObject(existing, PyDict_GetItemString(global_state.drop_dest.data, ev->mimes[0])); + if (existing) { + PyBytes_Concat(&existing, chunk); + data = existing; + } + if (!data || PyDict_SetItemString(global_state.drop_dest.data, ev->mimes[0], data) != 0) { finish(false); } + } else { + int posix_errno = -ret; + WINDOW_CALLBACK(on_drop, "iOii", posix_errno, Py_False, + global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); + finish(false); + } +#undef finish +} + +static void +on_drop(GLFWwindow *window, GLFWDropEvent *ev) { + if (!set_callback_window(window)) return; + switch (ev->type) { + case GLFW_DROP_ENTER: + case GLFW_DROP_MOVE: + global_state.callback_os_window->last_drag_event.x = (int)ev->xpos; + global_state.callback_os_window->last_drag_event.y = (int)ev->ypos; /* fallthrough */ - 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[32]; - int* prio_arr = (count <= (int)arraysz(priorities)) ? priorities : (int*)malloc(count * sizeof(int)); - if (!prio_arr) goto end; - // 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) { SWAP(mime_types[i], mime_types[new_count]); } - 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]) { - SWAP(mime_types[i], mime_types[j]); - SWAP(prio_arr[i], prio_arr[j]); - } - } - } - if (prio_arr != priorities) free(prio_arr); - *mime_count = new_count; - ret = (new_count > 0) ? 1 : 0; + case GLFW_DROP_STATUS_UPDATE: + update_allowed_mimes_for_drop(ev); + break; + case GLFW_DROP_LEAVE: + break; + case GLFW_DROP_DROP: + Py_CLEAR(global_state.drop_dest.data); + if (ev->from_self) { + if (global_state.drag_source.drag_data) { + WINDOW_CALLBACK(on_drop, "OOii", global_state.drag_source.drag_data, Py_True, + global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); + } else log_error("Got a drop from self but drag_source.drag_data is NULL"); + ev->finish_drop(window, GLFW_DRAG_OPERATION_COPY); + break; + } + update_allowed_mimes_for_drop(ev); + global_state.drop_dest.num_left = ev->num_mimes; + if (!global_state.drop_dest.num_left || !(global_state.drop_dest.data = PyDict_New())) { + ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); } break; - case GLFW_DRAG_LEAVE: - global_state.callback_os_window->last_drag_event.x = (int)xpos; - global_state.callback_os_window->last_drag_event.y = (int)ypos; + case GLFW_DROP_DATA_AVAILABLE: + if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); + else read_drop_data(window, ev); break; } -end: - global_state.callback_os_window = NULL; - return ret; -} - -static PyObject* -read_drop_data(GLFWDropData *drop, const char *mime) { - RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, 8192)); - if (!ans) return NULL; - size_t pos = 0; - monotonic_t timeout = s_double_to_monotonic_t(2); - while (true) { - int ret = glfwReadDropData(drop, mime, PyBytes_AS_STRING(ans) + pos, PyBytes_GET_SIZE(ans) - pos, timeout); - if (ret > 0) { - pos += ret; - if (pos >= (size_t)PyBytes_GET_SIZE(ans)) { - if (_PyBytes_Resize(&ans, pos * 2) != 0) return NULL; - } - } else if (ret == 0) { - if (_PyBytes_Resize(&ans, pos) != 0) return NULL; - return Py_NewRef(ans); - } else { - errno = -ret; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - } -} - -static void -get_mime_data(GLFWDropData *drop, const char **mimes, int mime_count, PyObject *ans) { - for (int i = 0; i < mime_count; i++) { - if (is_droppable_mime(mimes[i])) { - RAII_PyObject(data, read_drop_data(drop, mimes[i])); - if (data == NULL) return; - if (PyDict_SetItemString(ans, mimes[i], data) != 0) return; - } - } -} - -static void -drop_callback(GLFWwindow *w, GLFWDropData *drop, bool from_self) { - int num_mimes; - const char** mimes = glfwGetDropMimeTypes(drop, &num_mimes); - RAII_PyObject(ans, PyDict_New()); - if (from_self) { - if (global_state.drag_source.drag_data) PyDict_Update(ans, global_state.drag_source.drag_data); - else log_error("Got a drop from self but drag_source.drag_data is NULL"); - } else get_mime_data(drop, mimes, num_mimes, ans); - RAII_PyObject(exc, PyErr_GetRaisedException()); - glfwFinishDrop(drop, GLFW_DRAG_OPERATION_COPY, true); - if (!set_callback_window(w)) return; - if (exc != NULL) { WINDOW_CALLBACK(on_drop, "Oii", exc, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); } - else if (PyDict_Size(ans)) WINDOW_CALLBACK(on_drop, "Oii", ans, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); - request_tick_callback(); - global_state.callback_os_window = NULL; } static void @@ -1675,9 +1668,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { glfwSetCursorEnterCallback(glfw_window, cursor_enter_callback); glfwSetScrollCallback(glfw_window, scroll_callback); glfwSetKeyboardCallback(glfw_window, key_callback); - glfwSetDropCallback(glfw_window, drop_callback); - glfwSetDragCallback(glfw_window, drag_callback); - glfwSetDragSourceCallback(glfw_window, drag_source_callback); + + if (0) glfwSetDragSourceCallback(glfw_window, drag_source_callback); + + glfwSetDropEventCallback(glfw_window, on_drop); monotonic_t now = monotonic(); w->is_focused = true; w->cursor_blink_zero_time = now; diff --git a/kitty/state.c b/kitty/state.c index 448c1607c..6f502fe6f 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1584,6 +1584,8 @@ finalize(void) { free(global_state.drag_source.ongoing_transfers); Py_CLEAR(global_state.drag_source.drag_data); zero_at_ptr(&global_state.drag_source); + Py_CLEAR(global_state.drop_dest.data); + zero_at_ptr(&global_state.drop_dest); free_allocs_in_options(&global_state.opts); } diff --git a/kitty/state.h b/kitty/state.h index 7db06b635..b603cf30e 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -375,6 +375,13 @@ typedef struct GlobalState { bool supports_framebuffer_srgb; PyObject *options_object; + struct { + PyObject *data; + id_type os_window_id; + double x, y; + size_t num_left; + } drop_dest; + struct { bool is_active; PyObject *drag_data;