[gtk/wip/chergert/gdk-macos-gdkdrag: 7/8] macos: refactor pasteboard components into separate module




commit a67441700025915a8a795d2d45ce928c9e2df266
Author: Christian Hergert <chergert redhat com>
Date:   Fri Jun 18 11:27:54 2021 -0700

    macos: refactor pasteboard components into separate module
    
    This will make it easier to reuse from drag integration so that we don't
    require clipboards for everything.
    
    We will need to subclass the pasteboard provider twice, however, both
    for clipboard and dragging.

 gdk/macos/GdkMacosWindow.c             |   4 +-
 gdk/macos/gdkmacosclipboard-private.h  |  22 +-
 gdk/macos/gdkmacosclipboard.c          | 373 +----------------------------
 gdk/macos/gdkmacospasteboard-private.h |  64 +++++
 gdk/macos/gdkmacospasteboard.c         | 417 +++++++++++++++++++++++++++++++++
 gdk/macos/meson.build                  |   1 +
 6 files changed, 500 insertions(+), 381 deletions(-)
---
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
index 714a5047cd..9aa1c40916 100644
--- a/gdk/macos/GdkMacosWindow.c
+++ b/gdk/macos/GdkMacosWindow.c
@@ -28,10 +28,10 @@
 #import "GdkMacosGLView.h"
 #import "GdkMacosWindow.h"
 
-#include "gdkmacosclipboard-private.h"
 #include "gdkmacosdisplay-private.h"
 #include "gdkmacosdrop-private.h"
 #include "gdkmacosmonitor-private.h"
+#include "gdkmacospasteboard-private.h"
 #include "gdkmacossurface-private.h"
 #include "gdkmacospopupsurface-private.h"
 #include "gdkmacostoplevelsurface-private.h"
@@ -290,7 +290,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;
 }
diff --git a/gdk/macos/gdkmacosclipboard-private.h b/gdk/macos/gdkmacosclipboard-private.h
index ba0b52bf0a..a1fa83f7ca 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,28 +42,13 @@ 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 <NSPasteboardItemDataProvider>
+@interface GdkMacosClipboardDataProvider : GdkMacosPasteboardDataProvider
 {
-  GCancellable  *cancellable;
-  GdkClipboard  *clipboard;
-  char         **mimeTypes;
+  GdkClipboard *clipboard;
 }
 
--(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
--(NSArray<NSPasteboardType> *)types;
+-(id)initClipboard:(GdkMacosClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
 
 @end
 
diff --git a/gdk/macos/gdkmacosclipboard.c b/gdk/macos/gdkmacosclipboard.c
index ba755db0be..0af6dd3c8f 100644
--- a/gdk/macos/gdkmacosclipboard.c
+++ b/gdk/macos/gdkmacosclipboard.c
@@ -22,6 +22,7 @@
 #include <glib/gi18n.h>
 
 #include "gdkmacosclipboard-private.h"
+#include "gdkmacospasteboard-private.h"
 #include "gdkmacosutils-private.h"
 #include "gdk-private.h"
 
@@ -41,56 +42,8 @@ typedef struct
   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)
 {
@@ -100,95 +53,6 @@ write_request_free (WriteRequest *wr)
   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 +63,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,35 +100,25 @@ static void
 _gdk_macos_clipboard_send_to_pasteboard (GdkMacosClipboard  *self,
                                          GdkContentProvider *content)
 {
-  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));
+  g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+  g_assert (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]];
-
-  [self->pasteboard clearContents];
-  if ([self->pasteboard writeObjects:[NSArray arrayWithObject:item]] == NO)
-    g_warning ("Failed to write object to pasteboard");
+  dataProvider = [[GdkMacosClipboardDataProvider alloc] initClipboard:self mimetypes:mime_types];
+  _gdk_macos_pasteboard_send_content (self->pasteboard, content, dataProvider);
+  [dataProvider release];
 
   self->last_change_count = [self->pasteboard changeCount];
 
   g_clear_pointer (&serializable, gdk_content_formats_unref);
-
-  GDK_END_MACOS_ALLOC_POOL;
 }
 
 static gboolean
@@ -368,23 +213,18 @@ _gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self)
 
 @implementation GdkMacosClipboardDataProvider
 
