mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-06-27 11:21:32 +00:00
parent
a7b8e880c9
commit
5ea35cbbfc
12 changed files with 856 additions and 271 deletions
6
glfw/cocoa_platform.h
vendored
6
glfw/cocoa_platform.h
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
137
glfw/glfw3.h
vendored
|
|
@ -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
40
glfw/input.c
vendored
|
|
@ -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
17
glfw/internal.h
vendored
|
|
@ -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
24
glfw/null_window.c
vendored
|
|
@ -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
8
glfw/wl_platform.h
vendored
|
|
@ -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
205
glfw/wl_window.c
vendored
|
|
@ -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
12
glfw/x11_platform.h
vendored
|
|
@ -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
179
glfw/x11_window.c
vendored
|
|
@ -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
6
kitty/glfw-wrapper.c
generated
|
|
@ -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
51
kitty/glfw-wrapper.h
generated
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue