Refactor drag source API to be async

Wayland implementation done. Other two backends remain.
This commit is contained in:
Kovid Goyal 2026-02-16 10:03:33 +05:30
parent 76a29273c8
commit e5eb63fcd0
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
14 changed files with 351 additions and 876 deletions

View file

@ -4099,20 +4099,11 @@ void _glfwCocoaPostEmptyEvent(void) {
[NSApp postEvent:event atStart:YES];
}
void _glfwPlatformCancelDrag(_GLFWwindow* window) {
// Clean up all pending drag source data
cleanup_all_ns_pending_drag_source_data(window);
// Notify the application that the drag source is closed
_glfwInputDragSourceRequest(window, NULL, NULL);
}
int _glfwPlatformStartDrag(_GLFWwindow* window,
const char* const* mime_types,
int mime_count,
const GLFWimage* thumbnail,
int operations) {
int
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWDragSourceItem *items, int item_count, const GLFWimage* thumbnail, int operations) {
// cleanup stored data from previous drag
cleanup_all_ns_pending_drag_source_data(window);
if (!items || !item_count) { cleanup_all_ns_pending_drag_source_data(window); return 0; }
// Store the operations for the dragging source callback
window->ns.dragOperations = operations;
@ -4168,11 +4159,6 @@ int _glfwPlatformStartDrag(_GLFWwindow* window,
[dragItems addObject:dragItem];
}
if (dragItems.count == 0) {
_glfwPlatformCancelDrag(window);
return EINVAL;
}
// Start the drag session - try current event first, then create a synthetic one
NSEvent* event = [NSApp currentEvent];
if (!event || ([event type] != NSEventTypeLeftMouseDown &&

167
glfw/glfw3.h vendored
View file

@ -1811,57 +1811,30 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,const GLFWScrollEvent*);
*/
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
/*! @brief Drag event types.
*
* These constants are used to identify the type of drag event.
*
* @ingroup input
*/
typedef enum {
/*! The drag operation entered the window. */
GLFW_DRAG_ENTER = 1,
/*! The drag operation moved within the window. */
GLFW_DRAG_MOVE = 2,
/*! The drag operation left the window. */
GLFW_DRAG_LEAVE = 3,
/*! Async status update request (xpos/ypos are invalid). */
GLFW_DRAG_STATUS_UPDATE = 4
GLFW_DRAG_DATA_REQUEST,
GLFW_DRAG_CANCELLED,
GLFW_DRAG_FINSHED,
GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified
GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType
GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet
} GLFWDragEventType;
/*! @brief Opaque drag source data handle.
*
* This is an opaque handle to a heap-allocated object that represents
* data being requested from a drag source. The lifetime is managed by
* the GLFW backend - it is freed on end of data, error, drag source
* cancellation, or at exit.
*
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef struct GLFWDragSourceData GLFWDragSourceData;
typedef struct GLFWDragSourceItem {
const char *mime_type;
// Can be on null to provide data when the drag is started should be used only when the data is relatively small
const char *optional_data;
size_t data_size;
} GLFWDragSourceItem;
/*! @brief The function pointer type for drag source data request callbacks.
*
* This is the function pointer type for callbacks invoked when the OS
* requests data for a specific MIME type from the active drag source.
* The callback is called on the GUI thread.
*
* @param[in] window The window that initiated the drag.
* @param[in] mime_type The MIME type being requested, or NULL if the OS
* has closed the drag source.
* @param[in] source_data Opaque pointer to a heap-allocated object. Use this
* pointer when calling @ref glfwSendDragData to send data chunks.
*
* @sa @ref glfwStartDrag
* @sa @ref glfwSendDragData
* @sa @ref glfwSetDragSourceCallback
*
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef void (* GLFWdragsourcefun)(GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data);
typedef struct GLFWDragEvent {
GLFWDragEventType type;
const char *mime_type;
const char *data; size_t data_sz; int err_num;
GLFWDragOperationType action; // can be 0 indicating no action
} GLFWDragEvent;
typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev);
/*! @brief The function pointer type for drag event callbacks.
*
@ -4999,106 +4972,8 @@ GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow *window, GLFWdropev
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.
*
* This function sets the callback that is invoked when the OS requests data
* for a specific MIME type from the currently active drag source. The callback
* receives the MIME type and an opaque pointer to a heap-allocated object.
* The application should call @ref glfwSendDragData with chunks of data.
*
* If the callback is called with a NULL mime_type, the OS has closed the
* drag source.
*
* @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).
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref glfwStartDrag
* @sa @ref glfwSendDragData
*
* @since Added in version 4.0.
*
* @ingroup input
*/
GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* window, GLFWdragsourcefun callback);
/*! @brief Starts a drag operation with lazy data loading.
*
* This function starts a drag operation from the specified window. Data for
* each MIME type is loaded on demand when the OS requests it via the drag
* source callback set with @ref glfwSetDragSourceCallback.
*
* Calling with NULL mime_types or mime_count of 0 cancels the currently
* active drag source, if any. Similarly, when called with mime types, any
* currently active drag is canceled and replaced.
*
* @param[in] window The window initiating the drag.
* @param[in] mime_types Array of MIME type strings.
* @param[in] mime_count Number of MIME types in the array.
* @param[in] thumbnail Optional thumbnail/icon image to display during the
* drag operation, or `NULL` for no thumbnail. The image data is copied.
* @param[in] operations A bitfield containing ORed values from
* @ref GLFWDragOperationType specifying allowed operations.
*
* @return Zero on success, or a POSIX error code such as EINVAL or EIO on
* failure.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref drag_start
* @sa @ref glfwSetDragSourceCallback
* @sa @ref glfwSendDragData
*
* @since Added in version 4.0.
*
* @ingroup input
*/
GLFWAPI int glfwStartDrag(GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations);
/*! @brief Sends a chunk of drag data.
*
* This function is called by the application on the GUI thread to send chunks
* of data for a drag operation. Call this in response to the drag source
* callback. This function is non-blocking and may return before all data
* is written to the destination.
*
* End of data is indicated by calling with NULL data pointer and size zero.
* If an error occurs while reading data, call with NULL data pointer and
* size set to a POSIX error code.
*
* @param[in] source_data The opaque pointer received in the drag source callback.
* @param[in] data Pointer to the data chunk, or NULL to signal end of data or error.
* @param[in] size Size of the data chunk in bytes, or 0 for end of data,
* or a POSIX error code when data is NULL.
*
* @return The number of bytes sent (which may be less than size if the
* operation would block), or a negative POSIX error code on failure.
* For end-of-data or error signaling (NULL data), returns 0 on success
* or a negative error code.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref glfwStartDrag
* @sa @ref glfwSetDragSourceCallback
*
* @since Added in version 4.0.
*
* @ingroup input
*/
GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size);
GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWDragSourceItem *items, size_t mime_count, const GLFWimage* thumbnail, int operations);
/*! @brief Returns whether the specified joystick is present.
*

84
glfw/input.c vendored
View file

@ -402,15 +402,6 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered)
window->callbacks.cursorEnter((GLFWwindow*) window, entered);
}
// Notifies shared code of a drag event
//
int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count)
{
if (window->callbacks.drag)
return window->callbacks.drag((GLFWwindow*) window, event, xpos, ypos, mime_types, mime_count);
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;
@ -425,10 +416,9 @@ size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double x
// 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)
{
if (window->callbacks.dragSource)
window->callbacks.dragSource((GLFWwindow*) window, mime_type, source_data);
void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev) {
if (window->callbacks.drag_source)
window->callbacks.drag_source((GLFWwindow*) window, ev);
}
// Notifies shared code of a joystick connection or disconnection
@ -1134,16 +1124,6 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle,
return cbfun;
}
GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* handle, GLFWdragfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.drag, cbfun);
return cbfun;
}
GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow* handle, GLFWdropeventfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
@ -1161,31 +1141,51 @@ GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* handle, GLFWdrag
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.dragSource, cbfun);
_GLFW_SWAP_POINTERS(window->callbacks.drag_source, cbfun);
return cbfun;
}
GLFWAPI int glfwStartDrag(GLFWwindow* handle, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(EINVAL);
// If no mime types, cancel any existing drag
if (!mime_types || mime_count <= 0) {
_glfwPlatformCancelDrag(window);
return 0;
void
_glfwFreeDragSourceData(void) {
_glfwPlatformFreeDragSourceData();
if (_glfw.drag.items) {
for (size_t i = 0; i < _glfw.drag.item_count; i++) {
free((void*)_glfw.drag.items[i].mime_type);
free((void*)_glfw.drag.items[i].optional_data);
}
free(_glfw.drag.items);
}
return _glfwPlatformStartDrag(window, mime_types, mime_count, thumbnail, operations);
memset(&_glfw.drag, 0, sizeof(_glfw.drag));
}
GLFWAPI ssize_t glfwSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size)
{
if (!source_data) return -EINVAL;
_GLFW_REQUIRE_INIT_OR_RETURN(-EINVAL);
return _glfwPlatformSendDragData(source_data, data, size);
GLFWAPI int
glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_count, const GLFWimage* thumbnail, int operations) {
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(EINVAL);
if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type);
_glfwFreeDragSourceData();
if (!items || !item_count) return 0;
_glfw.drag.items = calloc(item_count, sizeof(_glfw.drag.items[0]));
if (!_glfw.drag.items) return ENOMEM;
_glfw.drag.item_count = item_count;
for (size_t i = 0; i < item_count; i++) {
if (!items[i].mime_type || !items[i].mime_type[0]) {
_glfwFreeDragSourceData(); return EINVAL;
}
_glfw.drag.items[i].mime_type = _glfw_strdup(items[i].mime_type);
if (!_glfw.drag.items[i].mime_type) { _glfwFreeDragSourceData(); return ENOMEM; }
if (items[i].optional_data) {
_glfw.drag.items[i].optional_data = malloc(items[i].data_size);
if (!_glfw.drag.items[i].optional_data) { _glfwFreeDragSourceData(); return ENOMEM; }
memcpy((void*)_glfw.drag.items[i].optional_data, items[i].optional_data, items[i].data_size);
}
_glfw.drag.items[i].data_size = items[i].data_size;
}
_glfw.drag.window_id = window->id;
int ans = _glfwPlatformStartDrag(window, thumbnail, operations);
if (ans != 0) _glfwFreeDragSourceData();
return ans;
}
GLFWAPI int glfwJoystickPresent(int jid)

35
glfw/internal.h vendored
View file

@ -85,17 +85,6 @@ typedef struct _GLFWjoystick _GLFWjoystick;
typedef struct _GLFWtls _GLFWtls;
typedef struct _GLFWmutex _GLFWmutex;
// Drag source data structure for chunked writing of drag data
// Lifetime is managed by the backend - freed on end of data, error, drag cancellation, or exit
struct GLFWDragSourceData {
GLFWid window_id; // ID of window that initiated the drag (use _glfwWindowForId to get pointer)
char* mime_type; // MIME type being sent (owned, copied from request)
int write_fd; // File descriptor for writing data (Wayland/X11), -1 if not used
bool finished; // Whether data sending is complete (EOF or error)
int error_code; // POSIX error code if an error occurred, 0 otherwise
void* platform_data; // Platform-specific data
};
typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*);
typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*);
typedef void (* _GLFWswapintervalfun)(int);
@ -491,9 +480,7 @@ struct _GLFWwindow
GLFWkeyboardfun keyboard;
GLFWliveresizefun liveResize;
GLFWdragfun drag;
GLFWdragsourcefun dragSource;
GLFWdragsourcefun drag_source;
GLFWdropeventfun drop_event;
} callbacks;
@ -677,6 +664,11 @@ struct _GLFWlibrary
_GLFWlibraryEGL egl;
// This is defined in osmesa_context.h
_GLFWlibraryOSMesa osmesa;
struct {
GLFWDragSourceItem *items; size_t item_count;
GLFWid window_id;
} drag;
};
// Global state shared between compilation units of GLFW
@ -781,10 +773,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev);
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 _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void);
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
@ -837,16 +825,19 @@ void _glfwInputScroll(_GLFWwindow* window, const GLFWScrollEvent *ev);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
int _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
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);
// Platform functions for drag source
int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations);
void _glfwFreeDragSourceData(void);
void _glfwPlatformFreeDragSourceData(void);
void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev);
int _glfwPlatformDragDataReady(const char *mime_type);
void _glfwInputColorScheme(GLFWColorScheme, bool);
void _glfwPlatformInputColorScheme(GLFWColorScheme);

16
glfw/wl_platform.h vendored
View file

@ -406,17 +406,19 @@ typedef struct _GLFWlibraryWayland
bool has_preferred_buffer_scale;
char *compositor_name;
// Drag operation state
// Drag source state
struct {
struct wl_data_source* source;
char** mimes; // Array of MIME type strings
int mime_count; // Number of MIME types
GLFWid window_id; // Window that initiated the drag
GLFWDragSourceData** pending_requests; // Array of pending data requests
int pending_request_count; // Number of pending requests
int pending_request_capacity; // Capacity of the pending requests array
struct wl_surface *drag_icon;
struct wp_viewport *drag_viewport;
struct {
const char *mime_type;
int fd;
GLFWid watch_id;
char *pending_data;
size_t sz, offset;
} *data_requests;
size_t count, capacity;
} drag;
} _GLFWlibraryWayland;

380
glfw/wl_window.c vendored
View file

@ -2299,7 +2299,7 @@ static void primary_selection_source_canceled(void *data UNUSED, struct zwp_prim
static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) {
}
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) {
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, unsigned int dnd_action UNUSED) {
}
static const struct wl_data_source_listener data_source_listener = {
@ -2483,7 +2483,7 @@ drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint
_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->is_self_offer = (_glfw.drag.window_id != 0);
offer->surface = surface;
offer->serial = serial;
offer->drag_accepted = false;
@ -3057,158 +3057,188 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) {
return true;
}
// Drag operation implementation
// Drag source {{{
static void
cleanup_wl_drag_source_data(GLFWDragSourceData* data) {
if (!data) return;
if (data->write_fd >= 0) {
close(data->write_fd);
data->write_fd = -1;
drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_CANCELLED};
_glfwInputDragSourceRequest(window, &ev);
}
free(data->mime_type);
free(data);
_glfwFreeDragSourceData();
}
// Remove a finished request from the pending requests array
#define dr _glfw.wl.drag.data_requests[i]
static void
remove_wl_pending_request(int index) {
if (index < 0 || index >= _glfw.wl.drag.pending_request_count) return;
cleanup_wl_drag_source_data(_glfw.wl.drag.pending_requests[index]);
// Shift remaining elements
for (int i = index; i < _glfw.wl.drag.pending_request_count - 1; i++) {
_glfw.wl.drag.pending_requests[i] = _glfw.wl.drag.pending_requests[i + 1];
}
_glfw.wl.drag.pending_request_count--;
finish_drag_write(size_t i) {
if (dr.watch_id) removeWatch(&_glfw.wl.eventLoopData, dr.watch_id);
dr.watch_id = 0;
if (dr.fd > -1) safe_close(dr.fd);
dr.fd = -1;
free((void*)dr.mime_type); dr.mime_type = NULL;
}
static ssize_t
write_as_much_as_possible(int fd, const char *data, size_t sz) {
size_t ans = 0;
while (ans < sz) {
ssize_t ret = write(fd, data + ans, sz - ans);
if (ret < 0) {
if (errno == EINTR) continue;
if (errno == EAGAIN) return ans;
return ret;
}
ans += ret;
}
return ans;
}
// Clean up all finished requests from the pending requests array
static void
cleanup_wl_finished_requests(void) {
for (int i = _glfw.wl.drag.pending_request_count - 1; i >= 0; i--) {
if (_glfw.wl.drag.pending_requests[i]->finished) {
remove_wl_pending_request(i);
send_drag_data(_GLFWwindow *window, size_t i) {
ssize_t ret;
bool has_preset_data = _glfw.drag.items[i].data_size > 0;
#define on_fail _glfwInputError(\
GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \
drag_source_cancelled(NULL, NULL)
if (dr.sz > dr.offset) {
ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset);
if (ret < 0) { on_fail; } else {
dr.offset += ret;
if (dr.offset >= dr.sz) {
free(dr.pending_data); dr.sz = 0; dr.offset = 0;
if (has_preset_data) finish_drag_write(i);
}
}
} else if (has_preset_data) {
do { ret = write(dr.fd, _glfw.drag.items[i].optional_data, _glfw.drag.items[i].data_size); } while (ret < 0 && errno == EINTR);
if (ret < 0) {
on_fail;
} else {
if ((size_t)ret >= _glfw.drag.items[i].data_size) {
finish_drag_write(i);
} else {
void *pending = malloc(_glfw.drag.items[i].data_size - ret);
if (!pending) { on_fail; } else {
dr.pending_data = pending; dr.sz = _glfw.drag.items[i].data_size - ret; dr.offset = 0;
}
}
}
} else {
GLFWDragEvent ev = {.type=GLFW_DRAG_DATA_REQUEST, .mime_type=dr.mime_type};
_glfwInputDragSourceRequest(window, &ev);
if (ev.err_num) {
if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; }
else drag_source_cancelled(NULL, NULL);
} else {
if (ev.data_sz) {
ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz);
if (ret >= 0) {
if ((size_t)ret < ev.data_sz) {
void *pending = malloc(ev.data_sz - ret);
if (!pending) { on_fail; } else {
dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0;
memcpy(pending, ev.data + ret, dr.sz);
}
}
}
_glfwInputDragSourceRequest(window, &ev);
if (ret < 0) { on_fail; }
} else finish_drag_write(i);
}
}
#undef on_fail
}
static void
ready_for_drag_data(int fd, int events UNUSED, void *data UNUSED) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (!window) { _glfwFreeDragSourceData(); return; }
for (size_t i = 0; i < _glfw.wl.drag.count; i++) {
if (dr.fd == fd) {
send_drag_data(window, i);
break;
}
}
}
// Clean up all pending requests
static void
cleanup_all_wl_pending_requests(void) {
for (int i = 0; i < _glfw.wl.drag.pending_request_count; i++) {
cleanup_wl_drag_source_data(_glfw.wl.drag.pending_requests[i]);
}
free(_glfw.wl.drag.pending_requests);
_glfw.wl.drag.pending_requests = NULL;
_glfw.wl.drag.pending_request_count = 0;
_glfw.wl.drag.pending_request_capacity = 0;
static GLFWid
add_drag_watch(int fd) {
return addWatch(
&_glfw.wl.eventLoopData, "drag_source", fd, POLLOUT | POLLERR | POLLHUP, true, ready_for_drag_data, NULL);
}
// Add a request to the pending requests array
static bool
add_wl_pending_request(GLFWDragSourceData* request) {
// First, clean up any finished requests to make room
cleanup_wl_finished_requests();
// Grow the array if necessary
if (_glfw.wl.drag.pending_request_count >= _glfw.wl.drag.pending_request_capacity) {
// Cap maximum capacity to prevent excessive memory use
if (_glfw.wl.drag.pending_request_capacity >= 512) {
return false;
int
_glfwPlatformDragDataReady(const char *mime_type) {
for (size_t i = 0; i < _glfw.wl.drag.count; i++) {
if (strcmp(dr.mime_type, mime_type) == 0) {
if (!dr.watch_id) dr.watch_id = add_drag_watch(dr.fd);
}
int new_capacity = _glfw.wl.drag.pending_request_capacity ? _glfw.wl.drag.pending_request_capacity * 2 : 4;
GLFWDragSourceData** new_array = realloc(_glfw.wl.drag.pending_requests,
new_capacity * sizeof(GLFWDragSourceData*));
if (!new_array) return false;
_glfw.wl.drag.pending_requests = new_array;
_glfw.wl.drag.pending_request_capacity = new_capacity;
}
_glfw.wl.drag.pending_requests[_glfw.wl.drag.pending_request_count++] = request;
return true;
return 0;
}
static void
cleanup_drag(struct wl_data_source *source) {
// Notify the application that the drag source is closed
_GLFWwindow *window = _glfwWindowForId(_glfw.wl.drag.window_id);
if (window && window->callbacks.dragSource) _glfwInputDragSourceRequest(window, NULL, NULL);
drag_source_send(void *data, struct wl_data_source *source, const char *mime_type, int fd) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
#define abort() safe_close(fd); drag_source_cancelled(data, source); return
if (!window) { abort(); }
mime_type = _glfw_strdup(mime_type);
if (!mime_type) { abort(); }
if (!_glfw.wl.drag.data_requests || _glfw.wl.drag.capacity <= _glfw.wl.drag.count + 1) {
_glfw.wl.drag.capacity = MAX(8u, _glfw.wl.drag.capacity * 2);
if (!(_glfw.wl.drag.data_requests = realloc(_glfw.wl.drag.data_requests, sizeof(_glfw.wl.drag.data_requests[0]) * _glfw.wl.drag.capacity))) { abort(); }
}
const size_t i = _glfw.wl.drag.count++;
memset(&dr, 0, sizeof(_glfw.wl.drag.data_requests[0]));
dr.mime_type = mime_type; dr.fd = fd;
dr.watch_id = add_drag_watch(fd);
#undef abort
}
// Clean up all pending data requests
cleanup_all_wl_pending_requests();
// Clean up MIME type strings
for (int i = 0; i < _glfw.wl.drag.mime_count; i++) free(_glfw.wl.drag.mimes[i]);
free(_glfw.wl.drag.mimes);
_glfw.wl.drag.mimes = NULL;
_glfw.wl.drag.mime_count = 0;
_glfw.wl.drag.window_id = 0;
if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport);
if (_glfw.wl.drag.drag_icon) wl_surface_destroy(_glfw.wl.drag.drag_icon);
_glfw.wl.drag.drag_icon = NULL; _glfw.wl.drag.drag_viewport = NULL;
if (_glfw.wl.drag.source && _glfw.wl.drag.source != source) wl_data_source_destroy(_glfw.wl.drag.source);
_glfw.wl.drag.source = NULL;
if (source) wl_data_source_destroy(source);
#undef dr
static void
drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_ACCEPTED, .mime_type=mime_type};
_glfwInputDragSourceRequest(window, &ev);
} else drag_source_cancelled(data, source);
}
static void
drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type, int fd) {
_GLFWwindow *window = _glfwWindowForId(_glfw.wl.drag.window_id);
if (!window) {
close(fd);
return;
}
// Create a new drag source data request
GLFWDragSourceData* request = calloc(1, sizeof(GLFWDragSourceData));
if (!request) {
close(fd);
return;
}
request->window_id = _glfw.wl.drag.window_id;
request->mime_type = _glfw_strdup(mime_type);
request->write_fd = fd;
request->finished = false;
request->error_code = 0;
if (!request->mime_type) {
cleanup_wl_drag_source_data(request);
return;
}
// Add to pending requests array
if (!add_wl_pending_request(request)) {
cleanup_wl_drag_source_data(request);
return;
}
// Notify the application via callback
_glfwInputDragSourceRequest(window, mime_type, request);
}
static void
drag_source_cancelled(void *data UNUSED, struct wl_data_source *source) {
cleanup_drag(source);
}
static void
drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type UNUSED) {
}
static void
drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action UNUSED) {
drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragOperationType op = GLFW_DRAG_OPERATION_GENERIC;
switch (dnd_action) {
case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: op = 0; break;
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: op = GLFW_DRAG_OPERATION_COPY; break;
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: op = GLFW_DRAG_OPERATION_MOVE; break;
}
GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED, .action=op};
_glfwInputDragSourceRequest(window, &ev);
} else drag_source_cancelled(data, source);
}
static void
drag_source_dnd_drop_performed(void *data UNUSED, struct wl_data_source *source UNUSED) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED};
_glfwInputDragSourceRequest(window, &ev);
} else drag_source_cancelled(data, source);
}
static void
drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source) {
drag_source_cancelled(data, source);
drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source UNUSED) {
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_FINSHED};
_glfwInputDragSourceRequest(window, &ev);
}
_glfwFreeDragSourceData();
}
static const struct wl_data_source_listener drag_source_listener = {
@ -3221,30 +3251,39 @@ static const struct wl_data_source_listener drag_source_listener = {
};
void
_glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) {
cleanup_drag(_glfw.wl.drag.source);
_glfwPlatformFreeDragSourceData(void) {
if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport);
if (_glfw.wl.drag.drag_icon) wl_surface_destroy(_glfw.wl.drag.drag_icon);
if (_glfw.wl.drag.source) wl_data_source_destroy(_glfw.wl.drag.source);
if (_glfw.wl.drag.data_requests) {
for (size_t i = 0; i < _glfw.wl.drag.count; i++) {
free((void*)_glfw.wl.drag.data_requests[i].mime_type);
free(_glfw.wl.drag.data_requests[i].pending_data);
if (_glfw.wl.drag.data_requests[i].watch_id) removeWatch(&_glfw.wl.eventLoopData, _glfw.wl.drag.data_requests[i].watch_id);
if (_glfw.wl.drag.data_requests[i].fd > -1) { safe_close(_glfw.wl.drag.data_requests[i].fd); }
}
free(_glfw.wl.drag.data_requests);
}
memset(&_glfw.wl.drag, 0, sizeof(_glfw.wl.drag));
}
int
_glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations) {
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) {
if (!_glfw.wl.dataDeviceManager) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device manager not available");
return EIO;
return ENOTSUP;
}
if (!_glfw.wl.dataDevice) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device not available");
return EIO;
return ENOTSUP;
}
// Clean up any existing drag operation
_glfwPlatformCancelDrag(window);
// Create the data source
_glfw.wl.drag.source = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.drag.source) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to create data source for drag");
return EIO;
return ENOMEM;
}
// Set the DND action based on operation type (bitfield)
@ -3257,28 +3296,7 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int m
wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
wl_data_source_set_actions(_glfw.wl.drag.source, wl_actions);
// Allocate storage for MIME types
_glfw.wl.drag.mimes = calloc(mime_count, sizeof(char*));
_glfw.wl.drag.mime_count = mime_count;
_glfw.wl.drag.window_id = window->id;
if (!_glfw.wl.drag.mimes) {
_glfwPlatformCancelDrag(window);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag MIME types");
return ENOMEM;
}
// Copy MIME types and offer them
for (int i = 0; i < mime_count; i++) {
_glfw.wl.drag.mimes[i] = _glfw_strdup(mime_types[i]);
if (!_glfw.wl.drag.mimes[i]) {
_glfwPlatformCancelDrag(window);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag MIME type");
return ENOMEM;
}
wl_data_source_offer(_glfw.wl.drag.source, mime_types[i]);
}
for (size_t i = 0; i < _glfw.drag.item_count; i++) wl_data_source_offer(_glfw.wl.drag.source, _glfw.drag.items[i].mime_type);
wl_data_source_add_listener(_glfw.wl.drag.source, &drag_source_listener, NULL);
// Set up the drag icon surface if thumbnail is provided
@ -3311,54 +3329,4 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int m
return 0;
}
ssize_t
_glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) {
if (!source_data || source_data->finished) return -EINVAL;
if (source_data->write_fd < 0) return -EIO;
// End of data: NULL data pointer and size zero
if (!data && size == 0) {
source_data->finished = true;
close(source_data->write_fd);
source_data->write_fd = -1;
// Clean up this and any other finished requests
cleanup_wl_finished_requests();
return 0;
}
// Error from application: NULL data pointer and size is error code
if (!data && size > 0) {
source_data->finished = true;
source_data->error_code = (int)size;
close(source_data->write_fd);
source_data->write_fd = -1;
// Clean up this and any other finished requests
cleanup_wl_finished_requests();
return 0;
}
// Non-blocking write - retry on EINTR, return 0 on would-block
ssize_t written;
do {
written = write(source_data->write_fd, data, size);
} while (written < 0 && errno == EINTR);
if (written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Would block, return 0 bytes written
return 0;
}
// Actual error
source_data->finished = true;
source_data->error_code = errno;
close(source_data->write_fd);
source_data->write_fd = -1;
// Clean up this and any other finished requests
cleanup_wl_finished_requests();
return -errno;
}
return written;
}
// }}}

8
glfw/x11_platform.h vendored
View file

@ -403,15 +403,9 @@ typedef struct _GLFWlibraryX11
// Drag source state
struct {
Window source_window;
char** mimes; // Array of MIME type strings
int mime_count; // Number of MIME types
Atom* type_atoms; // Atoms for each MIME type
size_t type_count;
Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink
bool active;
_GLFWwindow* window; // Window that initiated the drag
GLFWDragSourceData** pending_requests; // Array of pending data requests
int pending_request_count; // Number of pending requests
int pending_request_capacity; // Capacity of the pending requests array
} drag;
struct {

336
glfw/x11_window.c vendored
View file

@ -88,10 +88,6 @@ x11_cancel_momentum_scroll_timer(void) {
#define _GLFW_XDND_VERSION 5
// Forward declarations for drag source data handling
static void cleanup_x11_drag_source_data(GLFWDragSourceData* data);
static bool add_x11_pending_request(GLFWDragSourceData* request);
// Wait for data to arrive using poll
// This avoids blocking other threads via the per-display Xlib lock that also
// covers GLX functions
@ -1100,8 +1096,8 @@ static void handleSelectionClear(XEvent* event)
}
}
static void handleSelectionRequest(XEvent* event)
{
static void
handleSelectionRequest(XEvent* event) {
const XSelectionRequestEvent* request = &event->xselectionrequest;
XEvent reply = { SelectionNotify };
@ -1110,19 +1106,18 @@ static void handleSelectionRequest(XEvent* event)
reply.xselection.selection = request->selection;
reply.xselection.target = request->target;
reply.xselection.time = request->time;
reply.xselection.property = None;
// Handle XdndSelection (drag and drop) specially
if (request->selection == _glfw.x11.XdndSelection && _glfw.x11.drag.active && _glfw.x11.drag.window) {
if (request->selection == _glfw.x11.XdndSelection && _glfw.drag.window_id) {
// Handle TARGETS request for XdndSelection
if (request->target == _glfw.x11.TARGETS) {
// Return the list of supported MIME type atoms
Atom *targets = calloc(_glfw.x11.drag.mime_count + 2, sizeof(Atom));
Atom *targets = calloc(_glfw.x11.drag.type_count + 2, sizeof(Atom));
if (targets) {
targets[0] = _glfw.x11.TARGETS;
targets[1] = _glfw.x11.MULTIPLE;
for (int i = 0; i < _glfw.x11.drag.mime_count; i++) {
targets[i + 2] = _glfw.x11.drag.type_atoms[i];
}
for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) targets[i + 2] = _glfw.x11.drag.type_atoms[i];
XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
@ -1130,72 +1125,27 @@ static void handleSelectionRequest(XEvent* event)
32,
PropModeReplace,
(unsigned char*)targets,
_glfw.x11.drag.mime_count + 2);
_glfw.x11.drag.type_count + 2);
free(targets);
reply.xselection.property = request->property;
} else {
reply.xselection.property = None;
}
} else {
// Find the matching MIME type for the requested target
const char* mime_type = NULL;
for (int i = 0; i < _glfw.x11.drag.mime_count; i++) {
for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) {
if (_glfw.x11.drag.type_atoms[i] == request->target) {
mime_type = _glfw.x11.drag.mimes[i];
mime_type = _glfw.drag.items[i].mime_type;
break;
}
}
if (mime_type) {
// Create a drag source data request
GLFWDragSourceData* source_data = calloc(1, sizeof(GLFWDragSourceData));
if (source_data) {
source_data->window_id = _glfw.x11.drag.window->id;
source_data->mime_type = _glfw_strdup(mime_type);
source_data->write_fd = -1;
source_data->finished = false;
source_data->error_code = 0;
// Store request info in platform_data for later use
// We'll use a simple struct to hold the X11-specific data
struct {
Window requestor;
Atom property;
Atom target;
} *x11_data = malloc(sizeof(*x11_data));
if (x11_data && source_data->mime_type) {
x11_data->requestor = request->requestor;
x11_data->property = request->property;
x11_data->target = request->target;
source_data->platform_data = x11_data;
if (add_x11_pending_request(source_data)) {
// Notify the application via callback
_glfwInputDragSourceRequest(_glfw.x11.drag.window, mime_type, source_data);
reply.xselection.property = request->property;
} else {
free(x11_data);
free(source_data->mime_type);
free(source_data);
reply.xselection.property = None;
}
} else {
free(x11_data);
free(source_data->mime_type);
free(source_data);
reply.xselection.property = None;
}
} else {
reply.xselection.property = None;
}
} else {
reply.xselection.property = None;
// TODO: Create a pending request and send GLFW_DRAG_DATA_REQUEST to application
}
}
} else {
// Handle regular clipboard/primary selection
reply.xselection.property = writeTargetToProperty(request);
}
XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply);
}
@ -3863,262 +3813,12 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
}
// Helper function to clean up drag source data
static void cleanup_x11_drag_source_data(GLFWDragSourceData* data) {
if (!data) return;
if (data->write_fd >= 0) {
close(data->write_fd);
data->write_fd = -1;
}
free(data->platform_data);
free(data->mime_type);
free(data);
}
// Remove a finished request from the pending requests array
static void
remove_x11_pending_request(int index) {
if (index < 0 || index >= _glfw.x11.drag.pending_request_count) return;
cleanup_x11_drag_source_data(_glfw.x11.drag.pending_requests[index]);
// Shift remaining elements
for (int i = index; i < _glfw.x11.drag.pending_request_count - 1; i++) {
_glfw.x11.drag.pending_requests[i] = _glfw.x11.drag.pending_requests[i + 1];
}
_glfw.x11.drag.pending_request_count--;
}
// Clean up all finished requests from the pending requests array
static void
cleanup_x11_finished_requests(void) {
for (int i = _glfw.x11.drag.pending_request_count - 1; i >= 0; i--) {
if (_glfw.x11.drag.pending_requests[i]->finished) {
remove_x11_pending_request(i);
}
}
}
// Clean up all pending requests
static void
cleanup_all_x11_pending_requests(void) {
for (int i = 0; i < _glfw.x11.drag.pending_request_count; i++) {
cleanup_x11_drag_source_data(_glfw.x11.drag.pending_requests[i]);
}
free(_glfw.x11.drag.pending_requests);
_glfw.x11.drag.pending_requests = NULL;
_glfw.x11.drag.pending_request_count = 0;
_glfw.x11.drag.pending_request_capacity = 0;
}
// Add a request to the pending requests array
static bool
add_x11_pending_request(GLFWDragSourceData* request) {
// First, clean up any finished requests to make room
cleanup_x11_finished_requests();
// Grow the array if necessary
if (_glfw.x11.drag.pending_request_count >= _glfw.x11.drag.pending_request_capacity) {
// Cap maximum capacity to prevent excessive memory use
if (_glfw.x11.drag.pending_request_capacity >= 512) {
return false;
}
int new_capacity = _glfw.x11.drag.pending_request_capacity ? _glfw.x11.drag.pending_request_capacity * 2 : 4;
GLFWDragSourceData** new_array = realloc(_glfw.x11.drag.pending_requests,
new_capacity * sizeof(GLFWDragSourceData*));
if (!new_array) return false;
_glfw.x11.drag.pending_requests = new_array;
_glfw.x11.drag.pending_request_capacity = new_capacity;
}
_glfw.x11.drag.pending_requests[_glfw.x11.drag.pending_request_count++] = request;
return true;
}
static void cleanupDragSource(void) {
// Notify the application that the drag source is closed
if (_glfw.x11.drag.window && _glfw.x11.drag.window->callbacks.dragSource) {
_glfwInputDragSourceRequest(_glfw.x11.drag.window, NULL, NULL);
}
// Clean up all pending data requests
cleanup_all_x11_pending_requests();
// Clean up MIME type strings and atoms
for (int i = 0; i < _glfw.x11.drag.mime_count; i++) {
free(_glfw.x11.drag.mimes[i]);
}
free(_glfw.x11.drag.mimes);
free(_glfw.x11.drag.type_atoms);
_glfw.x11.drag.mimes = NULL;
_glfw.x11.drag.type_atoms = NULL;
_glfw.x11.drag.mime_count = 0;
_glfw.x11.drag.source_window = None;
_glfw.x11.drag.active = false;
_glfw.x11.drag.window = NULL;
}
void _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) {
cleanupDragSource();
}
int _glfwPlatformStartDrag(_GLFWwindow* window,
const char* const* mime_types,
int mime_count,
const GLFWimage* thumbnail UNUSED,
int operations) {
// Clean up any existing drag operation
cleanupDragSource();
// Set the drag action based on operation type (bitfield)
// Default to copy, prefer move if specified
if (operations & GLFW_DRAG_OPERATION_MOVE) {
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionMove;
} else if (operations & GLFW_DRAG_OPERATION_COPY) {
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
} else {
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
}
// Allocate storage for MIME types
_glfw.x11.drag.mimes = calloc(mime_count, sizeof(char*));
_glfw.x11.drag.type_atoms = calloc(mime_count, sizeof(Atom));
_glfw.x11.drag.mime_count = mime_count;
_glfw.x11.drag.source_window = window->x11.handle;
_glfw.x11.drag.window = window;
if (!_glfw.x11.drag.mimes || !_glfw.x11.drag.type_atoms) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag data");
return ENOMEM;
}
// Copy MIME types and create atoms
for (int i = 0; i < mime_count; i++) {
_glfw.x11.drag.mimes[i] = _glfw_strdup(mime_types[i]);
if (!_glfw.x11.drag.mimes[i]) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag MIME type");
return ENOMEM;
}
_glfw.x11.drag.type_atoms[i] = XInternAtom(_glfw.x11.display, mime_types[i], False);
}
// Set up XdndTypeList property if we have more than 3 types
if (mime_count > 3) {
XChangeProperty(_glfw.x11.display, window->x11.handle,
_glfw.x11.XdndTypeList, XA_ATOM, 32, PropModeReplace,
(unsigned char*)_glfw.x11.drag.type_atoms, mime_count);
}
// Take ownership of XdndSelection
XSetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection,
window->x11.handle, CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection) != window->x11.handle) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to acquire XdndSelection ownership");
return EIO;
}
_glfw.x11.drag.active = true;
// Note: The actual drag operation in X11 requires grabbing the pointer and tracking
// mouse movement to send XdndEnter/Position/Leave/Drop messages to target windows.
// This is a complex state machine that requires:
// 1. Grabbing the pointer with XGrabPointer
// 2. Tracking mouse movement
// 3. Finding window under cursor with XTranslateCoordinates
// 4. Sending XdndEnter when entering a new window
// 5. Sending XdndPosition as the cursor moves
// 6. Sending XdndLeave when leaving a window
// 7. Sending XdndDrop on button release
// 8. Responding to SelectionRequest events with the drag data
//
// For a complete implementation, this would need to be integrated with the
// event loop. For now, we set up the data source so the application can
// handle its own drag tracking if needed.
return 0;
}
ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) {
if (!source_data || source_data->finished) return -EINVAL;
// For X11, we set properties via XChangeProperty in response to SelectionRequest
// End of data: NULL data pointer and size zero
if (!data && size == 0) {
source_data->finished = true;
if (source_data->write_fd >= 0) {
close(source_data->write_fd);
source_data->write_fd = -1;
}
// Clean up this and any other finished requests
cleanup_x11_finished_requests();
return 0;
}
// Error from application: NULL data pointer and size is error code
if (!data && size > 0) {
source_data->finished = true;
source_data->error_code = (int)size;
if (source_data->write_fd >= 0) {
close(source_data->write_fd);
source_data->write_fd = -1;
}
// Clean up this and any other finished requests
cleanup_x11_finished_requests();
return 0;
}
// For X11, use XChangeProperty to set the data on the requestor window
if (source_data->platform_data) {
struct {
Window requestor;
Atom property;
Atom target;
} *x11_data = source_data->platform_data;
XChangeProperty(_glfw.x11.display,
x11_data->requestor,
x11_data->property,
x11_data->target,
8,
PropModeReplace,
(unsigned char*)data,
size);
XFlush(_glfw.x11.display);
// Mark as finished after sending data
source_data->finished = true;
cleanup_x11_finished_requests();
return (ssize_t)size;
}
// Fallback: Non-blocking write if we have an fd
if (source_data->write_fd >= 0) {
ssize_t written;
do {
written = write(source_data->write_fd, data, size);
} while (written < 0 && errno == EINTR);
if (written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Would block, return 0 bytes written
return 0;
}
source_data->finished = true;
source_data->error_code = errno;
close(source_data->write_fd);
source_data->write_fd = -1;
// Clean up this and any other finished requests
cleanup_x11_finished_requests();
return -errno;
}
return written;
}
// No valid mechanism to send data
return -EINVAL;
// Drag source {{{
int
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) {
(void)window; (void)thumbnail; (void)operations;
return ENOTSUP;
}
void _glfwPlatformFreeDragSourceData(void) {}
int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; }
// }}}

View file

@ -333,6 +333,7 @@ void focus_in_event(void);
void scroll_event(const GLFWScrollEvent *ev);
void on_key_input(const GLFWkeyevent *ev);
void request_window_attention(id_type, bool);
void free_drag_source(void);
locale_t get_c_locale(void);
#ifndef __APPLE__
void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name);

3
kitty/glfw-wrapper.c generated
View file

@ -371,9 +371,6 @@ load_glfw(const char* path) {
*(void **) (&glfwStartDrag_impl) = dlsym(handle, "glfwStartDrag");
if (glfwStartDrag_impl == NULL) fail("Failed to load glfw function glfwStartDrag with error: %s", dlerror());
*(void **) (&glfwSendDragData_impl) = dlsym(handle, "glfwSendDragData");
if (glfwSendDragData_impl == NULL) fail("Failed to load glfw function glfwSendDragData 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());

77
kitty/glfw-wrapper.h generated
View file

@ -1131,7 +1131,9 @@ typedef enum {
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
// Positions are only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE.
// They are in window co-ordinates same as for mouse events
double xpos, ypos;
bool from_self; // Only valid upto GLFW_DROP_DROP
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
@ -1537,57 +1539,30 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,const GLFWScrollEvent*);
*/
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
/*! @brief Drag event types.
*
* These constants are used to identify the type of drag event.
*
* @ingroup input
*/
typedef enum {
/*! The drag operation entered the window. */
GLFW_DRAG_ENTER = 1,
/*! The drag operation moved within the window. */
GLFW_DRAG_MOVE = 2,
/*! The drag operation left the window. */
GLFW_DRAG_LEAVE = 3,
/*! Async status update request (xpos/ypos are invalid). */
GLFW_DRAG_STATUS_UPDATE = 4
GLFW_DRAG_DATA_REQUEST,
GLFW_DRAG_CANCELLED,
GLFW_DRAG_FINSHED,
GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified
GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType
GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet
} GLFWDragEventType;
/*! @brief Opaque drag source data handle.
*
* This is an opaque handle to a heap-allocated object that represents
* data being requested from a drag source. The lifetime is managed by
* the GLFW backend - it is freed on end of data, error, drag source
* cancellation, or at exit.
*
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef struct GLFWDragSourceData GLFWDragSourceData;
typedef struct GLFWDragSourceItem {
const char *mime_type;
// Can be on null to provide data when the drag is started should be used only when the data is relatively small
const char *optional_data;
size_t data_size;
} GLFWDragSourceItem;
/*! @brief The function pointer type for drag source data request callbacks.
*
* This is the function pointer type for callbacks invoked when the OS
* requests data for a specific MIME type from the active drag source.
* The callback is called on the GUI thread.
*
* @param[in] window The window that initiated the drag.
* @param[in] mime_type The MIME type being requested, or NULL if the OS
* has closed the drag source.
* @param[in] source_data Opaque pointer to a heap-allocated object. Use this
* pointer when calling @ref glfwSendDragData to send data chunks.
*
* @sa @ref glfwStartDrag
* @sa @ref glfwSendDragData
* @sa @ref glfwSetDragSourceCallback
*
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef void (* GLFWdragsourcefun)(GLFWwindow* window, const char* mime_type, GLFWDragSourceData* source_data);
typedef struct GLFWDragEvent {
GLFWDragEventType type;
const char *mime_type;
const char *data; size_t data_sz; int err_num;
GLFWDragOperationType action; // can be 0 indicating no action
} GLFWDragEvent;
typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev);
/*! @brief The function pointer type for drag event callbacks.
*
@ -2320,14 +2295,10 @@ typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdra
GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl;
#define glfwSetDragSourceCallback glfwSetDragSourceCallback_impl
typedef int (*glfwStartDrag_func)(GLFWwindow*, const char* const*, int, const GLFWimage*, int);
typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWDragSourceItem*, size_t, const GLFWimage*, int);
GFW_EXTERN glfwStartDrag_func glfwStartDrag_impl;
#define glfwStartDrag glfwStartDrag_impl
typedef ssize_t (*glfwSendDragData_func)(GLFWDragSourceData*, const void*, size_t);
GFW_EXTERN glfwSendDragData_func glfwSendDragData_impl;
#define glfwSendDragData glfwSendDragData_impl
typedef int (*glfwJoystickPresent_func)(int);
GFW_EXTERN glfwJoystickPresent_func glfwJoystickPresent_impl;
#define glfwJoystickPresent glfwJoystickPresent_impl

View file

@ -772,48 +772,38 @@ application_close_requested_callback(int flags) {
}
#define ds (global_state.drag_source)
static void
try_sending_drag_source_data(id_type timer_id UNUSED, void *callback_data UNUSED) {
bool incomplete = false;
for (size_t i = 0; i < ds.num_ongoing_transfers; i++) {
#define t ds.ongoing_transfers[i]
size_t sz = PyBytes_GET_SIZE(t.weakref_to_data_object);
ssize_t ret;
if (sz > t.offset) {
const char *data = PyBytes_AS_STRING(t.weakref_to_data_object);
ret = glfwSendDragData(t.platform_data, data + t.offset, sz - t.offset);
} else ret = glfwSendDragData(t.platform_data, NULL, 0);
if (ret >= 0) {
t.offset += ret;
if (t.offset < sz) incomplete = true;
else glfwSendDragData(t.platform_data, NULL, 0); // tell glfw transfer is complete
} else {
log_error("Failed to send data from drag source with error: %s", strerror(-ret));
t.offset = sz;
}
#undef t
}
if (incomplete) add_main_loop_timer(ms_double_to_monotonic_t(2), false, try_sending_drag_source_data, NULL, NULL);
void
free_drag_source(void) {
if (ds.accepted_mime_type) free(ds.accepted_mime_type);
Py_CLEAR(ds.drag_data);
zero_at_ptr(&ds);
}
static void
drag_source_callback(GLFWwindow *window UNUSED, const char* mime_type, GLFWDragSourceData* source_data) {
PyObject *data = NULL;
if (mime_type == NULL) {
ds.is_active = false;
Py_CLEAR(ds.drag_data);
return;
drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
switch (ev->type) {
case GLFW_DRAG_DATA_REQUEST: // we currently pre-provide all data so this should never happen
if (ev->data_sz) {
// previously returned data is consumed, free it
} else {
ev->err_num = ENOENT;
}
break;
case GLFW_DRAG_ACCEPTED:
free(ds.accepted_mime_type);
ds.accepted_mime_type = ev->mime_type ? strdup(ev->mime_type) : NULL;
break;
case GLFW_DRAG_ACTION_CHANGED: ds.action = ev->action; break;
case GLFW_DRAG_DROPPED: ds.was_dropped = true; break;
case GLFW_DRAG_CANCELLED:
ds.was_canceled = true;
/* fallthrough */
case GLFW_DRAG_FINSHED:
ds.is_active = false;
free_drag_source();
break;
}
if (!ds.is_active || !ds.drag_data || !(data = PyDict_GetItemString(ds.drag_data, mime_type))) {
glfwSendDragData(source_data, NULL, EINVAL);
return;
}
ensure_space_for(&ds, ongoing_transfers, ds.ongoing_transfers[0], ds.num_ongoing_transfers + 1, ongoing_transfers_capacity, 8, true);
ds.ongoing_transfers[ds.num_ongoing_transfers].platform_data = source_data;
ds.ongoing_transfers[ds.num_ongoing_transfers].weakref_to_data_object = data;
ds.ongoing_transfers[ds.num_ongoing_transfers].offset = 0;
ds.num_ongoing_transfers++;
try_sending_drag_source_data(0, NULL);
}
#undef ds
@ -2761,19 +2751,25 @@ start_drag_with_data(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
&os_window_id, &PyDict_Type, &data_map, &thumbnail_data, &thumbnail_sz, &width, &height, &operations)) return NULL;
OSWindow *w = os_window_for_id(os_window_id);
if (!w || !w->handle) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist"); return NULL; }
RAII_ALLOC(const char*, mime_types, calloc(PyDict_Size(data_map), sizeof(const char*)));
if (!mime_types) { PyErr_NoMemory(); return NULL; }
PyObject *key, *value; Py_ssize_t pos = 0; int num = 0;
RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(const char*)));
if (!items) { PyErr_NoMemory(); return NULL; }
PyObject *key, *value; Py_ssize_t pos = 0; size_t num = 0;
while (PyDict_Next(data_map, &pos, &key, &value)) {
if (!PyUnicode_Check(key)) { PyErr_SetString(PyExc_TypeError, "data_map must have string keys"); return NULL; }
if (!PyBytes_Check(value)) { PyErr_SetString(PyExc_TypeError, "data_map must have bytes values"); return NULL; }
mime_types[num++] = PyUnicode_AsUTF8(key);
items[num].mime_type = PyUnicode_AsUTF8(key);
items[num].optional_data = PyBytes_AS_STRING(value); items[num].data_size = PyBytes_GET_SIZE(value);
num++;
}
GLFWimage thumbnail = {.pixels=thumbnail_data, .width=width, .height=height};
free_drag_source();
global_state.drag_source.is_active = true;
Py_CLEAR(global_state.drag_source.drag_data); global_state.drag_source.drag_data = Py_NewRef(data_map);
global_state.drag_source.num_ongoing_transfers = 0;
glfwStartDrag(w->handle, mime_types, num, thumbnail_data ? &thumbnail : NULL, operations);
global_state.drag_source.drag_data = Py_NewRef(data_map);
errno = glfwStartDrag(w->handle, items, num, thumbnail_data ? &thumbnail : NULL, operations);
if (errno != 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}

View file

@ -1581,9 +1581,7 @@ finalize(void) {
free_bgimage(&global_state.bgimage, false);
free_window_logo_table(&global_state.all_window_logos);
global_state.bgimage = NULL;
free(global_state.drag_source.ongoing_transfers);
Py_CLEAR(global_state.drag_source.drag_data);
zero_at_ptr(&global_state.drag_source);
free_drag_source();
Py_CLEAR(global_state.drop_dest.data);
zero_at_ptr(&global_state.drop_dest);

View file

@ -383,14 +383,10 @@ typedef struct GlobalState {
} drop_dest;
struct {
bool is_active;
bool is_active, was_dropped, was_canceled;
char *accepted_mime_type;
int action;
PyObject *drag_data;
struct {
void *platform_data;
size_t offset;
PyObject *weakref_to_data_object;
} *ongoing_transfers;
size_t num_ongoing_transfers, ongoing_transfers_capacity;
} drag_source;
} GlobalState;