--(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
+-(id)initClipboard:(GdkMacosClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
 {
-  [super init];
+  [super initPasteboard:gdkClipboard->pasteboard mimetypes:mime_types];
 
-  self->mimeTypes = g_strdupv ((char **)mime_types);
-  self->clipboard = g_object_ref (gdkClipboard);
+  self->clipboard = g_object_ref (GDK_CLIPBOARD (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];
 }
@@ -394,27 +234,6 @@ _gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self)
   g_clear_object (&self->clipboard);
 }
 
--(NSArray<NSPasteboardType> *)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,
@@ -460,11 +279,9 @@ on_data_ready_cb (GObject      *object,
   GDK_END_MACOS_ALLOC_POOL;
 }
 
--(void)   pasteboard:(NSPasteboard *)pasteboard
-                item:(NSPasteboardItem *)item
-  provideDataForType:(NSPasteboardType)type
+-(void)pasteboard:(NSPasteboard *)pasteboard item:(NSPasteboardItem *)item 
provideDataForType:(NSPasteboardType)type
 {
-  const char *mime_type = _gdk_macos_clipboard_from_ns_type (type);
+  const char *mime_type = _gdk_macos_pasteboard_from_ns_type (type);
   GMainContext *main_context = g_main_context_default ();
   WriteRequest *wr;
 
@@ -500,170 +317,4 @@ on_data_ready_cb (GObject      *object,
   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/gdkmacospasteboard-private.h b/gdk/macos/gdkmacospasteboard-private.h
new file mode 100644
index 0000000000..c135d82d58
--- /dev/null
+++ b/gdk/macos/gdkmacospasteboard-private.h
@@ -0,0 +1,64 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_PASTEBOARD_PRIVATE_H__
+#define __GDK_MACOS_PASTEBOARD_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <gio/gio.h>
+
+#include "gdkclipboardprivate.h"
+
+G_BEGIN_DECLS
+
+@interface GdkMacosPasteboardDataProvider : NSObject <NSPasteboardItemDataProvider>
+{
+  NSPasteboard  *pasteboard;
+  GCancellable  *cancellable;
+  char         **mimeTypes;
+}
+
+-(id)initPasteboard:(NSPasteboard *)pasteBoard mimetypes:(const char * const *)mime_types;
+-(NSArray<NSPasteboardType> *)types;
+
+@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);
+void               _gdk_macos_pasteboard_send_content        (NSPasteboard                    *pasteboard,
+                                                              GdkContentProvider              *content,
+                                                              GdkMacosPasteboardDataProvider  *dataProvider);
+
+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..0de91518e0
--- /dev/null
+++ b/gdk/macos/gdkmacospasteboard.c
@@ -0,0 +1,417 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdkmacospasteboard-private.h"
+#include "gdkmacosutils-private.h"
+#include "gdk-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);
+}
+
+void
+_gdk_macos_pasteboard_send_content (NSPasteboard                   *pasteboard,
+                                    GdkContentProvider             *content,
+                                    GdkMacosPasteboardDataProvider *dataProvider)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSPasteboardItem *item;
+
+  g_return_if_fail (pasteboard != NULL);
+  g_return_if_fail (GDK_IS_CONTENT_PROVIDER (content));
+
+  item = [[NSPasteboardItem alloc] init];
+  [item setDataProvider:dataProvider forTypes:[dataProvider types]];
+
+  [pasteboard clearContents];
+  if ([pasteboard writeObjects:[NSArray arrayWithObject:item]] == NO)
+    g_warning ("Failed to write object to pasteboard");
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+@implementation GdkMacosPasteboardDataProvider
+
+-(id)initPasteboard:(NSPasteboard *)pasteBoard mimetypes:(const char * const *)mime_types;
+{
+  [super init];
+
+  self->mimeTypes = g_strdupv ((char **)mime_types);
+  self->pasteboard = [pasteBoard retain];
+
+  return self;
+}
+
+-(void)dealloc
+{
+  g_cancellable_cancel (self->cancellable);
+
+  if (self->pasteboard)
+    {
+      [self->pasteboard release];
+      self->pasteboard = nil;
+    }
+
+  g_clear_pointer (&self->mimeTypes, g_strfreev);
+  g_clear_object (&self->cancellable);
+
+  [super dealloc];
+}
+
+-(void)pasteboardFinishedWithDataProvider:(NSPasteboard *)pasteboard
+{
+}
+
+-(void)pasteboard:(NSPasteboard *)pasteboard item:(NSPasteboardItem *)item 
provideDataForType:(NSPasteboardType)type
+{
+}
+
+-(NSArray<NSPasteboardType> *)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_pasteboard_to_ns_type (mime_type, &alternate)))
+        {
+          [ret addObject:type];
+          if (alternate)
+            [ret addObject:alternate];
+        }
+    }
+
+  return g_steal_pointer (&ret);
+}
+
+@end
+
+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]];
+}
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
index 3a10dbf944..79782c18df 100644
--- a/gdk/macos/meson.build
+++ b/gdk/macos/meson.build
@@ -16,6 +16,7 @@ gdk_macos_sources = files([
   'gdkmacoseventsource.c',
   'gdkmacoskeymap.c',
   'gdkmacosmonitor.c',
+  'gdkmacospasteboard.c',
   'gdkmacospopupsurface.c',
   'gdkmacosseat.c',
   'gdkmacossurface.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]