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