Refactor drop API and implement it on Wayland

This commit is contained in:
Kovid Goyal 2026-02-08 13:34:00 +05:30
parent 0ea65903b8
commit 3cf24afdc0
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
14 changed files with 516 additions and 682 deletions

View file

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

204
glfw/glfw3.h vendored
View file

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

60
glfw/input.c vendored
View file

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

17
glfw/internal.h vendored
View file

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

9
glfw/wl_init.c vendored
View file

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

23
glfw/wl_platform.h vendored
View file

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

550
glfw/wl_window.c vendored
View file

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

16
glfw/x11_window.c vendored
View file

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

View file

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

22
kitty/glfw-wrapper.c generated
View file

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

80
kitty/glfw-wrapper.h generated
View file

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

View file

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

View file

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

View file

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