diff --git a/gdk/macos/GdkMacosBaseView.c b/gdk/macos/GdkMacosBaseView.c index a86d9712b24d2f93049b450e47b19264b5f464eb..42bbe1ab17a3179573aaf3d2e3b7d86923b0340d 100644 --- a/gdk/macos/GdkMacosBaseView.c +++ b/gdk/macos/GdkMacosBaseView.c @@ -29,6 +29,7 @@ #include "gdkmacossurface-private.h" #include "gdkdebug.h" +#include "gdkdragprivate.h" @implementation GdkMacosBaseView diff --git a/gdk/macos/GdkMacosDraggingSource.c b/gdk/macos/GdkMacosDraggingSource.c new file mode 100644 index 0000000000000000000000000000000000000000..fc53a9e68b235a4061fa5f63521b34c2e35ea1c6 --- /dev/null +++ b/gdk/macos/GdkMacosDraggingSource.c @@ -0,0 +1,92 @@ +/* GdkMacosDraggingSource.c + * + * Copyright 2020 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 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" + +#import "GdkMacosDraggingSource.h" + +#include "gdkdebug.h" +#include "gdkmacosdisplay-private.h" +#include "gdkmacosdrag-private.h" + +@implementation GdkMacosDraggingSource + +- (id)init:(GdkDrag *)drag +{ + g_return_val_if_fail (GDK_IS_MACOS_DRAG (drag), self); + + g_set_object (&self->gdk_drag, drag); + return self; +} + +- (void)dealloc +{ + g_clear_object (&self->gdk_drag); + [super dealloc]; +} + +- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + GdkDrag *drag = self->gdk_drag; + GdkDisplay *display = gdk_drag_get_display (drag); + + GdkModifierType state = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display)); + _gdk_macos_drag_set_actions (drag, state); + + return _gdk_macos_drag_operation (GDK_MACOS_DRAG (drag)); +} + +- (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint +{ + GdkDrag *drag = self->gdk_drag; + GdkDisplay *display = gdk_drag_get_display (drag); + 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 (drag, x, y); + _gdk_macos_drag_set_last_position (drag, x, y); + _gdk_macos_drag_surface_move (GDK_MACOS_DRAG (drag), x, y); +} + +- (void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint +{ + GdkDrag *drag = self->gdk_drag; + GdkDisplay *display = gdk_drag_get_display (drag); + int x, y; + + _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display), screenPoint.x, screenPoint.y, &x, &y); + _gdk_macos_drag_set_last_position (drag, x, y); + _gdk_macos_drag_surface_move (GDK_MACOS_DRAG (drag), x, y); +} + +- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation +{ + GdkDrag *drag = self->gdk_drag; + GdkDisplay *display = gdk_drag_get_display (drag); + + 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 (GDK_MACOS_DISPLAY (display), [session draggingSequenceNumber], NULL); +} + +@end diff --git a/gdk/macos/GdkMacosDraggingSource.h b/gdk/macos/GdkMacosDraggingSource.h new file mode 100644 index 0000000000000000000000000000000000000000..5c550a3f7c18edd912673f139ad0bbc97811d774 --- /dev/null +++ b/gdk/macos/GdkMacosDraggingSource.h @@ -0,0 +1,36 @@ +/* GdkMacosDraggingSource.h + * + * Copyright © 2020 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 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 + */ + +#import +#import + +#include + +#define GDK_IS_MACOS_DRAGGING_SOURCE(obj) ((obj) && [obj isKindOfClass:[GdkMacosDraggingSource class]]) + +@interface GdkMacosDraggingSource : NSObject +{ + GdkDisplay *gdk_display; + GdkDrag *gdk_drag; +} + +- (id)init:(GdkDrag *)drag; + +@end diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 0eb1ec7ca8a533e98b3bb84cb233a6d82b0ade8f..8c5a19e26b4f2f9e050de8d6aaee898706ee23e5 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -27,12 +27,13 @@ #import "GdkMacosView.h" #import "GdkMacosWindow.h" -#include "gdkmacosclipboard-private.h" #include "gdkmacosdisplay-private.h" +#include "gdkmacosdrag-private.h" #include "gdkmacosdrop-private.h" #include "gdkmacosmonitor-private.h" -#include "gdkmacossurface-private.h" +#include "gdkmacospasteboard-private.h" #include "gdkmacospopupsurface-private.h" +#include "gdkmacossurface-private.h" #include "gdkmacostoplevelsurface-private.h" #include "gdkmacosutils-private.h" @@ -246,7 +247,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; } @@ -604,6 +605,7 @@ typedef NSString *CALayerContentsGravity; -(void)draggingEnded:(id )sender { + // [sender draggingSource] != nil if in-app dnd _gdk_macos_display_set_drop ([self gdkDisplay], [sender draggingSequenceNumber], NULL); } @@ -649,6 +651,7 @@ typedef NSString *CALayerContentsGravity; if (drop == NULL) return NO; + gdk_drop_set_actions (drop, GDK_MACOS_DROP (drop)->preferred_action); gdk_drop_emit_drop_event (drop, TRUE, location.x, @@ -665,10 +668,6 @@ typedef NSString *CALayerContentsGravity; return NO; } --(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation -{ -} - -(void)setStyleMask:(NSWindowStyleMask)styleMask { gboolean was_fullscreen; diff --git a/gdk/macos/gdkmacosclipboard-private.h b/gdk/macos/gdkmacosclipboard-private.h index ba0b52bf0a203381a125139e88744734751f59e9..ef70ee3e330987ef4b31ca5750e30baea9fce348 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 ba755db0be741ff6f84b46ba7803dea7ee3a59a6..db4a262092670cdc70f716377849b60cf922eac8 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 "gdk-private.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) { - GDK_BEGIN_MACOS_ALLOC_POOL; + GdkMacosPasteboardItem *item; + NSArray *items; - GdkMacosClipboardDataProvider *dataProvider; - GdkContentFormats *serializable; - NSPasteboardItem *item; - const char * const *mime_types; - gsize n_mime_types; + g_assert (GDK_IS_MACOS_CLIPBOARD (self)); + g_assert (GDK_IS_CONTENT_PROVIDER (content)); - g_return_if_fail (GDK_IS_MACOS_CLIPBOARD (self)); - g_return_if_fail (GDK_IS_CONTENT_PROVIDER (content)); + if (self->pasteboard == NULL) + return; - 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); + GDK_BEGIN_MACOS_ALLOC_POOL; - 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_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 83ae435e49db2c1f0131d31377c194fdd0874da9..122d004cec6ee7f9d769b9858755edd724574ced 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -161,6 +161,7 @@ void _gdk_macos_display_warp_pointer (GdkMacosDisp 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.c b/gdk/macos/gdkmacosdisplay.c index 41be42cfbee8619e94e24b715859aa45b7e85aeb..7c133f9c0bdc2d418017c2ba33e2b24697b43ffc 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -1028,6 +1028,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 98075f27ef51dbc41f63fa913336570f6aba17b4..f2b20010c331921633750a69aa26cb3da71d255f 100644 --- a/gdk/macos/gdkmacosdrag-private.h +++ b/gdk/macos/gdkmacosdrag-private.h @@ -23,6 +23,7 @@ #include "gdkdragprivate.h" #include "gdkmacosdragsurface-private.h" +#include "gdkmacospasteboard-private.h" G_BEGIN_DECLS @@ -44,6 +45,8 @@ struct _GdkMacosDrag GdkSeat *drag_seat; GdkCursor *cursor; + NSInteger sequence; + int hot_x; int hot_y; @@ -53,8 +56,7 @@ struct _GdkMacosDrag int start_x; int start_y; - guint did_update : 1; - guint cancelled : 1; + gboolean cancelled; }; struct _GdkMacosDragClass @@ -62,8 +64,24 @@ 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 *provider, + NSWindow *window, + double quartz_x, + double quartz_y); +void _gdk_macos_drag_surface_move (GdkMacosDrag *self, + double x_root, + double y_root); +void _gdk_macos_drag_set_start_position (GdkDrag *drag, + int start_x, + int start_y); +void _gdk_macos_drag_set_last_position (GdkDrag *drag, + int last_x, + int last_y); +void _gdk_macos_drag_set_actions (GdkDrag *drag, + GdkModifierType mods); +NSDragOperation _gdk_macos_drag_operation (GdkMacosDrag *self); G_END_DECLS diff --git a/gdk/macos/gdkmacosdrag.c b/gdk/macos/gdkmacosdrag.c index c9e2dd19a6a935c5ffbe80782c8436e95ed126f6..9039d3c55c06b2fbb3d673652c76e52d3793d7a7 100644 --- a/gdk/macos/gdkmacosdrag.c +++ b/gdk/macos/gdkmacosdrag.c @@ -32,6 +32,8 @@ #include "gdk/gdkseatprivate.h" #include "gdk/gdk-private.h" +#import "GdkMacosDraggingSource.h" + #define BIG_STEP 20 #define SMALL_STEP 1 #define ANIM_TIME 500000 /* .5 seconds */ @@ -53,6 +55,17 @@ enum { static GParamSpec *properties [N_PROPS]; +static void +gdk_macos_drag_drop_from_display (GdkMacosDrag *self) +{ + GdkDisplay *display; + + g_assert (GDK_IS_MACOS_DRAG (self)); + + display = gdk_drag_get_display (GDK_DRAG (self)); + _gdk_macos_display_set_drag (GDK_MACOS_DISPLAY (display), self->sequence, NULL); +} + static double ease_out_cubic (double t) { @@ -85,12 +98,12 @@ gdk_macos_zoomback_timeout (gpointer data) frame_clock = zb->frame_clock; if (!frame_clock) - return G_SOURCE_REMOVE; + goto hide_surface; current_time = gdk_frame_clock_get_frame_time (frame_clock); f = (current_time - zb->start_time) / (double) ANIM_TIME; if (f >= 1.0) - return G_SOURCE_REMOVE; + goto hide_surface; t = ease_out_cubic (f); @@ -105,6 +118,11 @@ gdk_macos_zoomback_timeout (gpointer data) _gdk_macos_surface_show (GDK_MACOS_SURFACE (drag->drag_surface)); return G_SOURCE_CONTINUE; + +hide_surface: + gdk_surface_hide (GDK_SURFACE (drag->drag_surface)); + + return G_SOURCE_REMOVE; } static GdkSurface * @@ -146,6 +164,8 @@ gdk_macos_drag_drop_done (GdkDrag *drag, g_assert (GDK_IS_MACOS_DRAG (self)); + g_print ("Drop done! success=%d\n", success); + if (success) { gdk_surface_hide (GDK_SURFACE (self->drag_surface)); @@ -187,29 +207,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) @@ -235,22 +232,8 @@ gdk_macos_drag_cancel (GdkDrag *drag, self->cancelled = TRUE; drag_ungrab (self); - gdk_drag_drop_done (drag, FALSE); -} - -static void -gdk_macos_drag_drop_performed (GdkDrag *drag, - guint32 time) -{ - GdkMacosDrag *self = (GdkMacosDrag *)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); + gdk_surface_hide (GDK_SURFACE (self->drag_surface)); + gdk_macos_drag_drop_from_display (self); } static void @@ -311,222 +294,9 @@ gdk_drag_get_current_actions (GdkModifierType state, } static void -gdk_drag_update (GdkDrag *drag, - double x_root, - double y_root, - GdkModifierType mods, - guint32 evtime) +gdk_macos_drag_dnd_finished (GdkDrag *drag) { - 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; - } + gdk_macos_drag_drop_from_display (GDK_MACOS_DRAG (drag)); } static void @@ -597,8 +367,7 @@ gdk_macos_drag_class_init (GdkMacosDragClass *klass) drag_class->drop_done = gdk_macos_drag_drop_done; 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; + drag_class->dnd_finished = gdk_macos_drag_dnd_finished; properties [PROP_DRAG_SURFACE] = g_param_spec_object ("drag-surface", NULL, NULL, @@ -613,12 +382,122 @@ gdk_macos_drag_init (GdkMacosDrag *self) { } +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; +} + gboolean -_gdk_macos_drag_begin (GdkMacosDrag *self) +_gdk_macos_drag_begin (GdkMacosDrag *self, + GdkContentProvider *content, + NSWindow *window, + double quartz_x, + double quartz_y) { + NSArray *items; + NSDraggingSession *session; + NSPasteboardItem *item; + NSEvent *nsevent; + GdkMacosDraggingSource *source; + g_return_val_if_fail (GDK_IS_MACOS_DRAG (self), 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]; + source = [[GdkMacosDraggingSource alloc] init:GDK_DRAG (self)]; + nsevent = _gdk_macos_display_get_last_nsevent (); + + session = [[window contentView] beginDraggingSessionWithItems:items + event:nsevent + source:source]; + self->sequence = [session draggingSequenceNumber]; + + [source autorelease]; + + GDK_END_MACOS_ALLOC_POOL; + + gdk_surface_hide (GDK_SURFACE (self->drag_surface)); + + _gdk_macos_display_set_drag (GDK_MACOS_DISPLAY (gdk_drag_get_display (GDK_DRAG (self))), + self->sequence, + GDK_DRAG (self)); + return TRUE; +} + +void +_gdk_macos_drag_surface_move (GdkMacosDrag *self, + double x_root, + double y_root) +{ + g_return_if_fail (GDK_IS_MACOS_DRAG (self)); + + 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 (GdkDrag *drag, + int start_x, + int start_y) +{ + GdkMacosDrag *self = (GdkMacosDrag *) drag; + + g_assert (GDK_IS_MACOS_DRAG (self)); + + self->start_x = start_x; + self->start_y = start_y; +} + +void +_gdk_macos_drag_set_last_position (GdkDrag *drag, + int last_x, + int last_y) +{ + GdkMacosDrag *self = (GdkMacosDrag *) drag; + + g_assert (GDK_IS_MACOS_DRAG (self)); + + self->last_x = last_x; + self->last_y = last_y; +} + +void +_gdk_macos_drag_set_actions (GdkDrag *drag, + GdkModifierType mods) +{ + GdkMacosDrag *self = (GdkMacosDrag *) drag; + 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 (drag), + &suggested_action, + &possible_actions); + + gdk_drag_set_selected_action (drag, suggested_action); + gdk_drag_set_actions (drag, possible_actions); } diff --git a/gdk/macos/gdkmacospasteboard-private.h b/gdk/macos/gdkmacospasteboard-private.h new file mode 100644 index 0000000000000000000000000000000000000000..166fa9b6a95abc57c71a4d8596462cbe223d7da8 --- /dev/null +++ b/gdk/macos/gdkmacospasteboard-private.h @@ -0,0 +1,76 @@ +/* + * 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 + +#define GDK_MACOS_LOCAL_DND_MIME_TYPE "application/x-gtk-local-dnd" + +@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 0000000000000000000000000000000000000000..6a2959a7cca19f0733d55d5d09a43cbadf8e6c6d --- /dev/null +++ b/gdk/macos/gdkmacospasteboard.c @@ -0,0 +1,606 @@ +/* + * 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 "gdkmacospasteboard-private.h" +#include "gdkmacosutils-private.h" +#include "gdk-private.h" +#include "gdkdragprivate.h" + +enum { + TYPE_STRING, + TYPE_PBOARD, + TYPE_URL, + TYPE_FILE_URL, + TYPE_COLOR, + TYPE_TIFF, + TYPE_PNG, + TYPE_INTERNAL, + 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 + + pasteboard_types[TYPE_INTERNAL] = [[NSString alloc] initWithUTF8String:"org.gtk.pasteboard.internal"]; + + 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"); + else if ([type isEqualToString:PTYPE(INTERNAL)]) + return g_intern_string (GDK_MACOS_LOCAL_DND_MIME_TYPE); + + 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); + } + else if (g_strcmp0 (mime_type, GDK_MACOS_LOCAL_DND_MIME_TYPE) == 0) + { + return PTYPE(INTERNAL); + } + + 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); + } + else if (strcmp (mime_type, GDK_MACOS_LOCAL_DND_MIME_TYPE) == 0) + { + /* Should be internal copy */ + g_warn_if_reached (); + } + + 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), + PTYPE(INTERNAL), + 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 (guint 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]; + } + } + + if ([ret count] == 0) + { + NSPasteboardType type; + NSPasteboardType alternate = nil; + + if ((type = _gdk_macos_pasteboard_to_ns_type (GDK_MACOS_LOCAL_DND_MIME_TYPE, &alternate))) + [ret addObject:type]; + } + + + 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 + ret = gdk_drag_write_finish (GDK_DRAG (object), result, &error); + + if (ret) + { + gsize size; + gpointer bytes; + + g_output_stream_close (G_OUTPUT_STREAM (wr->stream), NULL, NULL); + + size = g_memory_output_stream_get_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 d10992ffd47fc395b762cc179b8e30f4cbd9e000..31da0b37130b4c8276a5fc996ed0df72f1d7b9ae 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -24,6 +24,7 @@ #include #import "GdkMacosView.h" +#import "GdkMacosDraggingSource.h" #include "gdkmacossurface-private.h" @@ -35,8 +36,9 @@ #include "gdkseatprivate.h" #include "gdksurfaceprivate.h" -#include "gdkmacosdevice.h" +#include "gdkmacosclipboard-private.h" #include "gdkmacosdevice-private.h" +#include "gdkmacosdevice.h" #include "gdkmacosdisplay-private.h" #include "gdkmacosdrag-private.h" #include "gdkmacosdragsurface-private.h" @@ -419,10 +421,9 @@ gdk_macos_surface_drag_begin (GdkSurface *surface, GdkMacosSurface *self = (GdkMacosSurface *)surface; GdkMacosSurface *drag_surface; GdkMacosDrag *drag; - GdkCursor *cursor; + GdkDisplay *display; GdkSeat *seat; - double px; - double py; + int quartz_x, quartz_y; int sx; int sy; @@ -432,13 +433,18 @@ gdk_macos_surface_drag_begin (GdkSurface *surface, g_assert (GDK_IS_MACOS_DEVICE (device)); g_assert (GDK_IS_CONTENT_PROVIDER (content)); + display = gdk_surface_get_display (surface); + _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display), + surface->x + dx, + surface->y + dy, + &quartz_x, &quartz_y); + seat = gdk_device_get_seat (device); - gdk_macos_device_query_state (device, surface, NULL, &px, &py, NULL); - _gdk_macos_surface_get_root_coords (GDK_MACOS_SURFACE (surface), &sx, &sy); + drag_surface = _gdk_macos_surface_new (GDK_MACOS_DISPLAY (surface->display), GDK_SURFACE_TEMP, surface, - sx, sy, 1, 1); + quartz_x, quartz_y, 1, 1); drag = g_object_new (GDK_TYPE_MACOS_DRAG, "drag-surface", drag_surface, "surface", surface, @@ -446,18 +452,16 @@ gdk_macos_surface_drag_begin (GdkSurface *surface, "content", content, "actions", actions, NULL); - g_clear_object (&drag_surface); - - cursor = gdk_drag_get_cursor (GDK_DRAG (drag), - gdk_drag_get_selected_action (GDK_DRAG (drag))); - gdk_drag_set_cursor (GDK_DRAG (drag), cursor); + g_object_unref (drag_surface); - if (!_gdk_macos_drag_begin (drag)) + if (!_gdk_macos_drag_begin (drag, content, self->window, quartz_x, quartz_y)) { g_object_unref (drag); return NULL; } + gdk_seat_ungrab (seat); + /* Hold a reference until drop_done is called */ g_object_ref (drag); diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build index bd7bbb532453c24b742abe6951034812af84533d..8f8d66c6e48d5692029bb0949522ffceec2ded96 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', @@ -27,6 +28,7 @@ gdk_macos_sources = files([ 'GdkMacosLayer.c', 'GdkMacosTile.c', 'GdkMacosView.c', + 'GdkMacosDraggingSource.c', 'GdkMacosWindow.c', ])