mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-06-26 19:01:27 +00:00
Refactor drag source API to be async
Wayland implementation done. Other two backends remain.
This commit is contained in:
parent
76a29273c8
commit
e5eb63fcd0
14 changed files with 351 additions and 876 deletions
|
|
@ -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
167
glfw/glfw3.h
vendored
|
|
@ -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
84
glfw/input.c
vendored
|
|
@ -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
35
glfw/internal.h
vendored
|
|
@ -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
16
glfw/wl_platform.h
vendored
|
|
@ -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
380
glfw/wl_window.c
vendored
|
|
@ -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
8
glfw/x11_platform.h
vendored
|
|
@ -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
336
glfw/x11_window.c
vendored
|
|
@ -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; }
|
||||
// }}}
|
||||
|
|
|
|||
|
|
@ -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
3
kitty/glfw-wrapper.c
generated
|
|
@ -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
77
kitty/glfw-wrapper.h
generated
|
|
@ -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
|
||||
|
|
|
|||
86
kitty/glfw.c
86
kitty/glfw.c
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue