diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 36964391b3..0636018c93 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -27,10 +27,12 @@ #import "GdkMacosView.h" #import "GdkMacosWindow.h" -#include "gdkmacosclipboard-private.h" #include "gdkmacosdisplay-private.h" +#include "gdkmacosdrag-private.h" #include "gdkmacosdrop-private.h" +#include "gdkmacoseventsource-private.h" #include "gdkmacosmonitor-private.h" +#include "gdkmacospasteboard-private.h" #include "gdkmacossurface-private.h" #include "gdkmacospopupsurface-private.h" #include "gdkmacostoplevelsurface-private.h" @@ -144,7 +146,7 @@ typedef NSString *CALayerContentsGravity; * * TODO: Can we improve grab breaking to fix this? */ - _gdk_macos_display_send_button_event ([self gdkDisplay], event); + _gdk_macos_display_send_event ([self gdkDisplay], event); _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time); @@ -246,7 +248,7 @@ typedef NSString *CALayerContentsGravity; [view release]; /* TODO: We might want to make this more extensible at some point */ - _gdk_macos_clipboard_register_drag_types (self); + _gdk_macos_pasteboard_register_drag_types (self); return self; } @@ -668,7 +670,61 @@ typedef NSString *CALayerContentsGravity; } // NSDraggingSource protocol -// ... + +- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + NSInteger sequence_number = [session draggingSequenceNumber]; + GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface)); + GdkDrag *drag = _gdk_macos_display_find_drag (GDK_MACOS_DISPLAY (display), sequence_number); + GdkModifierType state = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display)); + + _gdk_macos_drag_set_actions (GDK_MACOS_DRAG (drag), state); + + return _gdk_macos_drag_operation (GDK_MACOS_DRAG (drag)); +} + +- (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint +{ + NSInteger sequence_number = [session draggingSequenceNumber]; + GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface)); + GdkDrag *drag = _gdk_macos_display_find_drag (GDK_MACOS_DISPLAY (display), sequence_number); + int x, y; + + _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display), screenPoint.x, screenPoint.y, &x, &y); + _gdk_macos_drag_set_start_position (GDK_MACOS_DRAG (drag), x, y); + _gdk_macos_drag_surface_move (GDK_MACOS_DRAG (drag), x, y); +} + +- (void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint +{ + NSInteger sequence_number = [session draggingSequenceNumber]; + GdkMacosDisplay *display = GDK_MACOS_DISPLAY (gdk_surface_get_display (GDK_SURFACE (gdk_surface))); + GdkDrag *drag = _gdk_macos_display_find_drag (GDK_MACOS_DISPLAY (display), sequence_number); + int x, y; + + _gdk_macos_display_send_event (display, [NSApp currentEvent]); + + _gdk_macos_display_from_display_coords (display, screenPoint.x, screenPoint.y, &x, &y); + _gdk_macos_drag_surface_move (GDK_MACOS_DRAG (drag), x, y); +} + +- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation +{ + NSInteger sequence_number = [session draggingSequenceNumber]; + GdkMacosDisplay *display = GDK_MACOS_DISPLAY (gdk_surface_get_display (GDK_SURFACE (gdk_surface))); + GdkDrag *drag = _gdk_macos_display_find_drag (display, sequence_number); + + _gdk_macos_display_send_event (display, [NSApp currentEvent]); + gdk_drag_set_selected_action (drag, _gdk_macos_drag_ns_operation_to_action (operation)); + + if (gdk_drag_get_selected_action (drag) != 0) + g_signal_emit_by_name (drag, "drop-performed"); + else + gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET); + + _gdk_macos_display_set_drag (display, [session draggingSequenceNumber], NULL); +} + // end -(void)setStyleMask:(NSWindowStyleMask)styleMask diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h index 1cf9ec805c..66fcd45717 100644 --- a/gdk/macos/GdkMacosWindow.h +++ b/gdk/macos/GdkMacosWindow.h @@ -32,7 +32,7 @@ #define GDK_IS_MACOS_WINDOW(obj) ([obj isKindOfClass:[GdkMacosWindow class]]) -@interface GdkMacosWindow : NSWindow { +@interface GdkMacosWindow : NSWindow { GdkMacosSurface *gdk_surface; BOOL inMove; diff --git a/gdk/macos/gdkmacosclipboard-private.h b/gdk/macos/gdkmacosclipboard-private.h index ba0b52bf0a..ef70ee3e33 100644 --- a/gdk/macos/gdkmacosclipboard-private.h +++ b/gdk/macos/gdkmacosclipboard-private.h @@ -24,6 +24,7 @@ #include "gdkclipboardprivate.h" #include "gdkmacosdisplay-private.h" +#include "gdkmacospasteboard-private.h" G_BEGIN_DECLS @@ -41,30 +42,6 @@ NSPasteboardType _gdk_macos_clipboard_to_ns_type (const char NSPasteboardType *alternate); const char *_gdk_macos_clipboard_from_ns_type (NSPasteboardType ns_type); void _gdk_macos_clipboard_register_drag_types (NSWindow *window); -GdkContentFormats *_gdk_macos_pasteboard_load_formats (NSPasteboard *pasteboard); -void _gdk_macos_pasteboard_read_async (GObject *object, - NSPasteboard *pasteboard, - GdkContentFormats *formats, - int io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -GInputStream *_gdk_macos_pasteboard_read_finish (GObject *object, - GAsyncResult *result, - const char **out_mime_type, - GError **error); - -@interface GdkMacosClipboardDataProvider : NSObject -{ - GCancellable *cancellable; - GdkClipboard *clipboard; - char **mimeTypes; -} - --(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types; --(NSArray *)types; - -@end G_END_DECLS diff --git a/gdk/macos/gdkmacosclipboard.c b/gdk/macos/gdkmacosclipboard.c index 3a2667c0df..4725784dfe 100644 --- a/gdk/macos/gdkmacosclipboard.c +++ b/gdk/macos/gdkmacosclipboard.c @@ -22,6 +22,7 @@ #include #include "gdkmacosclipboard-private.h" +#include "gdkmacospasteboard-private.h" #include "gdkmacosutils-private.h" #include "gdkprivate.h" @@ -32,163 +33,8 @@ struct _GdkMacosClipboard NSInteger last_change_count; }; -typedef struct -{ - GMemoryOutputStream *stream; - NSPasteboardItem *item; - NSPasteboardType type; - GMainContext *main_context; - guint done : 1; -} WriteRequest; - -enum { - TYPE_STRING, - TYPE_PBOARD, - TYPE_URL, - TYPE_FILE_URL, - TYPE_COLOR, - TYPE_TIFF, - TYPE_PNG, - TYPE_LAST -}; - -#define PTYPE(k) (get_pasteboard_type(TYPE_##k)) - -static NSPasteboardType pasteboard_types[TYPE_LAST]; - G_DEFINE_TYPE (GdkMacosClipboard, _gdk_macos_clipboard, GDK_TYPE_CLIPBOARD) -static NSPasteboardType -get_pasteboard_type (int type) -{ - static gsize initialized = FALSE; - - g_assert (type >= 0); - g_assert (type < TYPE_LAST); - - if (g_once_init_enter (&initialized)) - { - pasteboard_types[TYPE_PNG] = NSPasteboardTypePNG; - pasteboard_types[TYPE_STRING] = NSPasteboardTypeString; - pasteboard_types[TYPE_TIFF] = NSPasteboardTypeTIFF; - pasteboard_types[TYPE_COLOR] = NSPasteboardTypeColor; - - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - pasteboard_types[TYPE_PBOARD] = NSStringPboardType; - G_GNUC_END_IGNORE_DEPRECATIONS - -#ifdef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER - pasteboard_types[TYPE_URL] = NSPasteboardTypeURL; - pasteboard_types[TYPE_FILE_URL] = NSPasteboardTypeFileURL; -#else - pasteboard_types[TYPE_URL] = [[NSString alloc] initWithUTF8String:"public.url"]; - pasteboard_types[TYPE_FILE_URL] = [[NSString alloc] initWithUTF8String:"public.file-url"]; -#endif - - g_once_init_leave (&initialized, TRUE); - } - - return pasteboard_types[type]; -} - -static void -write_request_free (WriteRequest *wr) -{ - g_clear_pointer (&wr->main_context, g_main_context_unref); - g_clear_object (&wr->stream); - [wr->item release]; - g_slice_free (WriteRequest, wr); -} - -const char * -_gdk_macos_clipboard_from_ns_type (NSPasteboardType type) -{ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; - - if ([type isEqualToString:PTYPE(STRING)] || - [type isEqualToString:PTYPE(PBOARD)]) - return g_intern_string ("text/plain;charset=utf-8"); - else if ([type isEqualToString:PTYPE(URL)] || - [type isEqualToString:PTYPE(FILE_URL)]) - return g_intern_string ("text/uri-list"); - else if ([type isEqualToString:PTYPE(COLOR)]) - return g_intern_string ("application/x-color"); - else if ([type isEqualToString:PTYPE(TIFF)]) - return g_intern_string ("image/tiff"); - else if ([type isEqualToString:PTYPE(PNG)]) - return g_intern_string ("image/png"); - - G_GNUC_END_IGNORE_DEPRECATIONS; - - return NULL; -} - -NSPasteboardType -_gdk_macos_clipboard_to_ns_type (const char *mime_type, - NSPasteboardType *alternate) -{ - if (alternate) - *alternate = NULL; - - if (g_strcmp0 (mime_type, "text/plain;charset=utf-8") == 0) - { - return PTYPE(STRING); - } - else if (g_strcmp0 (mime_type, "text/uri-list") == 0) - { - if (alternate) - *alternate = PTYPE(URL); - return PTYPE(FILE_URL); - } - else if (g_strcmp0 (mime_type, "application/x-color") == 0) - { - return PTYPE(COLOR); - } - else if (g_strcmp0 (mime_type, "image/tiff") == 0) - { - return PTYPE(TIFF); - } - else if (g_strcmp0 (mime_type, "image/png") == 0) - { - return PTYPE(PNG); - } - - return nil; -} - -static void -populate_content_formats (GdkContentFormatsBuilder *builder, - NSPasteboardType type) -{ - const char *mime_type; - - g_return_if_fail (builder != NULL); - g_return_if_fail (type != NULL); - - mime_type = _gdk_macos_clipboard_from_ns_type (type); - - if (mime_type != NULL) - gdk_content_formats_builder_add_mime_type (builder, mime_type); -} - -static GdkContentFormats * -load_offer_formats (NSPasteboard *pasteboard) -{ - GDK_BEGIN_MACOS_ALLOC_POOL; - - GdkContentFormatsBuilder *builder; - GdkContentFormats *formats; - - builder = gdk_content_formats_builder_new (); - for (NSPasteboardType type in [pasteboard types]) - populate_content_formats (builder, type); - formats = gdk_content_formats_builder_free_to_formats (builder); - - GDK_END_MACOS_ALLOC_POOL; - - return g_steal_pointer (&formats); -} - static void _gdk_macos_clipboard_load_contents (GdkMacosClipboard *self) { @@ -199,22 +45,13 @@ _gdk_macos_clipboard_load_contents (GdkMacosClipboard *self) change_count = [self->pasteboard changeCount]; - formats = load_offer_formats (self->pasteboard); + formats = _gdk_macos_pasteboard_load_formats (self->pasteboard); gdk_clipboard_claim_remote (GDK_CLIPBOARD (self), formats); gdk_content_formats_unref (formats); self->last_change_count = change_count; } -static GInputStream * -create_stream_from_nsdata (NSData *data) -{ - const guint8 *bytes = [data bytes]; - gsize len = [data length]; - - return g_memory_input_stream_new_from_data (g_memdup2 (bytes, len), len, g_free); -} - static void _gdk_macos_clipboard_read_async (GdkClipboard *clipboard, GdkContentFormats *formats, @@ -245,34 +82,26 @@ static void _gdk_macos_clipboard_send_to_pasteboard (GdkMacosClipboard *self, GdkContentProvider *content) { + GdkMacosPasteboardItem *item; + NSArray *items; + + g_assert (GDK_IS_MACOS_CLIPBOARD (self)); + g_assert (GDK_IS_CONTENT_PROVIDER (content)); + + if (self->pasteboard == NULL) + return; + GDK_BEGIN_MACOS_ALLOC_POOL; - GdkMacosClipboardDataProvider *dataProvider; - GdkContentFormats *serializable; - NSPasteboardItem *item; - const char * const *mime_types; - gsize n_mime_types; - - g_return_if_fail (GDK_IS_MACOS_CLIPBOARD (self)); - g_return_if_fail (GDK_IS_CONTENT_PROVIDER (content)); - - serializable = gdk_content_provider_ref_storable_formats (content); - serializable = gdk_content_formats_union_serialize_mime_types (serializable); - mime_types = gdk_content_formats_get_mime_types (serializable, &n_mime_types); - - dataProvider = [[GdkMacosClipboardDataProvider alloc] initClipboard:GDK_CLIPBOARD (self) - mimetypes:mime_types]; - item = [[NSPasteboardItem alloc] init]; - [item setDataProvider:dataProvider forTypes:[dataProvider types]]; + item = [[GdkMacosPasteboardItem alloc] initForClipboard:GDK_CLIPBOARD (self) withContentProvider:content]; + items = [NSArray arrayWithObject:item]; [self->pasteboard clearContents]; - if ([self->pasteboard writeObjects:[NSArray arrayWithObject:item]] == NO) - g_warning ("Failed to write object to pasteboard"); + if ([self->pasteboard writeObjects:items] == NO) + g_warning ("Failed to send clipboard to pasteboard"); self->last_change_count = [self->pasteboard changeCount]; - g_clear_pointer (&serializable, gdk_content_formats_unref); - GDK_END_MACOS_ALLOC_POOL; } @@ -365,305 +194,3 @@ _gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self) if ([self->pasteboard changeCount] != self->last_change_count) _gdk_macos_clipboard_load_contents (self); } - -@implementation GdkMacosClipboardDataProvider - --(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types; -{ - [super init]; - - self->mimeTypes = g_strdupv ((char **)mime_types); - self->clipboard = g_object_ref (gdkClipboard); - - return self; -} - --(void)dealloc -{ - g_cancellable_cancel (self->cancellable); - - g_clear_pointer (&self->mimeTypes, g_strfreev); - g_clear_object (&self->clipboard); - g_clear_object (&self->cancellable); - - [super dealloc]; -} - --(void)pasteboardFinishedWithDataProvider:(NSPasteboard *)pasteboard -{ - g_clear_object (&self->clipboard); -} - --(NSArray *)types -{ - NSMutableArray *ret = [[NSMutableArray alloc] init]; - - for (guint i = 0; self->mimeTypes[i]; i++) - { - const char *mime_type = self->mimeTypes[i]; - NSPasteboardType type; - NSPasteboardType alternate = nil; - - if ((type = _gdk_macos_clipboard_to_ns_type (mime_type, &alternate))) - { - [ret addObject:type]; - if (alternate) - [ret addObject:alternate]; - } - } - - return g_steal_pointer (&ret); -} - -static void -on_data_ready_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDK_BEGIN_MACOS_ALLOC_POOL; - - GdkClipboard *clipboard = (GdkClipboard *)object; - WriteRequest *wr = user_data; - GError *error = NULL; - NSData *data = nil; - - g_assert (GDK_IS_CLIPBOARD (clipboard)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (wr != NULL); - g_assert (G_IS_MEMORY_OUTPUT_STREAM (wr->stream)); - g_assert ([wr->item isKindOfClass:[NSPasteboardItem class]]); - - if (gdk_clipboard_write_finish (clipboard, result, &error)) - { - gsize size; - gpointer bytes; - - g_output_stream_close (G_OUTPUT_STREAM (wr->stream), NULL, NULL); - - size = g_memory_output_stream_get_data_size (wr->stream); - bytes = g_memory_output_stream_steal_data (wr->stream); - data = [[NSData alloc] initWithBytesNoCopy:bytes - length:size - deallocator:^(void *alloc, NSUInteger length) { g_free (alloc); }]; - } - else - { - g_warning ("Failed to serialize clipboard contents: %s", - error->message); - g_clear_error (&error); - } - - [wr->item setData:data forType:wr->type]; - - wr->done = TRUE; - - GDK_END_MACOS_ALLOC_POOL; -} - --(void) pasteboard:(NSPasteboard *)pasteboard - item:(NSPasteboardItem *)item - provideDataForType:(NSPasteboardType)type -{ - const char *mime_type = _gdk_macos_clipboard_from_ns_type (type); - GMainContext *main_context = g_main_context_default (); - WriteRequest *wr; - - if (self->clipboard == NULL || mime_type == NULL) - { - [item setData:[NSData data] forType:type]; - return; - } - - wr = g_slice_new0 (WriteRequest); - wr->item = [item retain]; - wr->stream = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ()); - wr->type = type; - wr->main_context = g_main_context_ref (main_context); - wr->done = FALSE; - - gdk_clipboard_write_async (self->clipboard, - mime_type, - G_OUTPUT_STREAM (wr->stream), - G_PRIORITY_DEFAULT, - self->cancellable, - on_data_ready_cb, - wr); - - /* We're forced to provide data synchronously via this API - * so we must block on the main loop. Using another main loop - * than the default tends to get us locked up here, so that is - * what we'll do for now. - */ - while (!wr->done) - g_main_context_iteration (wr->main_context, TRUE); - - write_request_free (wr); -} - -void -_gdk_macos_clipboard_register_drag_types (NSWindow *window) -{ - [window registerForDraggedTypes:[NSArray arrayWithObjects:PTYPE(STRING), - PTYPE(PBOARD), - PTYPE(URL), - PTYPE(FILE_URL), - PTYPE(COLOR), - PTYPE(TIFF), - PTYPE(PNG), - nil]]; -} - -@end - -GdkContentFormats * -_gdk_macos_pasteboard_load_formats (NSPasteboard *pasteboard) -{ - return load_offer_formats (pasteboard); -} - -void -_gdk_macos_pasteboard_read_async (GObject *object, - NSPasteboard *pasteboard, - GdkContentFormats *formats, - int io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GDK_BEGIN_MACOS_ALLOC_POOL; - - GdkContentFormats *offer_formats = NULL; - const char *mime_type; - GInputStream *stream = NULL; - GTask *task = NULL; - - g_assert (G_IS_OBJECT (object)); - g_assert (pasteboard != NULL); - g_assert (formats != NULL); - - task = g_task_new (object, cancellable, callback, user_data); - g_task_set_source_tag (task, _gdk_macos_pasteboard_read_async); - g_task_set_priority (task, io_priority); - - offer_formats = load_offer_formats (pasteboard); - mime_type = gdk_content_formats_match_mime_type (formats, offer_formats); - - if (mime_type == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s", - _("No compatible transfer format found")); - goto cleanup; - } - - if (strcmp (mime_type, "text/plain;charset=utf-8") == 0) - { - NSString *nsstr = [pasteboard stringForType:NSPasteboardTypeString]; - - if (nsstr != NULL) - { - const char *str = [nsstr UTF8String]; - stream = g_memory_input_stream_new_from_data (g_strdup (str), - strlen (str) + 1, - g_free); - } - } - else if (strcmp (mime_type, "text/uri-list") == 0) - { - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; - - if ([[pasteboard types] containsObject:PTYPE(FILE_URL)]) - { - GString *str = g_string_new (NULL); - NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; - gsize n_files = [files count]; - char *data; - guint len; - - for (gsize i = 0; i < n_files; ++i) - { - NSString* uriString = [files objectAtIndex:i]; - uriString = [@"file://" stringByAppendingString:uriString]; - uriString = [uriString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - - g_string_append_printf (str, - "%s\r\n", - [uriString cStringUsingEncoding:NSUTF8StringEncoding]); - } - - len = str->len; - data = g_string_free (str, FALSE); - stream = g_memory_input_stream_new_from_data (data, len, g_free); - } - - G_GNUC_END_IGNORE_DEPRECATIONS; - } - else if (strcmp (mime_type, "application/x-color") == 0) - { - NSColorSpace *colorspace; - NSColor *nscolor; - guint16 color[4]; - - colorspace = [NSColorSpace genericRGBColorSpace]; - nscolor = [[NSColor colorFromPasteboard:pasteboard] - colorUsingColorSpace:colorspace]; - - color[0] = 0xffff * [nscolor redComponent]; - color[1] = 0xffff * [nscolor greenComponent]; - color[2] = 0xffff * [nscolor blueComponent]; - color[3] = 0xffff * [nscolor alphaComponent]; - - stream = g_memory_input_stream_new_from_data (g_memdup2 (&color, sizeof color), - sizeof color, - g_free); - } - else if (strcmp (mime_type, "image/tiff") == 0) - { - NSData *data = [pasteboard dataForType:PTYPE(TIFF)]; - stream = create_stream_from_nsdata (data); - } - else if (strcmp (mime_type, "image/png") == 0) - { - NSData *data = [pasteboard dataForType:PTYPE(PNG)]; - stream = create_stream_from_nsdata (data); - } - - if (stream != NULL) - { - g_task_set_task_data (task, g_strdup (mime_type), g_free); - g_task_return_pointer (task, g_steal_pointer (&stream), g_object_unref); - } - else - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("Failed to decode contents with mime-type of '%s'"), - mime_type); - } - -cleanup: - g_clear_object (&task); - g_clear_pointer (&offer_formats, gdk_content_formats_unref); - - GDK_END_MACOS_ALLOC_POOL; -} - -GInputStream * -_gdk_macos_pasteboard_read_finish (GObject *object, - GAsyncResult *result, - const char **out_mime_type, - GError **error) -{ - GTask *task = (GTask *)result; - - g_assert (G_IS_OBJECT (object)); - g_assert (G_IS_TASK (task)); - - if (out_mime_type != NULL) - *out_mime_type = g_strdup (g_task_get_task_data (task)); - - return g_task_propagate_pointer (task, error); -} diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index 83ae435e49..9fc910ec2e 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -155,12 +155,13 @@ void _gdk_macos_display_surface_became_key (GdkMacosDisp GdkMacosSurface *surface); void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self); const GList *_gdk_macos_display_get_surfaces (GdkMacosDisplay *self); -void _gdk_macos_display_send_button_event (GdkMacosDisplay *self, +void _gdk_macos_display_send_event (GdkMacosDisplay *self, NSEvent *nsevent); void _gdk_macos_display_warp_pointer (GdkMacosDisplay *self, int x, int y); NSEvent *_gdk_macos_display_get_nsevent (GdkEvent *event); +NSEvent *_gdk_macos_display_get_last_nsevent (void); GdkDrag *_gdk_macos_display_find_drag (GdkMacosDisplay *self, NSInteger sequence_number); GdkDrop *_gdk_macos_display_find_drop (GdkMacosDisplay *self, diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index b06dc83dbe..b88ed281ac 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -723,6 +723,85 @@ fill_scroll_event (GdkMacosDisplay *self, return g_steal_pointer (&ret); } + +static GdkEvent * +fill_event (GdkMacosDisplay *self, + GdkMacosWindow *window, + NSEvent *nsevent, + int x, + int y) +{ + GdkMacosSurface *surface = [window gdkSurface]; + NSEventType event_type = [nsevent type]; + GdkEvent *ret = NULL; + + switch ((int)event_type) + { + case NSEventTypeLeftMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeOtherMouseDown: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseUp: + case NSEventTypeOtherMouseUp: + ret = fill_button_event (self, surface, nsevent, x, y); + break; + + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + case NSEventTypeMouseMoved: + ret = fill_motion_event (self, surface, nsevent, x, y); + break; + + case NSEventTypeMagnify: + case NSEventTypeRotate: + ret = fill_pinch_event (self, surface, nsevent, x, y); + break; + + case NSEventTypeMouseExited: + case NSEventTypeMouseEntered: + { + GdkSeat *seat = gdk_display_get_default_seat (GDK_DISPLAY (self)); + GdkDevice *pointer = gdk_seat_get_pointer (seat); + GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), pointer); + + if ([(GdkMacosWindow *)window isInManualResizeOrMove]) + { + ret = GDK_MACOS_EVENT_DROP; + } + else if (grab == NULL) + { + if (event_type == NSEventTypeMouseExited) + [[NSCursor arrowCursor] set]; + + ret = synthesize_crossing_event (self, surface, nsevent, x, y); + } + } + + break; + + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + case NSEventTypeFlagsChanged: { + GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent); + + if (type) + ret = fill_key_event (self, surface, nsevent, type); + + break; + } + + case NSEventTypeScrollWheel: + ret = fill_scroll_event (self, surface, nsevent, x, y); + break; + + default: + break; + } + + return ret; +} + static gboolean is_mouse_button_press_event (NSEventType type) { @@ -1025,16 +1104,12 @@ find_surface_for_ns_event (GdkMacosDisplay *self, GdkMacosBaseView *view; GdkSurface *surface; NSPoint point; - int x_tmp; - int y_tmp; g_assert (GDK_IS_MACOS_DISPLAY (self)); g_assert (nsevent != NULL); g_assert (x != NULL); g_assert (y != NULL); - _gdk_macos_display_from_display_coords (self, point.x, point.y, &x_tmp, &y_tmp); - switch ((int)[nsevent type]) { case NSEventTypeLeftMouseDown: @@ -1083,7 +1158,6 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, GdkMacosWindow *window; NSEventType event_type; NSWindow *event_window; - GdkEvent *ret = NULL; int x; int y; @@ -1191,79 +1265,15 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, _gdk_macos_display_clear_sorting (self); } } - - switch ((int)event_type) - { - case NSEventTypeLeftMouseDown: - case NSEventTypeRightMouseDown: - case NSEventTypeOtherMouseDown: - case NSEventTypeLeftMouseUp: - case NSEventTypeRightMouseUp: - case NSEventTypeOtherMouseUp: - ret = fill_button_event (self, surface, nsevent, x, y); - break; - - case NSEventTypeLeftMouseDragged: - case NSEventTypeRightMouseDragged: - case NSEventTypeOtherMouseDragged: - case NSEventTypeMouseMoved: - ret = fill_motion_event (self, surface, nsevent, x, y); - break; - - case NSEventTypeMagnify: - case NSEventTypeRotate: - ret = fill_pinch_event (self, surface, nsevent, x, y); - break; - - case NSEventTypeMouseExited: - case NSEventTypeMouseEntered: - { - GdkSeat *seat = gdk_display_get_default_seat (GDK_DISPLAY (self)); - GdkDevice *pointer = gdk_seat_get_pointer (seat); - GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), pointer); - - if ([(GdkMacosWindow *)window isInManualResizeOrMove]) - { - ret = GDK_MACOS_EVENT_DROP; - } - else if (grab == NULL) - { - if (event_type == NSEventTypeMouseExited) - [[NSCursor arrowCursor] set]; - - ret = synthesize_crossing_event (self, surface, nsevent, x, y); - } - } - - break; - - case NSEventTypeKeyDown: - case NSEventTypeKeyUp: - case NSEventTypeFlagsChanged: { - GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent); - - if (type) - ret = fill_key_event (self, surface, nsevent, type); - - break; - } - - case NSEventTypeScrollWheel: - ret = fill_scroll_event (self, surface, nsevent, x, y); - break; - - default: - break; - } - - return ret; + return fill_event (self, window, nsevent, x, y); } void -_gdk_macos_display_send_button_event (GdkMacosDisplay *self, - NSEvent *nsevent) +_gdk_macos_display_send_event (GdkMacosDisplay *self, + NSEvent *nsevent) { GdkMacosSurface *surface; + GdkMacosWindow *window; GdkEvent *event; int x; int y; @@ -1272,7 +1282,8 @@ _gdk_macos_display_send_button_event (GdkMacosDisplay *self, g_return_if_fail (nsevent != NULL); if ((surface = find_surface_for_ns_event (self, nsevent, &x, &y)) && - (event = fill_button_event (self, surface, nsevent, x, y))) + (window = (GdkMacosWindow *)_gdk_macos_surface_get_native (surface)) && + (event = fill_event (self, window, nsevent, x, y))) _gdk_windowing_got_event (GDK_DISPLAY (self), _gdk_event_queue_append (GDK_DISPLAY (self), event), event, diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 0e5a9b8eb6..b9869fae7c 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -1024,6 +1024,16 @@ _gdk_macos_display_get_nsevent (GdkEvent *event) return NULL; } +NSEvent * +_gdk_macos_display_get_last_nsevent () +{ + const GdkToNSEventMap *map = g_queue_peek_tail (&event_map); + if (map) + return map->nsevent; + + return NULL; +} + GdkDrag * _gdk_macos_display_find_drag (GdkMacosDisplay *self, NSInteger sequence_number) diff --git a/gdk/macos/gdkmacosdrag-private.h b/gdk/macos/gdkmacosdrag-private.h index 98075f27ef..bdcbbbe23d 100644 --- a/gdk/macos/gdkmacosdrag-private.h +++ b/gdk/macos/gdkmacosdrag-private.h @@ -41,7 +41,6 @@ struct _GdkMacosDrag GdkDrag parent_instance; GdkMacosDragSurface *drag_surface; - GdkSeat *drag_seat; GdkCursor *cursor; int hot_x; @@ -62,8 +61,22 @@ struct _GdkMacosDragClass GdkDragClass parent_class; }; -GType gdk_macos_drag_get_type (void) G_GNUC_CONST; -gboolean _gdk_macos_drag_begin (GdkMacosDrag *self); +GType gdk_macos_drag_get_type (void) G_GNUC_CONST; +gboolean _gdk_macos_drag_begin (GdkMacosDrag *self, + GdkContentProvider *content, + GdkMacosWindow *window); +NSDragOperation _gdk_macos_drag_operation (GdkMacosDrag *self); +GdkDragAction _gdk_macos_drag_ns_operation_to_action + (NSDragOperation operation); +void _gdk_macos_drag_surface_move (GdkMacosDrag *self, + int x_root, + int y_root); +void _gdk_macos_drag_set_start_position (GdkMacosDrag *self, + int start_x, + int start_y); +void _gdk_macos_drag_set_actions (GdkMacosDrag *self, + GdkModifierType mods); + G_END_DECLS diff --git a/gdk/macos/gdkmacosdrag.c b/gdk/macos/gdkmacosdrag.c index 10a756998d..c0201df067 100644 --- a/gdk/macos/gdkmacosdrag.c +++ b/gdk/macos/gdkmacosdrag.c @@ -25,6 +25,7 @@ #include "gdkmacoscursor-private.h" #include "gdkmacosdisplay-private.h" #include "gdkmacosdragsurface-private.h" +#include "gdkmacospasteboard-private.h" #include "gdk/gdkdeviceprivate.h" #include "gdk/gdkeventsprivate.h" @@ -187,47 +188,6 @@ gdk_macos_drag_set_cursor (GdkDrag *drag, [nscursor set]; } -static gboolean -drag_grab (GdkMacosDrag *self) -{ - GdkSeat *seat; - - g_assert (GDK_IS_MACOS_DRAG (self)); - - seat = gdk_device_get_seat (gdk_drag_get_device (GDK_DRAG (self))); - - if (gdk_seat_grab (seat, - GDK_SURFACE (self->drag_surface), - GDK_SEAT_CAPABILITY_ALL_POINTING, - FALSE, - self->cursor, - NULL, - NULL, - NULL) != GDK_GRAB_SUCCESS) - return FALSE; - - g_set_object (&self->drag_seat, seat); - - return TRUE; -} - -static void -drag_ungrab (GdkMacosDrag *self) -{ - GdkDisplay *display; - - g_assert (GDK_IS_MACOS_DRAG (self)); - - if (self->drag_seat) - { - gdk_seat_ungrab (self->drag_seat); - g_clear_object (&self->drag_seat); - } - - display = gdk_drag_get_display (GDK_DRAG (self)); - _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), GDK_CURRENT_TIME); -} - static void gdk_macos_drag_cancel (GdkDrag *drag, GdkDragCancelReason reason) @@ -240,7 +200,6 @@ gdk_macos_drag_cancel (GdkDrag *drag, return; self->cancelled = TRUE; - drag_ungrab (self); gdk_drag_drop_done (drag, FALSE); } @@ -253,7 +212,6 @@ gdk_macos_drag_drop_performed (GdkDrag *drag, g_assert (GDK_IS_MACOS_DRAG (self)); g_object_ref (self); - drag_ungrab (self); g_signal_emit_by_name (drag, "dnd-finished"); gdk_drag_drop_done (drag, TRUE); g_object_unref (self); @@ -316,225 +274,6 @@ gdk_drag_get_current_actions (GdkModifierType state, } } -static void -gdk_drag_update (GdkDrag *drag, - double x_root, - double y_root, - GdkModifierType mods, - guint32 evtime) -{ - GdkMacosDrag *self = (GdkMacosDrag *)drag; - GdkDragAction suggested_action; - GdkDragAction possible_actions; - - g_assert (GDK_IS_MACOS_DRAG (self)); - - self->last_x = x_root; - self->last_y = y_root; - - gdk_drag_get_current_actions (mods, - GDK_BUTTON_PRIMARY, - gdk_drag_get_actions (drag), - &suggested_action, - &possible_actions); - - if (GDK_IS_MACOS_SURFACE (self->drag_surface)) - _gdk_macos_surface_move (GDK_MACOS_SURFACE (self->drag_surface), - x_root - self->hot_x, - y_root - self->hot_y); - - if (!self->did_update) - { - self->start_x = self->last_x; - self->start_y = self->last_y; - self->did_update = TRUE; - } - - gdk_drag_set_actions (drag, possible_actions); -} - -static gboolean -gdk_dnd_handle_motion_event (GdkDrag *drag, - GdkEvent *event) -{ - double x, y; - int x_root, y_root; - - g_assert (GDK_IS_MACOS_DRAG (drag)); - g_assert (event != NULL); - - /* Ignore motion while doing zoomback */ - if (GDK_MACOS_DRAG (drag)->cancelled) - return FALSE; - - gdk_event_get_position (event, &x, &y); - x_root = event->surface->x + x; - y_root = event->surface->y + y; - gdk_drag_update (drag, x_root, y_root, - gdk_event_get_modifier_state (event), - gdk_event_get_time (event)); - - return TRUE; -} - -static gboolean -gdk_dnd_handle_grab_broken_event (GdkDrag *drag, - GdkEvent *event) -{ - GdkMacosDrag *self = GDK_MACOS_DRAG (drag); - gboolean is_implicit = gdk_grab_broken_event_get_implicit (event); - GdkSurface *grab_surface = gdk_grab_broken_event_get_grab_surface (event); - - /* Don't cancel if we break the implicit grab from the initial button_press. */ - if (is_implicit || grab_surface == (GdkSurface *)self->drag_surface) - return FALSE; - - if (gdk_event_get_device (event) != gdk_drag_get_device (drag)) - return FALSE; - - gdk_drag_cancel (drag, GDK_DRAG_CANCEL_ERROR); - - return TRUE; -} - -static gboolean -gdk_dnd_handle_button_event (GdkDrag *drag, - GdkEvent *event) -{ - GdkMacosDrag *self = GDK_MACOS_DRAG (drag); - - g_assert (GDK_IS_MACOS_DRAG (self)); - g_assert (event != NULL); - -#if 0 - /* FIXME: Check the button matches */ - if (event->button != self->button) - return FALSE; -#endif - - if (gdk_drag_get_selected_action (drag) != 0) - g_signal_emit_by_name (drag, "drop-performed"); - else - gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET); - - return TRUE; -} - -static gboolean -gdk_dnd_handle_key_event (GdkDrag *drag, - GdkEvent *event) -{ - GdkMacosDrag *self = GDK_MACOS_DRAG (drag); - GdkModifierType state; - GdkDevice *pointer; - GdkSeat *seat; - int dx, dy; - - dx = dy = 0; - state = gdk_event_get_modifier_state (event); - seat = gdk_event_get_seat (event); - pointer = gdk_seat_get_pointer (seat); - - if (event->event_type == GDK_KEY_PRESS) - { - guint keyval = gdk_key_event_get_keyval (event); - - switch (keyval) - { - case GDK_KEY_Escape: - gdk_drag_cancel (drag, GDK_DRAG_CANCEL_USER_CANCELLED); - return TRUE; - - case GDK_KEY_space: - case GDK_KEY_Return: - case GDK_KEY_ISO_Enter: - case GDK_KEY_KP_Enter: - case GDK_KEY_KP_Space: - if (gdk_drag_get_selected_action (drag) != 0) - g_signal_emit_by_name (drag, "drop-performed"); - else - gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET); - - return TRUE; - - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - dy = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP; - break; - - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - dy = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP; - break; - - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - dx = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP; - break; - - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - dx = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP; - break; - - default: - break; - } - } - - /* The state is not yet updated in the event, so we need - * to query it here. We could use XGetModifierMapping, but - * that would be overkill. - */ - gdk_macos_device_query_state (pointer, NULL, NULL, NULL, NULL, &state); - - if (dx != 0 || dy != 0) - { - GdkDisplay *display = gdk_event_get_display ((GdkEvent *)event); - - self->last_x += dx; - self->last_y += dy; - - _gdk_macos_display_warp_pointer (GDK_MACOS_DISPLAY (display), - self->last_x, - self->last_y); - } - - gdk_drag_update (drag, - self->last_x, self->last_y, - state, - gdk_event_get_time (event)); - - return TRUE; -} - -static gboolean -gdk_macos_drag_handle_event (GdkDrag *drag, - GdkEvent *event) -{ - g_assert (GDK_IS_MACOS_DRAG (drag)); - g_assert (event != NULL); - - switch ((guint) event->event_type) - { - case GDK_MOTION_NOTIFY: - return gdk_dnd_handle_motion_event (drag, event); - - case GDK_BUTTON_RELEASE: - return gdk_dnd_handle_button_event (drag, event); - - case GDK_KEY_PRESS: - case GDK_KEY_RELEASE: - return gdk_dnd_handle_key_event (drag, event); - - case GDK_GRAB_BROKEN: - return gdk_dnd_handle_grab_broken_event (drag, event); - - default: - return FALSE; - } -} - static void gdk_macos_drag_finalize (GObject *object) { @@ -542,11 +281,6 @@ gdk_macos_drag_finalize (GObject *object) GdkMacosDragSurface *drag_surface = g_steal_pointer (&self->drag_surface); g_clear_object (&self->cursor); - if (self->drag_seat) - { - gdk_seat_ungrab (self->drag_seat); - g_clear_object (&self->drag_seat); - } G_OBJECT_CLASS (gdk_macos_drag_parent_class)->finalize (object); @@ -608,7 +342,6 @@ gdk_macos_drag_class_init (GdkMacosDragClass *klass) drag_class->set_cursor = gdk_macos_drag_set_cursor; drag_class->cancel = gdk_macos_drag_cancel; drag_class->drop_performed = gdk_macos_drag_drop_performed; - drag_class->handle_event = gdk_macos_drag_handle_event; properties [PROP_DRAG_SURFACE] = g_param_spec_object ("drag-surface", NULL, NULL, @@ -624,11 +357,113 @@ gdk_macos_drag_init (GdkMacosDrag *self) } gboolean -_gdk_macos_drag_begin (GdkMacosDrag *self) +_gdk_macos_drag_begin (GdkMacosDrag *self, + GdkContentProvider *content, + GdkMacosWindow *window) { + NSArray *items; + NSDraggingSession *session; + NSPasteboardItem *item; + NSEvent *nsevent; + g_return_val_if_fail (GDK_IS_MACOS_DRAG (self), FALSE); + g_return_val_if_fail (GDK_IS_MACOS_WINDOW (window), FALSE); - _gdk_macos_surface_show (GDK_MACOS_SURFACE (self->drag_surface)); + GDK_BEGIN_MACOS_ALLOC_POOL; - return drag_grab (self); + item = [[GdkMacosPasteboardItem alloc] initForDrag:GDK_DRAG (self) withContentProvider:content]; + items = [NSArray arrayWithObject:item]; + nsevent = _gdk_macos_display_get_last_nsevent (); + + session = [[window contentView] beginDraggingSessionWithItems:items + event:nsevent + source:window]; + + GDK_END_MACOS_ALLOC_POOL; + + _gdk_macos_display_set_drag (GDK_MACOS_DISPLAY (gdk_drag_get_display (GDK_DRAG (self))), + [session draggingSequenceNumber], + GDK_DRAG (self)); + + return TRUE; +} + +NSDragOperation +_gdk_macos_drag_operation (GdkMacosDrag *self) +{ + NSDragOperation operation = NSDragOperationNone; + GdkDragAction actions; + + g_return_val_if_fail (GDK_IS_MACOS_DRAG (self), NSDragOperationNone); + + actions = gdk_drag_get_actions (GDK_DRAG (self)); + + if (actions & GDK_ACTION_LINK) + operation |= NSDragOperationLink; + + if (actions & GDK_ACTION_MOVE) + operation |= NSDragOperationMove; + + if (actions & GDK_ACTION_COPY) + operation |= NSDragOperationCopy; + + return operation; +} + +GdkDragAction +_gdk_macos_drag_ns_operation_to_action (NSDragOperation operation) +{ + if (operation & NSDragOperationCopy) + return GDK_ACTION_COPY; + if (operation & NSDragOperationMove) + return GDK_ACTION_MOVE; + if (operation & NSDragOperationLink) + return GDK_ACTION_LINK; + return 0; +} + +void +_gdk_macos_drag_surface_move (GdkMacosDrag *self, + int x_root, + int y_root) +{ + g_return_if_fail (GDK_IS_MACOS_DRAG (self)); + + self->last_x = x_root; + self->last_y = y_root; + + if (GDK_IS_MACOS_SURFACE (self->drag_surface)) + _gdk_macos_surface_move (GDK_MACOS_SURFACE (self->drag_surface), + x_root - self->hot_x, + y_root - self->hot_y); +} + +void +_gdk_macos_drag_set_start_position (GdkMacosDrag *self, + int start_x, + int start_y) +{ + g_return_if_fail (GDK_IS_MACOS_DRAG (self)); + + self->start_x = start_x; + self->start_y = start_y; +} + +void +_gdk_macos_drag_set_actions (GdkMacosDrag *self, + GdkModifierType mods) +{ + GdkDragAction suggested_action; + GdkDragAction possible_actions; + + g_assert (GDK_IS_MACOS_DRAG (self)); + + gdk_drag_get_current_actions (mods, + GDK_BUTTON_PRIMARY, + gdk_drag_get_actions (GDK_DRAG (self)), + &suggested_action, + &possible_actions); + + gdk_drag_set_selected_action (GDK_DRAG (self), suggested_action); + gdk_drag_set_actions (GDK_DRAG (self), possible_actions); } diff --git a/gdk/macos/gdkmacoseventsource.c b/gdk/macos/gdkmacoseventsource.c index a60fb558c9..121efc679b 100644 --- a/gdk/macos/gdkmacoseventsource.c +++ b/gdk/macos/gdkmacoseventsource.c @@ -37,11 +37,15 @@ /* * This file implementations integration between the GLib main loop and * the native system of the Core Foundation run loop and Cocoa event - * handling. There are basically two different cases that we need to - * handle: either the GLib main loop is in control (the application - * has called gtk_main(), or is otherwise iterating the main loop), or - * CFRunLoop is in control (we are in a modal operation such as window - * resizing or drag-and-drop.) + * handling. There are basically three different cases that we need to + * handle: + * + * - the GLib main loop is in control. The application has called + * gtk_main(), or is otherwise iterating the main loop. + * - CFRunLoop is in control. We are in a modal operation such as window + * resizing. + * - CFRunLoop is running a nested loop. This happens when a drag-and-drop + * operation has been initiated. * * When the GLib main loop is in control we integrate in native event * handling in two ways: first we add a GSource that handles checking @@ -57,14 +61,23 @@ * stages of the GLib main loop (prepare, check, dispatch), and make the * appropriate calls into GLib. * - * Both cases share a single problem: the OS X API’s don’t allow us to + * When initiating a drag operation, a nested CFRunLoop is executed. + * The nested run loop is started when fetching a native event in our GLib + * main loop. The application does not receive any events until the nested loop + * is finished. We work around this by forwarding the + * events that trigger the callbacks of the NSDraggingSource protocol. + * The "run loop observer" is executing the GLib main loop stages as long as we're + * in the nested run loop, as if CFRunLoop were in control. + * See also GdkMacosWindow. + * + * All cases share a single problem: the macOS API’s don’t allow us to * wait simultaneously for file descriptors and for events. So when we * need to do a blocking wait that includes file descriptor activity, we * push the actual work of calling select() to a helper thread (the * "select thread") and wait for native events in the main thread. * * The main known limitation of this code is that if a callback is triggered - * via the OS X run loop while we are "polling" (in either case described + * via the macOS run loop while we are "polling" (in either case described * above), iteration of the GLib main loop is not possible from within * that callback. If the programmer tries to do so explicitly, then they * will get a warning from GLib "main loop already active in another thread". @@ -640,6 +653,23 @@ _gdk_macos_event_source_get_pending (void) return event; } +static void +_gdk_macos_event_source_queue_event (NSEvent *event) +{ + /* Just used to wake us up; if an event and a FD arrived at the same + * time; could have come from a previous iteration in some cases, + * but the spurious wake up is harmless if a little inefficient. + */ + if (!event || + ([event type] == NSEventTypeApplicationDefined && + [event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP)) + return; + + if (!current_events) + current_events = g_queue_new (); + g_queue_push_head (current_events, [event retain]); +} + static gboolean gdk_macos_event_source_prepare (GSource *source, int *timeout) @@ -782,23 +812,7 @@ poll_func (GPollFD *ufds, if (last_ufds == ufds && n_ready < 0) n_ready = select_thread_collect_poll (ufds, nfds); - if (event && - [event type] == NSEventTypeApplicationDefined && - [event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP) - { - /* Just used to wake us up; if an event and a FD arrived at the same - * time; could have come from a previous iteration in some cases, - * but the spurious wake up is harmless if a little inefficient. - */ - event = NULL; - } - - if (event) - { - if (!current_events) - current_events = g_queue_new (); - g_queue_push_head (current_events, [event retain]); - } + _gdk_macos_event_source_queue_event (event); return n_ready; } @@ -1018,7 +1032,10 @@ run_loop_observer_callback (CFRunLoopObserverRef observer, break; } - if (getting_events > 0) /* Activity we triggered */ + /* DnD starts a nested runloop, or so it seems. + If we have such a loop, we still want to run + our idle handlers. */ + if (getting_events > 0 && current_loop_level < 2) return; switch (activity) @@ -1042,7 +1059,6 @@ run_loop_observer_callback (CFRunLoopObserverRef observer, run_loop_exit (); break; case kCFRunLoopAllActivities: - /* TODO: Do most of the above? */ default: break; } diff --git a/gdk/macos/gdkmacospasteboard-private.h b/gdk/macos/gdkmacospasteboard-private.h new file mode 100644 index 0000000000..fdeb936535 --- /dev/null +++ b/gdk/macos/gdkmacospasteboard-private.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __GDK_MACOS_PASTEBOARD_PRIVATE_H__ +#define __GDK_MACOS_PASTEBOARD_PRIVATE_H__ + +#include +#include + +#include "gdkclipboardprivate.h" + +G_BEGIN_DECLS + +@interface GdkMacosPasteboardItemDataProvider : NSObject +{ + GdkContentProvider *_contentProvider; + GdkClipboard *_clipboard; + GdkDrag *_drag; +} + +-(id)initForClipboard:(GdkClipboard *)clipboard withContentProvider:(GdkContentProvider *)contentProvider; +-(id)initForDrag:(GdkDrag *)drag withContentProvider:(GdkContentProvider *)contentProvider; + +@end + +@interface GdkMacosPasteboardItem : NSPasteboardItem +{ + GdkContentProvider *_contentProvider; + GdkClipboard *_clipboard; + GdkDrag *_drag; + NSRect _draggingFrame; +} + +-(id)initForClipboard:(GdkClipboard *)clipboard withContentProvider:(GdkContentProvider *)contentProvider; +-(id)initForDrag:(GdkDrag *)drag withContentProvider:(GdkContentProvider *)contentProvider; + +@end + +NSPasteboardType _gdk_macos_pasteboard_to_ns_type (const char *mime_type, + NSPasteboardType *alternate); +const char *_gdk_macos_pasteboard_from_ns_type (NSPasteboardType type); +GdkContentFormats *_gdk_macos_pasteboard_load_formats (NSPasteboard *pasteboard); +void _gdk_macos_pasteboard_register_drag_types (NSWindow *window); +void _gdk_macos_pasteboard_read_async (GObject *object, + NSPasteboard *pasteboard, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GInputStream *_gdk_macos_pasteboard_read_finish (GObject *object, + GAsyncResult *result, + const char **out_mime_type, + GError **error); + +G_END_DECLS + +#endif /* __GDK_MACOS_PASTEBOARD_PRIVATE_H__ */ diff --git a/gdk/macos/gdkmacospasteboard.c b/gdk/macos/gdkmacospasteboard.c new file mode 100644 index 0000000000..66b3c9f03b --- /dev/null +++ b/gdk/macos/gdkmacospasteboard.c @@ -0,0 +1,602 @@ +/* + * Copyright © 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gdkdragprivate.h" +#include "gdkmacospasteboard-private.h" +#include "gdkmacosutils-private.h" + +enum { + TYPE_STRING, + TYPE_PBOARD, + TYPE_URL, + TYPE_FILE_URL, + TYPE_COLOR, + TYPE_TIFF, + TYPE_PNG, + TYPE_LAST +}; + +#define PTYPE(k) (get_pasteboard_type(TYPE_##k)) + +static NSPasteboardType pasteboard_types[TYPE_LAST]; + +static NSPasteboardType +get_pasteboard_type (int type) +{ + static gsize initialized = FALSE; + + g_assert (type >= 0); + g_assert (type < TYPE_LAST); + + if (g_once_init_enter (&initialized)) + { + pasteboard_types[TYPE_PNG] = NSPasteboardTypePNG; + pasteboard_types[TYPE_STRING] = NSPasteboardTypeString; + pasteboard_types[TYPE_TIFF] = NSPasteboardTypeTIFF; + pasteboard_types[TYPE_COLOR] = NSPasteboardTypeColor; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + pasteboard_types[TYPE_PBOARD] = NSStringPboardType; + G_GNUC_END_IGNORE_DEPRECATIONS + +#ifdef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER + pasteboard_types[TYPE_URL] = NSPasteboardTypeURL; + pasteboard_types[TYPE_FILE_URL] = NSPasteboardTypeFileURL; +#else + pasteboard_types[TYPE_URL] = [[NSString alloc] initWithUTF8String:"public.url"]; + pasteboard_types[TYPE_FILE_URL] = [[NSString alloc] initWithUTF8String:"public.file-url"]; +#endif + + g_once_init_leave (&initialized, TRUE); + } + + return pasteboard_types[type]; +} + +const char * +_gdk_macos_pasteboard_from_ns_type (NSPasteboardType type) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + + if ([type isEqualToString:PTYPE(STRING)] || + [type isEqualToString:PTYPE(PBOARD)]) + return g_intern_string ("text/plain;charset=utf-8"); + else if ([type isEqualToString:PTYPE(URL)] || + [type isEqualToString:PTYPE(FILE_URL)]) + return g_intern_string ("text/uri-list"); + else if ([type isEqualToString:PTYPE(COLOR)]) + return g_intern_string ("application/x-color"); + else if ([type isEqualToString:PTYPE(TIFF)]) + return g_intern_string ("image/tiff"); + else if ([type isEqualToString:PTYPE(PNG)]) + return g_intern_string ("image/png"); + + G_GNUC_END_IGNORE_DEPRECATIONS; + + return NULL; +} + +NSPasteboardType +_gdk_macos_pasteboard_to_ns_type (const char *mime_type, + NSPasteboardType *alternate) +{ + if (alternate) + *alternate = NULL; + + if (g_strcmp0 (mime_type, "text/plain;charset=utf-8") == 0) + { + return PTYPE(STRING); + } + else if (g_strcmp0 (mime_type, "text/uri-list") == 0) + { + if (alternate) + *alternate = PTYPE(URL); + return PTYPE(FILE_URL); + } + else if (g_strcmp0 (mime_type, "application/x-color") == 0) + { + return PTYPE(COLOR); + } + else if (g_strcmp0 (mime_type, "image/tiff") == 0) + { + return PTYPE(TIFF); + } + else if (g_strcmp0 (mime_type, "image/png") == 0) + { + return PTYPE(PNG); + } + + return nil; +} + +static void +populate_content_formats (GdkContentFormatsBuilder *builder, + NSPasteboardType type) +{ + const char *mime_type; + + g_assert (builder != NULL); + g_assert (type != NULL); + + if ((mime_type = _gdk_macos_pasteboard_from_ns_type (type))) + gdk_content_formats_builder_add_mime_type (builder, mime_type); +} + +static GdkContentFormats * +load_offer_formats (NSPasteboard *pasteboard) +{ + GDK_BEGIN_MACOS_ALLOC_POOL; + + GdkContentFormatsBuilder *builder; + GdkContentFormats *formats; + + builder = gdk_content_formats_builder_new (); + for (NSPasteboardType type in [pasteboard types]) + populate_content_formats (builder, type); + formats = gdk_content_formats_builder_free_to_formats (builder); + + GDK_END_MACOS_ALLOC_POOL; + + return g_steal_pointer (&formats); +} + +GdkContentFormats * +_gdk_macos_pasteboard_load_formats (NSPasteboard *pasteboard) +{ + return load_offer_formats (pasteboard); +} + +static GInputStream * +create_stream_from_nsdata (NSData *data) +{ + const guint8 *bytes = [data bytes]; + gsize len = [data length]; + + return g_memory_input_stream_new_from_data (g_memdup2 (bytes, len), len, g_free); +} + +void +_gdk_macos_pasteboard_read_async (GObject *object, + NSPasteboard *pasteboard, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDK_BEGIN_MACOS_ALLOC_POOL; + + GdkContentFormats *offer_formats = NULL; + const char *mime_type; + GInputStream *stream = NULL; + GTask *task = NULL; + + g_assert (G_IS_OBJECT (object)); + g_assert (pasteboard != NULL); + g_assert (formats != NULL); + + task = g_task_new (object, cancellable, callback, user_data); + g_task_set_source_tag (task, _gdk_macos_pasteboard_read_async); + g_task_set_priority (task, io_priority); + + offer_formats = load_offer_formats (pasteboard); + mime_type = gdk_content_formats_match_mime_type (formats, offer_formats); + + if (mime_type == NULL) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s", + _("No compatible transfer format found")); + goto cleanup; + } + + if (strcmp (mime_type, "text/plain;charset=utf-8") == 0) + { + NSString *nsstr = [pasteboard stringForType:NSPasteboardTypeString]; + + if (nsstr != NULL) + { + const char *str = [nsstr UTF8String]; + stream = g_memory_input_stream_new_from_data (g_strdup (str), + strlen (str) + 1, + g_free); + } + } + else if (strcmp (mime_type, "text/uri-list") == 0) + { + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + + if ([[pasteboard types] containsObject:PTYPE(FILE_URL)]) + { + GString *str = g_string_new (NULL); + NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType]; + gsize n_files = [files count]; + char *data; + guint len; + + for (gsize i = 0; i < n_files; ++i) + { + NSString* uriString = [files objectAtIndex:i]; + uriString = [@"file://" stringByAppendingString:uriString]; + uriString = [uriString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + g_string_append_printf (str, + "%s\r\n", + [uriString cStringUsingEncoding:NSUTF8StringEncoding]); + } + + len = str->len; + data = g_string_free (str, FALSE); + stream = g_memory_input_stream_new_from_data (data, len, g_free); + } + + G_GNUC_END_IGNORE_DEPRECATIONS; + } + else if (strcmp (mime_type, "application/x-color") == 0) + { + NSColorSpace *colorspace; + NSColor *nscolor; + guint16 color[4]; + + colorspace = [NSColorSpace genericRGBColorSpace]; + nscolor = [[NSColor colorFromPasteboard:pasteboard] + colorUsingColorSpace:colorspace]; + + color[0] = 0xffff * [nscolor redComponent]; + color[1] = 0xffff * [nscolor greenComponent]; + color[2] = 0xffff * [nscolor blueComponent]; + color[3] = 0xffff * [nscolor alphaComponent]; + + stream = g_memory_input_stream_new_from_data (g_memdup2 (&color, sizeof color), + sizeof color, + g_free); + } + else if (strcmp (mime_type, "image/tiff") == 0) + { + NSData *data = [pasteboard dataForType:PTYPE(TIFF)]; + stream = create_stream_from_nsdata (data); + } + else if (strcmp (mime_type, "image/png") == 0) + { + NSData *data = [pasteboard dataForType:PTYPE(PNG)]; + stream = create_stream_from_nsdata (data); + } + + if (stream != NULL) + { + g_task_set_task_data (task, g_strdup (mime_type), g_free); + g_task_return_pointer (task, g_steal_pointer (&stream), g_object_unref); + } + else + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Failed to decode contents with mime-type of '%s'"), + mime_type); + } + +cleanup: + g_clear_object (&task); + g_clear_pointer (&offer_formats, gdk_content_formats_unref); + + GDK_END_MACOS_ALLOC_POOL; +} + +GInputStream * +_gdk_macos_pasteboard_read_finish (GObject *object, + GAsyncResult *result, + const char **out_mime_type, + GError **error) +{ + GTask *task = (GTask *)result; + + g_assert (G_IS_OBJECT (object)); + g_assert (G_IS_TASK (task)); + + if (out_mime_type != NULL) + *out_mime_type = g_strdup (g_task_get_task_data (task)); + + return g_task_propagate_pointer (task, error); +} + +void +_gdk_macos_pasteboard_register_drag_types (NSWindow *window) +{ + [window registerForDraggedTypes:[NSArray arrayWithObjects:PTYPE(STRING), + PTYPE(PBOARD), + PTYPE(URL), + PTYPE(FILE_URL), + PTYPE(COLOR), + PTYPE(TIFF), + PTYPE(PNG), + nil]]; +} + +@implementation GdkMacosPasteboardItemDataProvider + +-(id)initForClipboard:(GdkClipboard*)clipboard withContentProvider:(GdkContentProvider*)contentProvider +{ + [super init]; + g_set_object (&self->_clipboard, clipboard); + g_set_object (&self->_contentProvider, contentProvider); + return self; +} + +-(id)initForDrag:(GdkDrag*)drag withContentProvider:(GdkContentProvider*)contentProvider +{ + [super init]; + g_set_object (&self->_drag, drag); + g_set_object (&self->_contentProvider, contentProvider); + return self; +} + +-(void)dealloc +{ + g_clear_object (&self->_contentProvider); + g_clear_object (&self->_clipboard); + g_clear_object (&self->_drag); + [super dealloc]; +} + +-(NSArray *)types +{ + NSMutableArray *ret = [[NSMutableArray alloc] init]; + GdkContentFormats *serializable; + const char * const *mime_types; + gsize n_mime_types; + + serializable = gdk_content_provider_ref_storable_formats (self->_contentProvider); + serializable = gdk_content_formats_union_serialize_mime_types (serializable); + mime_types = gdk_content_formats_get_mime_types (serializable, &n_mime_types); + + for (gsize i = 0; i < n_mime_types; i++) + { + const char *mime_type = mime_types[i]; + NSPasteboardType type; + NSPasteboardType alternate = nil; + + if ((type = _gdk_macos_pasteboard_to_ns_type (mime_type, &alternate))) + { + [ret addObject:type]; + if (alternate) + [ret addObject:alternate]; + } + } + + gdk_content_formats_unref (serializable); + + /* Default to an url type (think gobject://internal) + * to support internal, GType-based DnD. + */ + if (n_mime_types == 0) + { + GdkContentFormats *formats; + gsize n_gtypes; + + formats = gdk_content_provider_ref_formats (self->_contentProvider); + gdk_content_formats_get_gtypes (formats, &n_gtypes); + + if (n_gtypes) + [ret addObject:NSPasteboardTypeURL]; + + gdk_content_formats_unref (formats); + } + + return g_steal_pointer (&ret); +} + +typedef struct +{ + GMemoryOutputStream *stream; + NSPasteboardItem *item; + NSPasteboardType type; + GMainContext *main_context; + guint done : 1; +} WriteRequest; + +static void +write_request_free (WriteRequest *wr) +{ + g_clear_pointer (&wr->main_context, g_main_context_unref); + g_clear_object (&wr->stream); + [wr->item release]; + g_slice_free (WriteRequest, wr); +} + +static void +on_data_ready_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDK_BEGIN_MACOS_ALLOC_POOL; + + WriteRequest *wr = user_data; + GError *error = NULL; + NSData *data = nil; + gboolean ret; + + g_assert (G_IS_OBJECT (object)); + g_assert (GDK_IS_CLIPBOARD (object) || GDK_IS_DRAG (object)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (wr != NULL); + g_assert (G_IS_MEMORY_OUTPUT_STREAM (wr->stream)); + g_assert ([wr->item isKindOfClass:[NSPasteboardItem class]]); + + if (GDK_IS_CLIPBOARD (object)) + ret = gdk_clipboard_write_finish (GDK_CLIPBOARD (object), result, &error); + else if (GDK_IS_DRAG (object)) + ret = gdk_drag_write_finish (GDK_DRAG (object), result, &error); + else + g_return_if_reached (); + + if (ret) + { + gsize size; + gpointer bytes; + + g_output_stream_close (G_OUTPUT_STREAM (wr->stream), NULL, NULL); + + size = g_memory_output_stream_get_data_size (wr->stream); + bytes = g_memory_output_stream_steal_data (wr->stream); + data = [[NSData alloc] initWithBytesNoCopy:bytes + length:size + deallocator:^(void *alloc, NSUInteger length) { g_free (alloc); }]; + } + else + { + g_warning ("Failed to serialize pasteboard contents: %s", + error->message); + g_clear_error (&error); + } + + [wr->item setData:data forType:wr->type]; + + wr->done = TRUE; + + GDK_END_MACOS_ALLOC_POOL; +} + +-(void)pasteboard:(NSPasteboard *)pasteboard item:(NSPasteboardItem *)item provideDataForType:(NSPasteboardType)type +{ + const char *mime_type = _gdk_macos_pasteboard_from_ns_type (type); + GMainContext *main_context = g_main_context_default (); + WriteRequest *wr; + + if (self->_contentProvider == NULL || mime_type == NULL) + { + [item setData:[NSData data] forType:type]; + return; + } + + wr = g_slice_new0 (WriteRequest); + wr->item = [item retain]; + wr->stream = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ()); + wr->type = type; + wr->main_context = g_main_context_ref (main_context); + wr->done = FALSE; + + if (GDK_IS_CLIPBOARD (self->_clipboard)) + gdk_clipboard_write_async (self->_clipboard, + mime_type, + G_OUTPUT_STREAM (wr->stream), + G_PRIORITY_DEFAULT, + NULL, + on_data_ready_cb, + wr); + else if (GDK_IS_DRAG (self->_drag)) + gdk_drag_write_async (self->_drag, + mime_type, + G_OUTPUT_STREAM (wr->stream), + G_PRIORITY_DEFAULT, + NULL, + on_data_ready_cb, + wr); + else + g_return_if_reached (); + + /* We're forced to provide data synchronously via this API + * so we must block on the main loop. Using another main loop + * than the default tends to get us locked up here, so that is + * what we'll do for now. + */ + while (!wr->done) + g_main_context_iteration (wr->main_context, TRUE); + + write_request_free (wr); +} + +-(void)pasteboardFinishedWithDataProvider:(NSPasteboard *)pasteboard +{ + g_clear_object (&self->_clipboard); + g_clear_object (&self->_drag); + g_clear_object (&self->_contentProvider); +} + +@end + +@implementation GdkMacosPasteboardItem + +-(id)initForClipboard:(GdkClipboard*)clipboard withContentProvider:(GdkContentProvider*)contentProvider +{ + GdkMacosPasteboardItemDataProvider *dataProvider; + + dataProvider = [[GdkMacosPasteboardItemDataProvider alloc] initForClipboard:clipboard withContentProvider:contentProvider]; + + [super init]; + g_set_object (&self->_clipboard, clipboard); + g_set_object (&self->_contentProvider, contentProvider); + [self setDataProvider:dataProvider forTypes:[dataProvider types]]; + + [dataProvider release]; + + return self; +} + +-(id)initForDrag:(GdkDrag*)drag withContentProvider:(GdkContentProvider*)contentProvider +{ + GdkMacosPasteboardItemDataProvider *dataProvider; + + dataProvider = [[GdkMacosPasteboardItemDataProvider alloc] initForDrag:drag withContentProvider:contentProvider]; + + [super init]; + g_set_object (&self->_drag, drag); + g_set_object (&self->_contentProvider, contentProvider); + [self setDataProvider:dataProvider forTypes:[dataProvider types]]; + + [dataProvider release]; + + return self; +} + +-(void)dealloc +{ + g_clear_object (&self->_contentProvider); + g_clear_object (&self->_clipboard); + g_clear_object (&self->_drag); + [super dealloc]; +} + +-(NSRect)draggingFrame +{ + return self->_draggingFrame; +} + +-(void)setDraggingFrame:(NSRect)draggingFrame; +{ + self->_draggingFrame = draggingFrame; +} + +-(id)item +{ + return self; +} + +-(NSArray* (^) (void))imageComponentsProvider +{ + return nil; +} + +@end diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 37400d1892..a002dc7db3 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -446,7 +446,7 @@ gdk_macos_surface_drag_begin (GdkSurface *surface, gdk_drag_get_selected_action (GDK_DRAG (drag))); gdk_drag_set_cursor (GDK_DRAG (drag), cursor); - if (!_gdk_macos_drag_begin (drag)) + if (!_gdk_macos_drag_begin (drag, content, self->window)) { g_object_unref (drag); return NULL; diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build index bd7bbb5324..b3baefb898 100644 --- a/gdk/macos/meson.build +++ b/gdk/macos/meson.build @@ -19,6 +19,7 @@ gdk_macos_sources = files([ 'gdkmacoseventsource.c', 'gdkmacoskeymap.c', 'gdkmacosmonitor.c', + 'gdkmacospasteboard.c', 'gdkmacospopupsurface.c', 'gdkmacosseat.c', 'gdkmacossurface.c',