Refactor drag API to make it asynchronous

Fixes #9477
This commit is contained in:
copilot-swe-agent[bot] 2026-02-05 15:00:53 +00:00 committed by Kovid Goyal
parent a7b8e880c9
commit 5ea35cbbfc
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
12 changed files with 856 additions and 271 deletions

View file

@ -169,12 +169,16 @@ typedef struct _GLFWwindowNS
GLFWcocoarenderframefun resizeCallback;
// Current drag operation type for NSDraggingSource
GLFWDragOperationType dragOperationType;
int dragOperations; // Bitfield of GLFWDragOperationType
// Cached MIME types from drag enter (for move events)
const char** dragMimes;
int dragMimeCount; // Current count of MIME types (may be reduced by callback)
int dragMimeArraySize; // Original array size for proper cleanup
// Drag source state
char** sourceMimes; // Array of MIME type strings for drag source
int sourceMimeCount; // Number of source MIME types
} _GLFWwindowNS;
// Cocoa-specific global data

View file

@ -759,6 +759,134 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
}
@end // }}}
// File Promise Provider Delegate for async drag data {{{
// Structure to hold async drag state
typedef struct {
NSURL* destinationURL; // URL to write to
void (^completionHandler)(NSError*); // Completion block to call
NSFileHandle* fileHandle; // File handle for writing
bool finished; // Whether writing is complete
int errorCode; // Error code if any
} GLFWFilePromiseState;
@interface GLFWFilePromiseProviderDelegate : NSObject <NSFilePromiseProviderDelegate>
{
GLFWid windowId;
char* mimeType; // MIME type for this provider
}
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime;
@end
@implementation GLFWFilePromiseProviderDelegate
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime {
self = [super init];
if (self) {
windowId = initWindow ? initWindow->id : 0;
mimeType = _glfw_strdup(mime);
}
return self;
}
- (void)dealloc {
free(mimeType);
[super dealloc];
}
- (NSString*)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider fileNameForType:(NSString*)fileType {
(void)filePromiseProvider;
(void)fileType;
// Generate a unique filename based on the MIME type
NSString* extension = @"data";
if (mimeType) {
NSString* mt = @(mimeType);
if ([mt hasPrefix:@"text/"]) extension = @"txt";
else if ([mt isEqualToString:@"image/png"]) extension = @"png";
else if ([mt isEqualToString:@"image/jpeg"]) extension = @"jpg";
else if ([mt isEqualToString:@"application/json"]) extension = @"json";
}
return [NSString stringWithFormat:@"glfw-drag-%@.%@", [[NSUUID UUID] UUIDString], extension];
}
- (void)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider
writePromiseToURL:(NSURL*)url
completionHandler:(void (^)(NSError*))completionHandler {
(void)filePromiseProvider;
// Get the window from the ID
_GLFWwindow* window = _glfwWindowForId(windowId);
if (!window) {
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]);
return;
}
// Create the file
NSError* error = nil;
if (![[NSFileManager defaultManager] createFileAtPath:url.path contents:nil attributes:nil]) {
error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil];
completionHandler(error);
return;
}
NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingToURL:url error:&error];
if (!fileHandle) {
completionHandler(error);
return;
}
// Create the drag source data with file promise state
GLFWDragSourceData* source_data = calloc(1, sizeof(GLFWDragSourceData));
if (!source_data) {
[fileHandle closeFile];
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]);
return;
}
// Create the file promise state
GLFWFilePromiseState* state = calloc(1, sizeof(GLFWFilePromiseState));
if (!state) {
free(source_data);
[fileHandle closeFile];
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]);
return;
}
state->destinationURL = [url retain];
state->completionHandler = [completionHandler copy];
state->fileHandle = [fileHandle retain];
state->finished = false;
state->errorCode = 0;
source_data->window_id = windowId;
source_data->mime_type = _glfw_strdup(mimeType);
source_data->write_fd = -1;
source_data->finished = false;
source_data->error_code = 0;
source_data->platform_data = state;
if (!source_data->mime_type) {
[state->fileHandle closeFile];
[state->fileHandle release];
[state->destinationURL release];
Block_release(state->completionHandler);
free(state);
free(source_data);
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]);
return;
}
// Notify the application via callback - this will trigger glfwSendDragData calls
_glfwInputDragSourceRequest(window, mimeType, source_data);
// Note: The completion handler will be called from glfwSendDragData when finished
// If the application didn't finish (sync callback didn't complete), we need to handle it
// The platform_data still holds the state for async completion
}
@end
// }}}
// Content view class for the GLFW window {{{
@interface GLFWContentView : NSView <NSTextInputClient, NSDraggingSource>
@ -1539,15 +1667,15 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co
{
(void)session;
(void)context;
// Return the operation based on the stored drag operation type
switch (window->ns.dragOperationType) {
case GLFW_DRAG_OPERATION_COPY:
return NSDragOperationCopy;
case GLFW_DRAG_OPERATION_MOVE:
return NSDragOperationMove;
case GLFW_DRAG_OPERATION_GENERIC:
return NSDragOperationGeneric;
}
// Return the operation based on the stored drag operations bitfield
NSDragOperation ops = 0;
if (window->ns.dragOperations & GLFW_DRAG_OPERATION_COPY)
ops |= NSDragOperationCopy;
if (window->ns.dragOperations & GLFW_DRAG_OPERATION_MOVE)
ops |= NSDragOperationMove;
if (window->ns.dragOperations & GLFW_DRAG_OPERATION_GENERIC)
ops |= NSDragOperationGeneric;
return ops ? ops : NSDragOperationCopy;
}
- (void)draggingSession:(NSDraggingSession *)session
@ -1557,7 +1685,15 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co
(void)session;
(void)screenPoint;
(void)operation;
// Drag session ended
// Notify the application that the drag source is closed
_glfwInputDragSourceRequest(window, NULL, NULL);
// Clean up source MIME types
for (int i = 0; i < window->ns.sourceMimeCount; i++) {
free(window->ns.sourceMimes[i]);
}
free(window->ns.sourceMimes);
window->ns.sourceMimes = NULL;
window->ns.sourceMimeCount = 0;
}
- (BOOL)hasMarkedText
@ -3771,101 +3907,231 @@ void _glfwCocoaPostEmptyEvent(void) {
[NSApp postEvent:event atStart:YES];
}
void _glfwPlatformCancelDrag(_GLFWwindow* window) {
// Clean up source MIME types
for (int i = 0; i < window->ns.sourceMimeCount; i++) {
free(window->ns.sourceMimes[i]);
}
free(window->ns.sourceMimes);
window->ns.sourceMimes = NULL;
window->ns.sourceMimeCount = 0;
// Notify the application that the drag source is closed
_glfwInputDragSourceRequest(window, NULL, NULL);
}
int _glfwPlatformStartDrag(_GLFWwindow* window,
const GLFWdragitem* items,
int item_count,
const char* const* mime_types,
int mime_count,
const GLFWimage* thumbnail,
GLFWDragOperationType operation) {
// Store the operation type for the dragging source callback
window->ns.dragOperationType = operation;
int operations) {
// Cancel any existing drag operation
_glfwPlatformCancelDrag(window);
// Store the operations for the dragging source callback
window->ns.dragOperations = operations;
@autoreleasepool {
// Create pasteboard items for each drag item
NSMutableArray<NSPasteboardItem*>* pasteboardItems = [[NSMutableArray alloc] init];
for (int i = 0; i < item_count; i++) {
NSPasteboardItem* item = [[NSPasteboardItem alloc] init];
// Convert MIME type to UTI using the existing helper function
NSString* utiString = mime_to_uti(items[i].mime_type);
NSData* data = [NSData dataWithBytes:items[i].data length:items[i].data_size];
[item setData:data forType:utiString];
[pasteboardItems addObject:item];
// Store MIME types for later lookup
window->ns.sourceMimes = calloc(mime_count, sizeof(char*));
window->ns.sourceMimeCount = mime_count;
if (!window->ns.sourceMimes) {
return ENOMEM;
}
// Create the dragging item
NSDraggingItem* dragItem = nil;
if (thumbnail && thumbnail->pixels) {
// Create NSImage from thumbnail
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:thumbnail->width
pixelsHigh:thumbnail->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:thumbnail->width * 4
bitsPerPixel:32];
if (imageRep) {
memcpy([imageRep bitmapData], thumbnail->pixels,
thumbnail->width * thumbnail->height * 4);
NSImage* image = [[NSImage alloc] initWithSize:
NSMakeSize(thumbnail->width, thumbnail->height)];
[image addRepresentation:imageRep];
dragItem = [[NSDraggingItem alloc]
initWithPasteboardWriter:pasteboardItems.firstObject];
[dragItem setDraggingFrame:NSMakeRect(0, 0, thumbnail->width, thumbnail->height)
contents:image];
for (int i = 0; i < mime_count; i++) {
window->ns.sourceMimes[i] = _glfw_strdup(mime_types[i]);
if (!window->ns.sourceMimes[i]) {
_glfwPlatformCancelDrag(window);
return ENOMEM;
}
}
if (!dragItem && pasteboardItems.count > 0) {
dragItem = [[NSDraggingItem alloc]
initWithPasteboardWriter:pasteboardItems.firstObject];
[dragItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil];
}
// Create dragging items array - one NSFilePromiseProvider per MIME type
NSMutableArray<NSDraggingItem*>* dragItems = [[NSMutableArray alloc] init];
if (dragItem) {
// Start the drag session - try current event first, then create a synthetic one
NSEvent* event = [NSApp currentEvent];
if (!event || ([event type] != NSEventTypeLeftMouseDown &&
[event type] != NSEventTypeLeftMouseDragged)) {
// Create a synthetic left mouse down event using stored cursor position
// Convert window coordinates to screen coordinates
NSRect contentRect = [window->ns.view frame];
NSPoint windowPos = NSMakePoint(window->virtualCursorPosX,
contentRect.size.height - window->virtualCursorPosY);
for (int i = 0; i < mime_count; i++) {
NSString* utiString = mime_to_uti(mime_types[i]);
event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
location:windowPos
modifierFlags:0
timestamp:[[NSProcessInfo processInfo] systemUptime]
windowNumber:[window->ns.object windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// Create file promise provider with our delegate
GLFWFilePromiseProviderDelegate* delegate = [[GLFWFilePromiseProviderDelegate alloc]
initWithWindow:window mimeType:mime_types[i]];
NSFilePromiseProvider* provider = [[NSFilePromiseProvider alloc]
initWithFileType:utiString delegate:delegate];
// Store the delegate in the provider's user info so it's retained
provider.userInfo = delegate;
// Create the dragging item
NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:provider];
if (i == 0 && thumbnail && thumbnail->pixels) {
// Create NSImage from thumbnail for the first item
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:thumbnail->width
pixelsHigh:thumbnail->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:thumbnail->width * 4
bitsPerPixel:32];
if (imageRep) {
memcpy([imageRep bitmapData], thumbnail->pixels,
thumbnail->width * thumbnail->height * 4);
NSImage* image = [[NSImage alloc] initWithSize:
NSMakeSize(thumbnail->width, thumbnail->height)];
[image addRepresentation:imageRep];
[dragItem setDraggingFrame:NSMakeRect(0, 0, thumbnail->width, thumbnail->height)
contents:image];
}
} else {
[dragItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil];
}
if (event) {
[window->ns.view beginDraggingSessionWithItems:@[dragItem]
event:event
source:window->ns.view];
return true;
}
[dragItems addObject:dragItem];
}
return false;
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 &&
[event type] != NSEventTypeLeftMouseDragged)) {
// Create a synthetic left mouse down event using stored cursor position
// Convert window coordinates to screen coordinates
NSRect contentRect = [window->ns.view frame];
NSPoint windowPos = NSMakePoint(window->virtualCursorPosX,
contentRect.size.height - window->virtualCursorPosY);
event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
location:windowPos
modifierFlags:0
timestamp:[[NSProcessInfo processInfo] systemUptime]
windowNumber:[window->ns.object windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
}
if (event) {
[window->ns.view beginDraggingSessionWithItems:dragItems
event:event
source:window->ns.view];
return 0;
}
return EIO;
}
}
ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) {
if (!source_data || source_data->finished) return -EINVAL;
if (!source_data->platform_data) return -EINVAL;
GLFWFilePromiseState* state = (GLFWFilePromiseState*)source_data->platform_data;
// End of data: NULL data pointer and size zero
if (!data && size == 0) {
source_data->finished = true;
// Close the file handle
@try {
[state->fileHandle closeFile];
} @catch (NSException* e) {
(void)e;
}
// Call the completion handler with success
if (state->completionHandler) {
state->completionHandler(nil);
Block_release(state->completionHandler);
state->completionHandler = nil;
}
// Clean up
[state->fileHandle release];
state->fileHandle = nil;
[state->destinationURL release];
state->destinationURL = nil;
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;
state->errorCode = (int)size;
// Close the file handle
@try {
[state->fileHandle closeFile];
} @catch (NSException* e) {
(void)e;
}
// Call the completion handler with error
if (state->completionHandler) {
NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:size userInfo:nil];
state->completionHandler(error);
Block_release(state->completionHandler);
state->completionHandler = nil;
}
// Clean up
[state->fileHandle release];
state->fileHandle = nil;
[state->destinationURL release];
state->destinationURL = nil;
return 0;
}
// Write data to the file - Cocoa file operations are typically synchronous
// but we return the number of bytes written to match the non-blocking interface
if (state->fileHandle) {
@try {
NSData* nsData = [NSData dataWithBytes:data length:size];
if (@available(macOS 10.15, *)) {
NSError* error = nil;
if (![state->fileHandle writeData:nsData error:&error]) {
int errCode = error ? (int)error.code : EIO;
if (errCode == 0) errCode = EIO; // Ensure we have a valid error code
source_data->error_code = errCode;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to write drag data: %s",
error ? [[error localizedDescription] UTF8String] : "unknown error");
return -errCode;
}
} else {
// Pre-10.15 writeData: writes all bytes synchronously or throws an exception.
// NSFileHandle.writeData: is documented to write all data atomically,
// so returning size is correct. Any failure throws NSFileHandleOperationException.
[state->fileHandle writeData:nsData];
}
// NSFileHandle.writeData writes all data atomically, so size == bytes written
return (ssize_t)size;
} @catch (NSException* e) {
source_data->error_code = EIO;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Exception writing drag data: %s",
e ? [[e reason] UTF8String] : "unknown exception");
return -EIO;
}
}
// No file handle, consider all data accepted
return (ssize_t)size;
}
void _glfwPlatformUpdateDragState(_GLFWwindow* window) {
// Call the drag callback with STATUS_UPDATE to get updated acceptance and MIME list
// Position values are not valid for this event type

137
glfw/glfw3.h vendored
View file

@ -1844,25 +1844,40 @@ typedef enum {
GLFW_DRAG_OPERATION_GENERIC = 4
} GLFWDragOperationType;
/*! @brief Drag data item.
/*! @brief Opaque drag source data handle.
*
* This structure describes a single item of drag data with its MIME type.
*
* @sa @ref drag_start
* @sa @ref glfwStartDrag
* 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 GLFWdragitem {
/*! The MIME type of this data item (e.g., "text/plain", "image/png"). */
const char* mime_type;
/*! Pointer to the binary data. */
const unsigned char* data;
/*! Size of the data in bytes. */
size_t data_size;
} GLFWdragitem;
typedef struct GLFWDragSourceData GLFWDragSourceData;
/*! @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);
/*! @brief The function pointer type for drag event callbacks.
*
@ -5069,27 +5084,55 @@ GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWlive
*/
GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* window, GLFWdragfun callback);
/*! @brief Starts a drag operation.
/*! @brief Sets the drag source data request callback.
*
* This function starts a drag operation from the specified window with the
* given data items and optional thumbnail image. The drag operation will
* continue until the user releases the mouse button.
* 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.
*
* The data items array contains one or more MIME types with their associated
* binary data. The data is copied internally, so the caller can free it after
* this function returns.
* 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] items Array of drag data items.
* @param[in] item_count Number of items in the array.
* @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] operation The type of drag operation: @ref GLFW_DRAG_OPERATION_MOVE,
* @ref GLFW_DRAG_OPERATION_COPY, or @ref GLFW_DRAG_OPERATION_GENERIC. The default
* should be @ref GLFW_DRAG_OPERATION_MOVE.
* @param[in] operations A bitfield containing ORed values from
* @ref GLFWDragOperationType specifying allowed operations.
*
* @return `true` if the drag operation was started successfully, `false`
* otherwise.
* @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.
@ -5097,13 +5140,49 @@ GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* window, GLFWdragfun callback
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref drag_start
* @sa @ref glfwSetDragCallback
* @sa @ref glfwSetDragSourceCallback
* @sa @ref glfwSendDragData
*
* @since Added in version 4.0.
*
* @ingroup input
*/
GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation);
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);
/*! @brief Schedules a call to the drag callback to update drag state.
*

40
glfw/input.c vendored
View file

@ -419,6 +419,14 @@ int _glfwInputDragEvent(_GLFWwindow* window, int event, double xpos, double ypos
return 0;
}
// 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);
}
// Notifies shared code of a joystick connection or disconnection
//
void _glfwInputJoystick(_GLFWjoystick* js, int event)
@ -1130,15 +1138,37 @@ GLFWAPI GLFWdragfun glfwSetDragCallback(GLFWwindow* handle, GLFWdragfun cbfun)
return cbfun;
}
GLFWAPI int glfwStartDrag(GLFWwindow* handle, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation)
GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* handle, GLFWdragsourcefun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
assert(items != NULL);
assert(item_count > 0);
_GLFW_REQUIRE_INIT_OR_RETURN(false);
return _glfwPlatformStartDrag(window, items, item_count, thumbnail, operation);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.dragSource, 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;
}
return _glfwPlatformStartDrag(window, mime_types, mime_count, thumbnail, operations);
}
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 void glfwUpdateDragState(GLFWwindow* handle)

17
glfw/internal.h vendored
View file

@ -106,6 +106,17 @@ struct GLFWDropData {
int x11_version; // Xdnd protocol version (X11)
};
// 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);
@ -502,6 +513,7 @@ struct _GLFWwindow
GLFWdropfun drop;
GLFWliveresizefun liveResize;
GLFWdragfun drag;
GLFWdragsourcefun dragSource;
} callbacks;
// This is defined in the window API's platform.h
@ -788,7 +800,9 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev);
void _glfwPlatformChangeCursorTheme(void);
int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation);
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);
@ -845,6 +859,7 @@ void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
void _glfwInputDrop(_GLFWwindow* window, GLFWDropData* drop);
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);

24
glfw/null_window.c vendored
View file

@ -531,13 +531,25 @@ void _glfwPlatformSetCursor(_GLFWwindow* window UNUSED, _GLFWcursor* cursor UNUS
{
}
int _glfwPlatformStartDrag(_GLFWwindow* window UNUSED,
const GLFWdragitem* items UNUSED,
int item_count UNUSED,
const GLFWimage* thumbnail UNUSED,
GLFWDragOperationType operation UNUSED)
void _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED)
{
return false;
// No-op for null platform
}
int _glfwPlatformStartDrag(_GLFWwindow* window UNUSED,
const char* const* mime_types UNUSED,
int mime_count UNUSED,
const GLFWimage* thumbnail UNUSED,
int operations UNUSED)
{
return ENOTSUP;
}
ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data UNUSED,
const void* data UNUSED,
size_t size UNUSED)
{
return -ENOTSUP;
}
void _glfwPlatformUpdateDragState(_GLFWwindow* window UNUSED)

8
glfw/wl_platform.h vendored
View file

@ -412,10 +412,10 @@ typedef struct _GLFWlibraryWayland
// Drag operation state
struct {
struct wl_data_source* source;
unsigned char** items_data;
size_t* items_sizes;
char** items_mimes;
int item_count;
char** mimes; // Array of MIME type strings
int mime_count; // Number of MIME types
_GLFWwindow* window; // Window that initiated the drag
GLFWDragSourceData* current_request; // Current data request being processed
} drag;
} _GLFWlibraryWayland;

205
glfw/wl_window.c vendored
View file

@ -3006,37 +3006,83 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) {
// Drag operation implementation
static void cleanup_drag_source_data(GLFWDragSourceData* data) {
if (!data) return;
if (data->write_fd >= 0) {
close(data->write_fd);
data->write_fd = -1;
}
free(data->mime_type);
free(data);
}
static void cleanup_drag(void) {
// Notify the application that the drag source is closed
if (_glfw.wl.drag.window && _glfw.wl.drag.window->callbacks.dragSource) {
_glfwInputDragSourceRequest(_glfw.wl.drag.window, NULL, NULL);
}
// Clean up any pending data request
if (_glfw.wl.drag.current_request) {
cleanup_drag_source_data(_glfw.wl.drag.current_request);
_glfw.wl.drag.current_request = NULL;
}
// 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 = NULL;
}
static void
drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type, int fd) {
// Find the matching MIME type and send its data
for (int i = 0; i < _glfw.wl.drag.item_count; i++) {
if (strcmp(_glfw.wl.drag.items_mimes[i], mime_type) == 0) {
write_all(fd, (const char*)_glfw.wl.drag.items_data[i], _glfw.wl.drag.items_sizes[i]);
break;
}
if (!_glfw.wl.drag.window) {
close(fd);
return;
}
close(fd);
// 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 ? _glfw.wl.drag.window->id : 0;
request->mime_type = _glfw_strdup(mime_type);
request->write_fd = fd;
request->finished = false;
request->error_code = 0;
if (!request->mime_type) {
cleanup_drag_source_data(request);
return;
}
// Store as current request
if (_glfw.wl.drag.current_request) {
cleanup_drag_source_data(_glfw.wl.drag.current_request);
}
_glfw.wl.drag.current_request = request;
// Notify the application via callback
_glfwInputDragSourceRequest(_glfw.wl.drag.window, mime_type, request);
}
static void
drag_source_cancelled(void *data UNUSED, struct wl_data_source *source) {
// Clean up drag data
if (_glfw.wl.drag.source == source) {
for (int i = 0; i < _glfw.wl.drag.item_count; i++) {
free(_glfw.wl.drag.items_data[i]);
free(_glfw.wl.drag.items_mimes[i]);
}
free(_glfw.wl.drag.items_data);
free(_glfw.wl.drag.items_sizes);
free(_glfw.wl.drag.items_mimes);
_glfw.wl.drag.items_data = NULL;
_glfw.wl.drag.items_sizes = NULL;
_glfw.wl.drag.items_mimes = NULL;
_glfw.wl.drag.item_count = 0;
cleanup_drag();
_glfw.wl.drag.source = NULL;
}
wl_data_source_destroy(source);
_glfw.wl.drag.source = NULL;
if (source) {
wl_data_source_destroy(source);
}
}
static void
@ -3065,67 +3111,66 @@ static const struct wl_data_source_listener drag_source_listener = {
.dnd_finished = drag_source_dnd_finished,
};
void
_glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) {
if (_glfw.wl.drag.source) {
drag_source_cancelled(NULL, _glfw.wl.drag.source);
_glfw.wl.drag.source = NULL;
}
}
int
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_count, const GLFWimage* thumbnail, GLFWDragOperationType operation) {
_glfwPlatformStartDrag(_GLFWwindow* window, const char* const* mime_types, int mime_count, const GLFWimage* thumbnail, int operations) {
if (!_glfw.wl.dataDeviceManager) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device manager not available");
return false;
return EIO;
}
if (!_glfw.wl.dataDevice) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device not available");
return false;
return EIO;
}
// Clean up any existing drag operation
if (_glfw.wl.drag.source) drag_source_cancelled(NULL, _glfw.wl.drag.source);
_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 false;
return EIO;
}
// Set the DND action based on operation type
// Set the DND action based on operation type (bitfield)
uint32_t wl_actions = 0;
switch (operation) {
case GLFW_DRAG_OPERATION_COPY: wl_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; break;
case GLFW_DRAG_OPERATION_MOVE: wl_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; break;
case GLFW_DRAG_OPERATION_GENERIC:
wl_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; break;
}
if (operations & GLFW_DRAG_OPERATION_COPY)
wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
if (operations & GLFW_DRAG_OPERATION_MOVE)
wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
if (operations & GLFW_DRAG_OPERATION_GENERIC)
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 drag data (copy the data)
_glfw.wl.drag.items_data = calloc(item_count, sizeof(unsigned char*));
_glfw.wl.drag.items_sizes = calloc(item_count, sizeof(size_t));
_glfw.wl.drag.items_mimes = calloc(item_count, sizeof(char*));
_glfw.wl.drag.item_count = item_count;
// 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 = window;
if (!_glfw.wl.drag.items_data || !_glfw.wl.drag.items_sizes || !_glfw.wl.drag.items_mimes) {
drag_source_cancelled(NULL, _glfw.wl.drag.source);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag data");
return false;
if (!_glfw.wl.drag.mimes) {
_glfwPlatformCancelDrag(window);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag MIME types");
return ENOMEM;
}
// Copy the data and offer MIME types
for (int i = 0; i < item_count; i++) {
_glfw.wl.drag.items_data[i] = malloc(items[i].data_size);
if (!_glfw.wl.drag.items_data[i]) {
drag_source_cancelled(NULL, _glfw.wl.drag.source);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag item data");
return false;
// 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;
}
memcpy(_glfw.wl.drag.items_data[i], items[i].data, items[i].data_size);
_glfw.wl.drag.items_sizes[i] = items[i].data_size;
_glfw.wl.drag.items_mimes[i] = _glfw_strdup(items[i].mime_type);
if (!_glfw.wl.drag.items_mimes[i]) {
drag_source_cancelled(NULL, _glfw.wl.drag.source);
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to allocate drag item MIME type");
return false;
}
wl_data_source_offer(_glfw.wl.drag.source, items[i].mime_type);
wl_data_source_offer(_glfw.wl.drag.source, mime_types[i]);
}
wl_data_source_add_listener(_glfw.wl.drag.source, &drag_source_listener, NULL);
@ -3158,7 +3203,51 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWdragitem* items, int item_
wl_surface_destroy(icon_surface);
}
return true;
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;
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;
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;
return -errno;
}
return written;
}
void

12
glfw/x11_platform.h vendored
View file

@ -394,13 +394,13 @@ typedef struct _GLFWlibraryX11
// Drag source state
struct {
Window source_window;
unsigned char** items_data;
size_t* items_sizes;
char** items_mimes;
int item_count;
Atom* type_atoms;
Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink
char** mimes; // Array of MIME type strings
int mime_count; // Number of MIME types
Atom* type_atoms; // Atoms for each MIME type
Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink
bool active;
_GLFWwindow* window; // Window that initiated the drag
GLFWDragSourceData* current_request; // Current data request being processed
} drag;
struct {

179
glfw/x11_window.c vendored
View file

@ -3711,86 +3711,93 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
}
// Helper function to clean up drag source data
static void cleanupDragSource(void) {
if (_glfw.x11.drag.items_data) {
for (int i = 0; i < _glfw.x11.drag.item_count; i++) {
free(_glfw.x11.drag.items_data[i]);
free(_glfw.x11.drag.items_mimes[i]);
}
free(_glfw.x11.drag.items_data);
free(_glfw.x11.drag.items_sizes);
free(_glfw.x11.drag.items_mimes);
free(_glfw.x11.drag.type_atoms);
_glfw.x11.drag.items_data = NULL;
_glfw.x11.drag.items_sizes = NULL;
_glfw.x11.drag.items_mimes = NULL;
_glfw.x11.drag.type_atoms = NULL;
_glfw.x11.drag.item_count = 0;
_glfw.x11.drag.source_window = None;
_glfw.x11.drag.active = false;
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->mime_type);
free(data);
}
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 any pending data request
if (_glfw.x11.drag.current_request) {
cleanup_x11_drag_source_data(_glfw.x11.drag.current_request);
_glfw.x11.drag.current_request = NULL;
}
// 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 GLFWdragitem* items,
int item_count,
const char* const* mime_types,
int mime_count,
const GLFWimage* thumbnail UNUSED,
GLFWDragOperationType operation) {
int operations) {
// Clean up any existing drag operation
cleanupDragSource();
// Set the drag action based on operation type
switch (operation) {
case GLFW_DRAG_OPERATION_COPY:
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
break;
case GLFW_DRAG_OPERATION_MOVE:
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionMove;
break;
case GLFW_DRAG_OPERATION_GENERIC:
_glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy;
break;
// 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 drag data (copy the data)
_glfw.x11.drag.items_data = calloc(item_count, sizeof(unsigned char*));
_glfw.x11.drag.items_sizes = calloc(item_count, sizeof(size_t));
_glfw.x11.drag.items_mimes = calloc(item_count, sizeof(char*));
_glfw.x11.drag.type_atoms = calloc(item_count, sizeof(Atom));
_glfw.x11.drag.item_count = item_count;
// 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.items_data || !_glfw.x11.drag.items_sizes ||
!_glfw.x11.drag.items_mimes || !_glfw.x11.drag.type_atoms) {
if (!_glfw.x11.drag.mimes || !_glfw.x11.drag.type_atoms) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag data");
return false;
return ENOMEM;
}
// Copy the data and create atoms for MIME types
for (int i = 0; i < item_count; i++) {
_glfw.x11.drag.items_data[i] = malloc(items[i].data_size);
if (!_glfw.x11.drag.items_data[i]) {
// 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 item data");
return false;
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag MIME type");
return ENOMEM;
}
memcpy(_glfw.x11.drag.items_data[i], items[i].data, items[i].data_size);
_glfw.x11.drag.items_sizes[i] = items[i].data_size;
_glfw.x11.drag.items_mimes[i] = _glfw_strdup(items[i].mime_type);
if (!_glfw.x11.drag.items_mimes[i]) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to allocate drag item MIME type");
return false;
}
_glfw.x11.drag.type_atoms[i] = XInternAtom(_glfw.x11.display, items[i].mime_type, False);
_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 (item_count > 3) {
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, item_count);
(unsigned char*)_glfw.x11.drag.type_atoms, mime_count);
}
// Take ownership of XdndSelection
@ -3800,7 +3807,7 @@ int _glfwPlatformStartDrag(_GLFWwindow* window,
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection) != window->x11.handle) {
cleanupDragSource();
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to acquire XdndSelection ownership");
return false;
return EIO;
}
_glfw.x11.drag.active = true;
@ -3821,7 +3828,61 @@ int _glfwPlatformStartDrag(_GLFWwindow* window,
// event loop. For now, we set up the data source so the application can
// handle its own drag tracking if needed.
return true;
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 typically set properties for SelectionRequest events
// The write_fd is used if we set up a pipe-based transfer
// 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;
}
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;
}
return 0;
}
// For X11, data is typically set via XChangeProperty in response to SelectionRequest
// Store the data in platform_data for the SelectionRequest handler
// Non-blocking write - retry on EINTR, return 0 on would-block
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;
return -errno;
}
return written;
}
// No fd available, all data is accepted (buffered by platform_data)
return (ssize_t)size;
}
void _glfwPlatformUpdateDragState(_GLFWwindow* window) {

6
kitty/glfw-wrapper.c generated
View file

@ -362,9 +362,15 @@ load_glfw(const char* path) {
*(void **) (&glfwSetDragCallback_impl) = dlsym(handle, "glfwSetDragCallback");
if (glfwSetDragCallback_impl == NULL) fail("Failed to load glfw function glfwSetDragCallback 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());
*(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 **) (&glfwUpdateDragState_impl) = dlsym(handle, "glfwUpdateDragState");
if (glfwUpdateDragState_impl == NULL) fail("Failed to load glfw function glfwUpdateDragState with error: %s", dlerror());

51
kitty/glfw-wrapper.h generated
View file

@ -1572,25 +1572,40 @@ typedef enum {
GLFW_DRAG_OPERATION_GENERIC = 4
} GLFWDragOperationType;
/*! @brief Drag data item.
/*! @brief Opaque drag source data handle.
*
* This structure describes a single item of drag data with its MIME type.
*
* @sa @ref drag_start
* @sa @ref glfwStartDrag
* 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 GLFWdragitem {
/*! The MIME type of this data item (e.g., "text/plain", "image/png"). */
const char* mime_type;
/*! Pointer to the binary data. */
const unsigned char* data;
/*! Size of the data in bytes. */
size_t data_size;
} GLFWdragitem;
typedef struct GLFWDragSourceData GLFWDragSourceData;
/*! @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);
/*! @brief The function pointer type for drag event callbacks.
*
@ -2315,10 +2330,18 @@ typedef GLFWdragfun (*glfwSetDragCallback_func)(GLFWwindow*, GLFWdragfun);
GFW_EXTERN glfwSetDragCallback_func glfwSetDragCallback_impl;
#define glfwSetDragCallback glfwSetDragCallback_impl
typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWdragitem*, int, const GLFWimage*, GLFWDragOperationType);
typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdragsourcefun);
GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl;
#define glfwSetDragSourceCallback glfwSetDragSourceCallback_impl
typedef int (*glfwStartDrag_func)(GLFWwindow*, const char* const*, int, 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 void (*glfwUpdateDragState_func)(GLFWwindow*);
GFW_EXTERN glfwUpdateDragState_func glfwUpdateDragState_impl;
#define glfwUpdateDragState glfwUpdateDragState_impl