mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-06-27 03:11:46 +00:00
Port Cocoa drag source backend to new API
This commit is contained in:
parent
e5eb63fcd0
commit
a2b7a0cd6d
9 changed files with 298 additions and 459 deletions
7
glfw/cocoa_platform.h
vendored
7
glfw/cocoa_platform.h
vendored
|
|
@ -178,12 +178,6 @@ typedef struct _GLFWwindowNS
|
|||
// Cached MIME types from drag enter (for move events)
|
||||
_GLFWDropData drop_data;
|
||||
|
||||
// Pending drag source data requests (for cleanup on cancellation)
|
||||
// Current drag operation type for NSDraggingSource
|
||||
int dragOperations; // Bitfield of GLFWDragOperationType
|
||||
GLFWDragSourceData** pendingDragSourceData;
|
||||
int pendingDragSourceDataCount;
|
||||
int pendingDragSourceDataCapacity;
|
||||
} _GLFWwindowNS;
|
||||
|
||||
// Cocoa-specific global data
|
||||
|
|
@ -221,7 +215,6 @@ typedef struct _GLFWlibraryNS
|
|||
|
||||
// the callback to handle url open events
|
||||
GLFWhandleurlopen url_open_callback;
|
||||
|
||||
} _GLFWlibraryNS;
|
||||
|
||||
// Cocoa-specific per-monitor data
|
||||
|
|
|
|||
|
|
@ -781,211 +781,31 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||
// File Promise Provider Delegate for async drag data {{{
|
||||
|
||||
// Structure to hold async drag state
|
||||
typedef struct {
|
||||
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;
|
||||
|
||||
// Helper function to clean up a single drag source data
|
||||
static void
|
||||
cleanup_ns_drag_source_data(GLFWDragSourceData* data) {
|
||||
if (!data) return;
|
||||
if (data->platform_data) {
|
||||
GLFWFilePromiseState* state = (GLFWFilePromiseState*)data->platform_data;
|
||||
// If the data wasn't finished, call completion with error
|
||||
if (!data->finished && state->completionHandler) {
|
||||
NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ECANCELED userInfo:nil];
|
||||
state->completionHandler(error);
|
||||
Block_release(state->completionHandler);
|
||||
state->completionHandler = nil;
|
||||
}
|
||||
if (state->fileHandle) {
|
||||
@try {
|
||||
[state->fileHandle closeFile];
|
||||
} @catch (NSException* e) {
|
||||
(void)e;
|
||||
}
|
||||
[state->fileHandle release];
|
||||
state->fileHandle = nil;
|
||||
}
|
||||
free(state);
|
||||
}
|
||||
free(data->mime_type);
|
||||
free(data);
|
||||
}
|
||||
|
||||
// Add a drag source data to the pending array for tracking
|
||||
static bool
|
||||
add_ns_pending_drag_source_data(_GLFWwindow* window, GLFWDragSourceData* data) {
|
||||
if (!window || !data) return false;
|
||||
|
||||
// Grow array if needed
|
||||
if (window->ns.pendingDragSourceDataCount >= window->ns.pendingDragSourceDataCapacity) {
|
||||
// Cap maximum capacity to prevent excessive memory use
|
||||
if (window->ns.pendingDragSourceDataCapacity >= 512) {
|
||||
return false;
|
||||
}
|
||||
int new_capacity = window->ns.pendingDragSourceDataCapacity ? window->ns.pendingDragSourceDataCapacity * 2 : 4;
|
||||
GLFWDragSourceData** new_array = realloc(window->ns.pendingDragSourceData,
|
||||
new_capacity * sizeof(GLFWDragSourceData*));
|
||||
if (!new_array) return false;
|
||||
window->ns.pendingDragSourceData = new_array;
|
||||
window->ns.pendingDragSourceDataCapacity = new_capacity;
|
||||
}
|
||||
|
||||
window->ns.pendingDragSourceData[window->ns.pendingDragSourceDataCount++] = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove a specific drag source data from the pending array
|
||||
static void
|
||||
remove_ns_pending_drag_source_data(_GLFWwindow* window, GLFWDragSourceData* data) {
|
||||
if (!window || !data) return;
|
||||
|
||||
for (int i = 0; i < window->ns.pendingDragSourceDataCount; i++) {
|
||||
if (window->ns.pendingDragSourceData[i] == data) {
|
||||
// Shift remaining elements
|
||||
for (int j = i; j < window->ns.pendingDragSourceDataCount - 1; j++) {
|
||||
window->ns.pendingDragSourceData[j] = window->ns.pendingDragSourceData[j + 1];
|
||||
}
|
||||
window->ns.pendingDragSourceDataCount--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up all pending drag source data for a window
|
||||
static void
|
||||
cleanup_all_ns_pending_drag_source_data(_GLFWwindow* window) {
|
||||
if (!window) return;
|
||||
|
||||
for (int i = 0; i < window->ns.pendingDragSourceDataCount; i++) {
|
||||
cleanup_ns_drag_source_data(window->ns.pendingDragSourceData[i]);
|
||||
}
|
||||
free(window->ns.pendingDragSourceData);
|
||||
window->ns.pendingDragSourceData = NULL;
|
||||
window->ns.pendingDragSourceDataCount = 0;
|
||||
window->ns.pendingDragSourceDataCapacity = 0;
|
||||
}
|
||||
|
||||
@interface GLFWFilePromiseProviderDelegate : NSObject <NSFilePromiseProviderDelegate>
|
||||
{
|
||||
GLFWid windowId;
|
||||
GLFWid windowId, instanceId;
|
||||
char* mimeType; // MIME type for this provider
|
||||
NSFileHandle *file_handle;
|
||||
NSURL *file_url;
|
||||
void (^completion_handler)(NSError*);
|
||||
}
|
||||
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime;
|
||||
|
||||
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime instanceId:(GLFWid)iid;
|
||||
- (void)request_drag_data;
|
||||
- (void)end_transfer:(int)errorCode;
|
||||
- (void)end_transfer_with_error:(NSError*)err;
|
||||
- (bool)is_mimetype:(const char*)mime_type;
|
||||
@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;
|
||||
@interface GLFWDraggingSource : NSObject <NSDraggingSource> {
|
||||
}
|
||||
|
||||
- (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) {
|
||||
UTType *type = [UTType typeWithMIMEType:@(mimeType)];
|
||||
extension = type.preferredFilenameExtension;
|
||||
}
|
||||
return [NSString stringWithFormat:@"kitty-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
|
||||
char *mt = _glfw_strdup(mimeType);
|
||||
GLFWFilePromiseState* state = calloc(1, sizeof(GLFWFilePromiseState));
|
||||
if (!state || !mt) {
|
||||
free(source_data); free(mt); free(state);
|
||||
[fileHandle closeFile];
|
||||
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]);
|
||||
return;
|
||||
}
|
||||
|
||||
state->completionHandler = [completionHandler copy];
|
||||
state->fileHandle = [fileHandle retain];
|
||||
state->finished = false;
|
||||
state->errorCode = 0;
|
||||
|
||||
source_data->window_id = windowId;
|
||||
source_data->mime_type = mt;
|
||||
source_data->write_fd = -1;
|
||||
source_data->finished = false;
|
||||
source_data->error_code = 0;
|
||||
source_data->platform_data = state;
|
||||
|
||||
// Track this source data for cleanup on cancellation
|
||||
if (!add_ns_pending_drag_source_data(window, source_data)) {
|
||||
// Call completion handler with memory error before cleanup
|
||||
completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]);
|
||||
// Mark as finished to prevent cleanup_ns_drag_source_data from calling completionHandler again
|
||||
source_data->finished = true;
|
||||
cleanup_ns_drag_source_data(source_data);
|
||||
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>
|
||||
@interface GLFWContentView : NSView <NSTextInputClient>
|
||||
{
|
||||
_GLFWwindow* window;
|
||||
NSTrackingArea* trackingArea;
|
||||
|
|
@ -995,11 +815,12 @@ cleanup_all_ns_pending_drag_source_data(_GLFWwindow* window) {
|
|||
bool marked_text_cleared_by_insert;
|
||||
int in_key_handler;
|
||||
NSString *input_source_at_last_key_event;
|
||||
GLFWDraggingSource *dragging_source;
|
||||
}
|
||||
|
||||
- (void) removeGLFWWindow;
|
||||
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
||||
|
||||
- (GLFWDraggingSource*)draggingSource;
|
||||
@end
|
||||
|
||||
@implementation GLFWContentView
|
||||
|
|
@ -1012,6 +833,7 @@ cleanup_all_ns_pending_drag_source_data(_GLFWwindow* window) {
|
|||
window = initWindow;
|
||||
trackingArea = nil;
|
||||
input_context = [[GLFWTextInputContext alloc] initWithClient:self];
|
||||
dragging_source = [[GLFWDraggingSource alloc] init];
|
||||
markedText = [[NSMutableAttributedString alloc] init];
|
||||
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
|
||||
input_source_at_last_key_event = nil;
|
||||
|
|
@ -1040,34 +862,23 @@ cleanup_all_ns_pending_drag_source_data(_GLFWwindow* window) {
|
|||
{
|
||||
[trackingArea release];
|
||||
[markedText release];
|
||||
[dragging_source release];
|
||||
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
|
||||
[input_context release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) removeGLFWWindow
|
||||
{
|
||||
window = NULL;
|
||||
}
|
||||
- (void) removeGLFWWindow { window = NULL; }
|
||||
|
||||
- (_GLFWwindow*)glfwWindow {
|
||||
return window;
|
||||
}
|
||||
- (GLFWDraggingSource*)draggingSource { return dragging_source; }
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return window && [window->ns.object isOpaque];
|
||||
}
|
||||
- (_GLFWwindow*)glfwWindow { return window; }
|
||||
|
||||
- (BOOL)canBecomeKeyView
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)isOpaque { return window && [window->ns.object isOpaque]; }
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)canBecomeKeyView { return YES; }
|
||||
|
||||
- (BOOL)acceptsFirstResponder { return YES; }
|
||||
|
||||
- (void) viewWillStartLiveResize
|
||||
{
|
||||
|
|
@ -1625,10 +1436,7 @@ update_drop_state(_GLFWwindow *window, size_t mime_count) {
|
|||
|
||||
// Pre-allocate C array for MIME types
|
||||
const char** mime_array = (const char**)calloc(max_types, sizeof(const char*));
|
||||
if (!mime_array) {
|
||||
int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, NULL, NULL);
|
||||
return accepted ? NSDragOperationGeneric : NSDragOperationNone;
|
||||
}
|
||||
if (!mime_array) return NSDragOperationNone;
|
||||
|
||||
size_t mime_count = 0;
|
||||
|
||||
|
|
@ -1877,37 +1685,6 @@ _glfwPlatformEndDrop(GLFWwindow *w UNUSED, GLFWDragOperationType op UNUSED) {
|
|||
}
|
||||
// }}}
|
||||
|
||||
// NSDraggingSource protocol methods
|
||||
- (NSDragOperation)draggingSession:(NSDraggingSession *)session
|
||||
sourceOperationMaskForDraggingContext:(NSDraggingContext)context
|
||||
{
|
||||
(void)session;
|
||||
(void)context;
|
||||
// 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
|
||||
endedAtPoint:(NSPoint)screenPoint
|
||||
operation:(NSDragOperation)operation
|
||||
{
|
||||
(void)session;
|
||||
(void)screenPoint;
|
||||
if (operation == NSDragOperationNone) { // drag was canceled
|
||||
// Clean up all pending drag source data
|
||||
cleanup_all_ns_pending_drag_source_data(window);
|
||||
// Notify the application that the drag source is closed
|
||||
_glfwInputDragSourceRequest(window, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [markedText length] > 0;
|
||||
|
|
@ -4099,201 +3876,278 @@ void _glfwCocoaPostEmptyEvent(void) {
|
|||
[NSApp postEvent:event atStart:YES];
|
||||
}
|
||||
|
||||
// Drag source implementation {{{
|
||||
@implementation GLFWDraggingSource
|
||||
- (NSDragOperation)draggingSession:(NSDraggingSession*)session
|
||||
sourceOperationMaskForDraggingContext:(NSDraggingContext)context
|
||||
{
|
||||
(void)session; (void)context;
|
||||
// Return the operation based on the stored drag operations bitfield
|
||||
NSDragOperation ops = NSDragOperationCopy;
|
||||
int q = _glfw.drag.operations;
|
||||
if (q & GLFW_DRAG_OPERATION_COPY) ops |= NSDragOperationCopy;
|
||||
if (q & GLFW_DRAG_OPERATION_MOVE) ops |= NSDragOperationMove;
|
||||
if (q & GLFW_DRAG_OPERATION_GENERIC) ops |= NSDragOperationGeneric;
|
||||
return ops;
|
||||
}
|
||||
|
||||
- (void)draggingSession:(NSDraggingSession *)session
|
||||
willBeginAtPoint:(NSPoint)screenPoint
|
||||
{
|
||||
(void)session; (void)screenPoint;
|
||||
}
|
||||
|
||||
- (void)draggingSession:(NSDraggingSession *)session
|
||||
movedToPoint:(NSPoint)screenPoint
|
||||
{
|
||||
(void)session; (void)screenPoint;
|
||||
}
|
||||
|
||||
|
||||
- (void)draggingSession:(NSDraggingSession *)session
|
||||
endedAtPoint:(NSPoint)screenPoint
|
||||
operation:(NSDragOperation)operation
|
||||
{
|
||||
(void)session; (void)screenPoint;
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
GLFWDragEvent ev = {0};
|
||||
switch(operation) {
|
||||
case NSDragOperationCopy: case NSDragOperationLink: ev.action = GLFW_DRAG_OPERATION_COPY; break;
|
||||
case NSDragOperationMove: case NSDragOperationDelete: ev.action = GLFW_DRAG_OPERATION_MOVE; break;
|
||||
case NSDragOperationNone: break;
|
||||
default: ev.action = GLFW_DRAG_OPERATION_GENERIC; break;
|
||||
}
|
||||
ev.type = (operation == NSDragOperationNone) ? GLFW_DRAG_CANCELLED : GLFW_DRAG_DROPPED;
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
if (operation == NSDragOperationNone) _glfwFreeDragSourceData();
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static NSMutableArray<GLFWFilePromiseProviderDelegate*> *file_promise_providers = nil;
|
||||
|
||||
int
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWDragSourceItem *items, int item_count, const GLFWimage* thumbnail, int operations) {
|
||||
// cleanup stored data from previous drag
|
||||
cleanup_all_ns_pending_drag_source_data(window);
|
||||
if (!items || !item_count) { cleanup_all_ns_pending_drag_source_data(window); return 0; }
|
||||
|
||||
// Store the operations for the dragging source callback
|
||||
window->ns.dragOperations = operations;
|
||||
|
||||
@autoreleasepool {
|
||||
// Create dragging items array - one NSFilePromiseProvider per MIME type
|
||||
NSMutableArray<NSDraggingItem*>* dragItems = [[NSMutableArray alloc] init];
|
||||
|
||||
for (int i = 0; i < mime_count; i++) {
|
||||
NSString* utiString = mime_to_uti(mime_types[i]);
|
||||
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autoreleasepool{
|
||||
if (file_promise_providers) {
|
||||
for (NSInteger i = [file_promise_providers count] - 1; i >= 0; i--) {
|
||||
GLFWFilePromiseProviderDelegate* d = file_promise_providers[i];
|
||||
[d end_transfer:EINVAL];
|
||||
}
|
||||
}
|
||||
NSMutableArray<NSDraggingItem*>* dragItems = [[[NSMutableArray alloc] init] autorelease];
|
||||
for (size_t i = 0; i < _glfw.drag.item_count; i++) {
|
||||
NSString* utiString = mime_to_uti(_glfw.drag.items[i].mime_type);
|
||||
id w;
|
||||
if (_glfw.drag.items[i].optional_data) {
|
||||
NSPasteboardItem *pbItem = [[[NSPasteboardItem alloc] init] autorelease];
|
||||
NSData *data = [NSData dataWithBytes:_glfw.drag.items[i].optional_data length:_glfw.drag.items[i].data_size];
|
||||
[pbItem setData:data forType:utiString];
|
||||
w = pbItem;
|
||||
} else {
|
||||
// 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];
|
||||
|
||||
GLFWFilePromiseProviderDelegate* delegate = [[[GLFWFilePromiseProviderDelegate alloc]
|
||||
initWithWindow:window mimeType:_glfw.drag.items[i].mime_type instanceId:_glfw.drag.instance_id] autorelease];
|
||||
NSFilePromiseProvider *provider = [[[NSFilePromiseProvider alloc]
|
||||
initWithFileType:utiString delegate:delegate] autorelease];
|
||||
// Store the delegate in the provider's user info so it's retained
|
||||
provider.userInfo = delegate;
|
||||
w = provider;
|
||||
}
|
||||
NSDraggingItem* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:w] autorelease];
|
||||
|
||||
// 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 (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)];
|
||||
if (imageRep) {
|
||||
memcpy([imageRep bitmapData], thumbnail->pixels, thumbnail->width * thumbnail->height * 4);
|
||||
NSWindow *nsw = window->ns.object;
|
||||
CGFloat scaleFactor = [nsw backingScaleFactor];
|
||||
if (scaleFactor == 0) scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
||||
NSSize pointSize = NSMakeSize(thumbnail->width / scaleFactor, thumbnail->height / scaleFactor);
|
||||
[imageRep setSize:pointSize];
|
||||
NSImage* image = [[NSImage alloc] initWithSize: pointSize];
|
||||
if (image) {
|
||||
[image addRepresentation:imageRep];
|
||||
|
||||
[dragItem setDraggingFrame:NSMakeRect(0, 0, thumbnail->width, thumbnail->height)
|
||||
contents:image];
|
||||
[dragItem setDraggingFrame:NSMakeRect(0, 0, image.size.width, image.size.height) contents:image];
|
||||
[image release];
|
||||
}
|
||||
} else {
|
||||
[dragItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil];
|
||||
[imageRep release];
|
||||
}
|
||||
|
||||
[dragItems addObject:dragItem];
|
||||
} else {
|
||||
[dragItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil];
|
||||
}
|
||||
|
||||
// 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;
|
||||
[dragItems addObject: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);
|
||||
|
||||
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) {
|
||||
GLFWContentView *v = window->ns.view;
|
||||
[v beginDraggingSessionWithItems:dragItems event:event source:[v draggingSource]];
|
||||
return 0;
|
||||
}
|
||||
return EIO;
|
||||
}}
|
||||
|
||||
|
||||
@implementation GLFWFilePromiseProviderDelegate
|
||||
|
||||
- (void)end_transfer_with_error:(NSError*)err {
|
||||
if (err && file_url) {
|
||||
NSError *error;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
[fileManager removeItemAtURL:file_url error:&error];
|
||||
}
|
||||
if (file_handle) [file_handle release];
|
||||
file_handle = nil;
|
||||
if (completion_handler) {
|
||||
completion_handler(err);
|
||||
Block_release(completion_handler);
|
||||
completion_handler = nil;
|
||||
}
|
||||
[file_promise_providers removeObject:self];
|
||||
}
|
||||
|
||||
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;
|
||||
- (void)end_transfer:(int)errorCode {
|
||||
[self end_transfer_with_error:errorCode ? [NSError errorWithDomain:NSPOSIXErrorDomain code:errorCode userInfo:nil] : nil];
|
||||
}
|
||||
|
||||
GLFWFilePromiseState* state = (GLFWFilePromiseState*)source_data->platform_data;
|
||||
- (bool)is_mimetype:(const char*)q { return strcmp(q, mimeType) == 0; }
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Remove from pending list and clean up
|
||||
_GLFWwindow* window = _glfwWindowForId(source_data->window_id);
|
||||
if (window) {
|
||||
remove_ns_pending_drag_source_data(window, source_data);
|
||||
}
|
||||
// source_data->finished is true, so cleanup_ns_drag_source_data won't call completionHandler again
|
||||
cleanup_ns_drag_source_data(source_data);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Remove from pending list and clean up
|
||||
_GLFWwindow* window = _glfwWindowForId(source_data->window_id);
|
||||
if (window) {
|
||||
remove_ns_pending_drag_source_data(window, source_data);
|
||||
}
|
||||
// source_data->finished is true, so cleanup_ns_drag_source_data won't call completionHandler again
|
||||
cleanup_ns_drag_source_data(source_data);
|
||||
|
||||
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, *)) {
|
||||
- (void)request_drag_data {
|
||||
if (instanceId != _glfw.drag.instance_id) { [self end_transfer:EINVAL]; return; }
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (!window) { [self end_transfer:EINVAL]; return; }
|
||||
bool keep_going = true;
|
||||
while (keep_going) {
|
||||
GLFWDragEvent ev = {.type=GLFW_DRAG_DATA_REQUEST, .mime_type=mimeType};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
if (ev.err_num) {
|
||||
keep_going = false;
|
||||
if (ev.err_num != EAGAIN) [self end_transfer:ev.err_num];
|
||||
} else {
|
||||
if (ev.data_sz) {
|
||||
NSData* nsData = [NSData dataWithBytes:ev.data length:ev.data_sz];
|
||||
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;
|
||||
if (![file_handle writeData:nsData error:&error]) {
|
||||
keep_going = false;
|
||||
[self end_transfer_with_error:error];
|
||||
}
|
||||
} 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];
|
||||
keep_going = false;
|
||||
[self end_transfer_with_error:nil];
|
||||
}
|
||||
// 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;
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
// No file handle, consider all data accepted
|
||||
return (ssize_t)size;
|
||||
}
|
||||
|
||||
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime instanceId:(GLFWid) instance_id {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
windowId = initWindow ? initWindow->id : 0;
|
||||
mimeType = _glfw_strdup(mime);
|
||||
instanceId = instance_id;
|
||||
if (file_promise_providers == nil) file_promise_providers = [NSMutableArray array];
|
||||
[file_promise_providers addObject:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
free(mimeType); mimeType = NULL;
|
||||
if (file_url) [file_url release];
|
||||
file_url = nil;
|
||||
[self end_transfer:EINVAL];
|
||||
[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) {
|
||||
UTType *type = [UTType typeWithMIMEType:@(mimeType)];
|
||||
extension = type.preferredFilenameExtension;
|
||||
}
|
||||
return [NSString stringWithFormat:@"kitty-drag-source-%@.%@", [[NSUUID UUID] UUIDString], extension];
|
||||
}
|
||||
|
||||
- (void)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider
|
||||
writePromiseToURL:(NSURL*)url
|
||||
completionHandler:(void (^)(NSError*))completionHandler
|
||||
{
|
||||
(void)filePromiseProvider;
|
||||
_GLFWwindow* window = _glfwWindowForId(windowId);
|
||||
if (!window || instanceId != _glfw.drag.instance_id) {
|
||||
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);
|
||||
NSError *error;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
[fileManager removeItemAtURL:url error:&error];
|
||||
return;
|
||||
}
|
||||
file_handle = fileHandle; completion_handler = completionHandler;
|
||||
file_url = [url retain];
|
||||
[self request_drag_data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void
|
||||
_glfwPlatformFreeDragSourceData(void) { }
|
||||
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type) {
|
||||
if (!file_promise_providers) return 0;
|
||||
for (GLFWFilePromiseProviderDelegate *d in file_promise_providers) {
|
||||
if ([d is_mimetype:mime_type]) [d request_drag_data];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
6
glfw/input.c
vendored
6
glfw/input.c
vendored
|
|
@ -1155,7 +1155,9 @@ _glfwFreeDragSourceData(void) {
|
|||
}
|
||||
free(_glfw.drag.items);
|
||||
}
|
||||
GLFWid iid = _glfw.drag.instance_id;
|
||||
memset(&_glfw.drag, 0, sizeof(_glfw.drag));
|
||||
_glfw.drag.instance_id = iid;
|
||||
}
|
||||
|
||||
GLFWAPI int
|
||||
|
|
@ -1165,6 +1167,7 @@ glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_c
|
|||
_GLFW_REQUIRE_INIT_OR_RETURN(EINVAL);
|
||||
if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type);
|
||||
_glfwFreeDragSourceData();
|
||||
_glfw.drag.instance_id++;
|
||||
if (!items || !item_count) return 0;
|
||||
_glfw.drag.items = calloc(item_count, sizeof(_glfw.drag.items[0]));
|
||||
if (!_glfw.drag.items) return ENOMEM;
|
||||
|
|
@ -1183,7 +1186,8 @@ glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_c
|
|||
_glfw.drag.items[i].data_size = items[i].data_size;
|
||||
}
|
||||
_glfw.drag.window_id = window->id;
|
||||
int ans = _glfwPlatformStartDrag(window, thumbnail, operations);
|
||||
_glfw.drag.operations = operations;
|
||||
int ans = _glfwPlatformStartDrag(window, thumbnail);
|
||||
if (ans != 0) _glfwFreeDragSourceData();
|
||||
return ans;
|
||||
}
|
||||
|
|
|
|||
4
glfw/internal.h
vendored
4
glfw/internal.h
vendored
|
|
@ -667,7 +667,7 @@ struct _GLFWlibrary
|
|||
|
||||
struct {
|
||||
GLFWDragSourceItem *items; size_t item_count;
|
||||
GLFWid window_id;
|
||||
GLFWid window_id, instance_id; int operations;
|
||||
} drag;
|
||||
};
|
||||
|
||||
|
|
@ -832,7 +832,7 @@ ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, cha
|
|||
void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op);
|
||||
int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime);
|
||||
// Platform functions for drag source
|
||||
int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations);
|
||||
int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail);
|
||||
void _glfwFreeDragSourceData(void);
|
||||
void _glfwPlatformFreeDragSourceData(void);
|
||||
void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev);
|
||||
|
|
|
|||
24
glfw/null_window.c
vendored
24
glfw/null_window.c
vendored
|
|
@ -535,27 +535,13 @@ void _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED)
|
|||
{
|
||||
// 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)
|
||||
{
|
||||
int
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
||||
(void)window; (void)thumbnail;
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data UNUSED,
|
||||
const void* data UNUSED,
|
||||
size_t size UNUSED)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
void _glfwPlatformUpdateDragState(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
// No-op for null platform
|
||||
}
|
||||
void _glfwPlatformFreeDragSourceData(void) {}
|
||||
int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; }
|
||||
|
||||
const char** _glfwPlatformGetDropMimeTypes(GLFWDropData* drop UNUSED, int* count)
|
||||
{
|
||||
|
|
|
|||
1
glfw/wl_platform.h
vendored
1
glfw/wl_platform.h
vendored
|
|
@ -419,6 +419,7 @@ typedef struct _GLFWlibraryWayland
|
|||
size_t sz, offset;
|
||||
} *data_requests;
|
||||
size_t count, capacity;
|
||||
GLFWDragOperationType action;
|
||||
} drag;
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
|
|
|
|||
7
glfw/wl_window.c
vendored
7
glfw/wl_window.c
vendored
|
|
@ -3217,6 +3217,7 @@ drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint
|
|||
case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: op = GLFW_DRAG_OPERATION_COPY; break;
|
||||
case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: op = GLFW_DRAG_OPERATION_MOVE; break;
|
||||
}
|
||||
_glfw.wl.drag.action = op;
|
||||
GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED, .action=op};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
} else drag_source_cancelled(data, source);
|
||||
|
|
@ -3235,7 +3236,7 @@ static void
|
|||
drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source UNUSED) {
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
GLFWDragEvent ev = {.type=GLFW_DRAG_FINSHED};
|
||||
GLFWDragEvent ev = {.type=GLFW_DRAG_FINSHED, .action=_glfw.wl.drag.action};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
}
|
||||
_glfwFreeDragSourceData();
|
||||
|
|
@ -3268,7 +3269,7 @@ _glfwPlatformFreeDragSourceData(void) {
|
|||
}
|
||||
|
||||
int
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) {
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
||||
if (!_glfw.wl.dataDeviceManager) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device manager not available");
|
||||
return ENOTSUP;
|
||||
|
|
@ -3287,7 +3288,7 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int oper
|
|||
}
|
||||
|
||||
// Set the DND action based on operation type (bitfield)
|
||||
uint32_t wl_actions = 0;
|
||||
uint32_t wl_actions = 0; int operations = _glfw.drag.operations;
|
||||
if (operations & GLFW_DRAG_OPERATION_COPY)
|
||||
wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
|
||||
if (operations & GLFW_DRAG_OPERATION_MOVE)
|
||||
|
|
|
|||
4
glfw/x11_window.c
vendored
4
glfw/x11_window.c
vendored
|
|
@ -3815,8 +3815,8 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
|||
|
||||
// Drag source {{{
|
||||
int
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail, int operations) {
|
||||
(void)window; (void)thumbnail; (void)operations;
|
||||
_glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
||||
(void)window; (void)thumbnail;
|
||||
return ENOTSUP;
|
||||
}
|
||||
void _glfwPlatformFreeDragSourceData(void) {}
|
||||
|
|
|
|||
|
|
@ -2751,15 +2751,15 @@ start_drag_with_data(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|||
&os_window_id, &PyDict_Type, &data_map, &thumbnail_data, &thumbnail_sz, &width, &height, &operations)) return NULL;
|
||||
OSWindow *w = os_window_for_id(os_window_id);
|
||||
if (!w || !w->handle) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist"); return NULL; }
|
||||
RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(const char*)));
|
||||
RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(GLFWDragSourceItem)));
|
||||
if (!items) { PyErr_NoMemory(); return NULL; }
|
||||
PyObject *key, *value; Py_ssize_t pos = 0; size_t num = 0;
|
||||
while (PyDict_Next(data_map, &pos, &key, &value)) {
|
||||
if (!PyUnicode_Check(key)) { PyErr_SetString(PyExc_TypeError, "data_map must have string keys"); return NULL; }
|
||||
if (!PyBytes_Check(value)) { PyErr_SetString(PyExc_TypeError, "data_map must have bytes values"); return NULL; }
|
||||
items[num].mime_type = PyUnicode_AsUTF8(key);
|
||||
items[num].optional_data = PyBytes_AS_STRING(value); items[num].data_size = PyBytes_GET_SIZE(value);
|
||||
num++;
|
||||
GLFWDragSourceItem *item = items + num++;
|
||||
item->mime_type = PyUnicode_AsUTF8(key);
|
||||
item->optional_data = PyBytes_AS_STRING(value); item->data_size = PyBytes_GET_SIZE(value);
|
||||
}
|
||||
GLFWimage thumbnail = {.pixels=thumbnail_data, .width=width, .height=height};
|
||||
free_drag_source();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue