macos: refactor pasteboard components into separate module
authorChristian Hergert <chergert@redhat.com>
Fri, 18 Jun 2021 18:27:54 +0000 (11:27 -0700)
committerArjan Molenaar <gaphor@gmail.com>
Fri, 13 Jan 2023 19:03:55 +0000 (20:03 +0100)
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
gdk/macos/gdkmacosclipboard-private.h
gdk/macos/gdkmacosclipboard.c
gdk/macos/gdkmacospasteboard-private.h [new file with mode: 0644]
gdk/macos/gdkmacospasteboard.c [new file with mode: 0644]
gdk/macos/meson.build

index 36964391b37fbe744101824327689216cafe4787..7680e6d8b013953042d3462d2c4be04fa76b39dc 100644 (file)
 #import "GdkMacosView.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"
@@ -246,7 +246,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;
 }
index ba0b52bf0a203381a125139e88744734751f59e9..a1fa83f7ca01599478e7fed06d600ef24873ba13 100644 (file)
@@ -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
 
index 3a2667c0dfbbceffe883dbfe92b3240b4fea490b..e647950364816185a40327c23214a2b6b30dff5e 100644 (file)
@@ -22,6 +22,7 @@
 #include <glib/gi18n.h>
 
 #include "gdkmacosclipboard-private.h"
+#include "gdkmacospasteboard-private.h"
 #include "gdkmacosutils-private.h"
 #include "gdkprivate.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 (file)
index 0000000..c135d82
--- /dev/null
@@ -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 (file)
index 0000000..73e6af0
--- /dev/null
@@ -0,0 +1,416 @@
+/*
+ * 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"
+
+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]];
+}
index bd7bbb532453c24b742abe6951034812af84533d..b3baefb898e333b44ea6e452a5ff3114d5573aa6 100644 (file)
@@ -19,6 +19,7 @@ gdk_macos_sources = files([
   'gdkmacoseventsource.c',
   'gdkmacoskeymap.c',
   'gdkmacosmonitor.c',
+  'gdkmacospasteboard.c',
   'gdkmacospopupsurface.c',
   'gdkmacosseat.c',
   'gdkmacossurface.c',