[gtk/wip/chergert/quartz4u: 465/465] macos: prototype new GDK backend for macOS



commit 15f79a60cfe11e0cad71736dfae9faea4d5a0010
Author: Christian Hergert <chergert redhat com>
Date:   Thu Apr 23 16:36:46 2020 -0700

    macos: prototype new GDK backend for macOS
    
    This is fairly substantial rewrite of the GDK backend for quartz and
    renamed to macOS to allow for a greenfield implementation.
    
    Many things have come across from the quartz implementation fairly
    intact such as the eventloop integration design and discovery of
    event windows from the NSEvent.
    
    However much has been changed to fit in with the new GDK design and
    how removal of child GdkWindow have been completely eliminated.
    Furthermore, the new GdkPopup allows for regular NSWindow to be used
    to provide popovers unlike the previous implementation.
    
    The object design more closely follows the ideal for a GDK backend.
    
    Views have been broken out into subclasses so that we can support
    multiple GSK renderer paths such as GL and Cairo (and Metal in the
    future). However mixed mode GL and Cairo will not be supported.
    
    A new frame clock implementation using CVDisplayLink provides more
    accurate information about when to draw drawing the next frame. Some
    testing will need to be done here to understand the power implications
    of this.
    
    This implementation has also gained edge snapping for CSD windows.
    
         ** NOTE: This is still very much a work-in-progress **

 gdk/gdkconfig.h.meson                       |    1 +
 gdk/gdkcontentdeserializer.c                |   12 +-
 gdk/gdkcontentserializer.c                  |   18 +-
 gdk/gdkdisplaymanager.c                     |    7 +
 gdk/macos/GdkMacosBaseView.c                |  718 ++++++++++++++++
 gdk/macos/GdkMacosBaseView.h                |   46 +
 gdk/macos/GdkMacosCairoSubview.c            |  171 ++++
 gdk/macos/GdkMacosCairoSubview.h            |   35 +
 gdk/macos/GdkMacosCairoView.c               |  170 ++++
 gdk/macos/GdkMacosCairoView.h               |   37 +
 gdk/macos/GdkMacosGLLayer.c                 |  157 ++++
 gdk/macos/GdkMacosGLLayer.h                 |   40 +
 gdk/macos/GdkMacosWindow.c                  |  732 ++++++++++++++++
 gdk/macos/GdkMacosWindow.h                  |   70 ++
 gdk/macos/edgesnapping.c                    |  229 +++++
 gdk/macos/edgesnapping.h                    |   50 ++
 gdk/macos/gdkdisplaylinksource.c            |  254 ++++++
 gdk/macos/gdkdisplaylinksource.h            |   49 ++
 gdk/macos/gdkmacos.h                        |   32 +
 gdk/macos/gdkmacoscairocontext-private.h    |   38 +
 gdk/macos/gdkmacoscairocontext.c            |  155 ++++
 gdk/macos/gdkmacosclipboard-private.h       |   54 ++
 gdk/macos/gdkmacosclipboard.c               |  576 +++++++++++++
 gdk/macos/gdkmacoscursor-private.h          |   32 +
 gdk/macos/gdkmacoscursor.c                  |  181 ++++
 gdk/macos/gdkmacosdevice.c                  |  208 +++++
 gdk/macos/gdkmacosdevice.h                  |   43 +
 gdk/macos/gdkmacosdisplay-private.h         |  154 ++++
 gdk/macos/gdkmacosdisplay-settings.c        |  194 +++++
 gdk/macos/gdkmacosdisplay-translate.c       | 1219 +++++++++++++++++++++++++++
 gdk/macos/gdkmacosdisplay.c                 | 1071 +++++++++++++++++++++++
 gdk/macos/gdkmacosdisplay.h                 |   47 ++
 gdk/macos/gdkmacosdrag-private.h            |   70 ++
 gdk/macos/gdkmacosdrag.c                    |  617 ++++++++++++++
 gdk/macos/gdkmacosdragsurface-private.h     |   50 ++
 gdk/macos/gdkmacosdragsurface.c             |  138 +++
 gdk/macos/gdkmacoseventsource-private.h     |   40 +
 gdk/macos/gdkmacoseventsource.c             | 1094 ++++++++++++++++++++++++
 gdk/macos/gdkmacosglcontext-private.h       |   60 ++
 gdk/macos/gdkmacosglcontext.c               |  153 ++++
 gdk/macos/gdkmacosglcontext.h               |   43 +
 gdk/macos/gdkmacoskeymap-private.h          |   36 +
 gdk/macos/gdkmacoskeymap.c                  |  698 +++++++++++++++
 gdk/macos/gdkmacoskeymap.h                  |   43 +
 gdk/macos/gdkmacosmonitor-private.h         |   39 +
 gdk/macos/gdkmacosmonitor.c                 |  288 +++++++
 gdk/macos/gdkmacosmonitor.h                 |   43 +
 gdk/macos/gdkmacospopupsurface-private.h    |   48 ++
 gdk/macos/gdkmacospopupsurface.c            |  378 +++++++++
 gdk/macos/gdkmacosseat-private.h            |   35 +
 gdk/macos/gdkmacosseat.c                    |   65 ++
 gdk/macos/gdkmacossurface-private.h         |  131 +++
 gdk/macos/gdkmacossurface.c                 | 1071 +++++++++++++++++++++++
 gdk/macos/gdkmacossurface.h                 |   43 +
 gdk/macos/gdkmacostoplevelsurface-private.h |   47 ++
 gdk/macos/gdkmacostoplevelsurface.c         |  581 +++++++++++++
 gdk/macos/gdkmacosutils-private.h           |   36 +
 gdk/macos/meson.build                       |   62 ++
 gdk/meson.build                             |   14 +-
 gsk/gskrenderer.c                           |    7 +
 gtk/meson.build                             |    8 +-
 meson.build                                 |   17 +-
 meson_options.txt                           |    2 +
 subprojects/atk.wrap                        |    5 +
 testsuite/gtk/meson.build                   |    6 +-
 65 files changed, 12737 insertions(+), 31 deletions(-)
---
diff --git a/gdk/gdkconfig.h.meson b/gdk/gdkconfig.h.meson
index 80666b25f0..867b430e43 100644
--- a/gdk/gdkconfig.h.meson
+++ b/gdk/gdkconfig.h.meson
@@ -12,6 +12,7 @@ G_BEGIN_DECLS
 
 #mesondefine GDK_WINDOWING_X11
 #mesondefine GDK_WINDOWING_BROADWAY
+#mesondefine GDK_WINDOWING_MACOS
 #mesondefine GDK_WINDOWING_WAYLAND
 #mesondefine GDK_WINDOWING_WIN32
 
diff --git a/gdk/gdkcontentdeserializer.c b/gdk/gdkcontentdeserializer.c
index 12594b1fd8..3846da9745 100644
--- a/gdk/gdkcontentdeserializer.c
+++ b/gdk/gdkcontentdeserializer.c
@@ -41,7 +41,7 @@
 
 typedef struct _Deserializer Deserializer;
 
-struct _Deserializer 
+struct _Deserializer
 {
   const char *                    mime_type; /* interned */
   GType                           type;
@@ -424,7 +424,7 @@ lookup_deserializer (const char *mime_type,
           deserializer->type == type)
         return deserializer;
     }
-  
+
   return NULL;
 }
 
@@ -835,8 +835,8 @@ init (void)
   for (f = formats; f; f = f->next)
     {
       GdkPixbufFormat *fmt = f->data;
-      gchar *name; 
- 
+      gchar *name;
+
       name = gdk_pixbuf_format_get_name (fmt);
       if (g_str_equal (name, "png"))
        {
@@ -849,7 +849,7 @@ init (void)
        }
 
       g_free (name);
-    }  
+    }
 
   for (f = formats; f; f = f->next)
     {
@@ -875,7 +875,7 @@ init (void)
 
   g_slist_free (formats);
 
-#ifdef G_OS_UNIX
+#if defined(G_OS_UNIX) && !defined(__APPLE__)
   file_transfer_portal_register ();
 #endif
 
diff --git a/gdk/gdkcontentserializer.c b/gdk/gdkcontentserializer.c
index 63d1232167..ce7af1cb3f 100644
--- a/gdk/gdkcontentserializer.c
+++ b/gdk/gdkcontentserializer.c
@@ -43,7 +43,7 @@
 
 typedef struct _Serializer Serializer;
 
-struct _Serializer 
+struct _Serializer
 {
   const char *                    mime_type; /* interned */
   GType                           type;
@@ -427,7 +427,7 @@ lookup_serializer (const char *mime_type,
           serializer->type == type)
         return serializer;
     }
-  
+
   return NULL;
 }
 
@@ -606,7 +606,7 @@ pixbuf_serializer (GdkContentSerializer *serializer)
   const GValue *value;
   GdkPixbuf *pixbuf;
   const char *name;
-  
+
   name = gdk_content_serializer_get_user_data (serializer);
   value = gdk_content_serializer_get_value (serializer);
 
@@ -732,7 +732,7 @@ file_uri_serializer (GdkContentSerializer *serializer)
   else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
     {
       GSList *l;
-      
+
       for (l = g_value_get_boxed (value); l; l = l->next)
         {
           uri = g_file_get_uri (l->data);
@@ -776,7 +776,7 @@ file_text_serializer (GdkContentSerializer *serializer)
     {
       GString *str;
       GSList *l;
-      
+
       str = g_string_new (NULL);
 
       for (l = g_value_get_boxed (value); l; l = l->next)
@@ -864,8 +864,8 @@ init (void)
   for (f = formats; f; f = f->next)
     {
       GdkPixbufFormat *fmt = f->data;
-      gchar *name; 
- 
+      gchar *name;
+
       name = gdk_pixbuf_format_get_name (fmt);
       if (g_str_equal (name, "png"))
        {
@@ -878,7 +878,7 @@ init (void)
        }
 
       g_free (name);
-    }  
+    }
 
   for (f = formats; f; f = f->next)
     {
@@ -907,7 +907,7 @@ init (void)
 
   g_slist_free (formats);
 
-#ifdef G_OS_UNIX
+#if defined(G_OS_UNIX) && !defined(__APPLE__)
   file_transfer_portal_register ();
 #endif
 
diff --git a/gdk/gdkdisplaymanager.c b/gdk/gdkdisplaymanager.c
index 94ced0387e..a17305fcbe 100644
--- a/gdk/gdkdisplaymanager.c
+++ b/gdk/gdkdisplaymanager.c
@@ -50,6 +50,10 @@
 #include "broadway/gdkprivate-broadway.h"
 #endif
 
+#ifdef GDK_WINDOWING_MACOS
+#include "macos/gdkmacosdisplay-private.h"
+#endif
+
 #ifdef GDK_WINDOWING_WIN32
 #include "win32/gdkwin32.h"
 #include "win32/gdkprivate-win32.h"
@@ -262,6 +266,9 @@ static GdkBackend gdk_backends[] = {
 #ifdef GDK_WINDOWING_QUARTZ
   { "quartz",   _gdk_quartz_display_open },
 #endif
+#ifdef GDK_WINDOWING_MACOS
+  { "macos",   _gdk_macos_display_open },
+#endif
 #ifdef GDK_WINDOWING_WIN32
   { "win32",    _gdk_win32_display_open },
 #endif
diff --git a/gdk/macos/GdkMacosBaseView.c b/gdk/macos/GdkMacosBaseView.c
new file mode 100644
index 0000000000..c6750dfdb9
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -0,0 +1,718 @@
+/* GdkMacosBaseView.c
+ *
+ * Copyright 2005-2007 Imendio AB
+ * Copyright 2011 Hiroyuki Yamamoto
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+/* Text Input Client */
+#define TIC_MARKED_TEXT      "tic-marked-text"
+#define TIC_SELECTED_POS     "tic-selected-pos"
+#define TIC_SELECTED_LEN     "tic-selected-len"
+#define TIC_INSERT_TEXT      "tic-insert-text"
+#define TIC_IN_KEY_DOWN      "tic-in-key-down"
+
+/* GtkIMContext */
+#define GIC_CURSOR_RECT      "gic-cursor-rect"
+#define GIC_FILTER_KEY       "gic-filter-key"
+#define GIC_FILTER_PASSTHRU  0
+#define GIC_FILTER_FILTERED  1
+
+@implementation GdkMacosBaseView
+
+-(id)initWithFrame:(NSRect)frameRect
+{
+  if ((self = [super initWithFrame: frameRect]))
+    {
+      NSRect rect = NSMakeRect (0, 0, 0, 0);
+      NSTrackingAreaOptions options;
+
+      markedRange = NSMakeRange (NSNotFound, 0);
+      selectedRange = NSMakeRange (0, 0);
+      [self setValue: @(YES) forKey: @"postsFrameChangedNotifications"];
+
+      options = (NSTrackingMouseEnteredAndExited |
+                 NSTrackingMouseMoved |
+                 NSTrackingInVisibleRect |
+                 NSTrackingActiveAlways);
+      trackingArea = [[NSTrackingArea alloc] initWithRect:rect
+                                                  options:options
+                                                    owner:(id)self
+                                                 userInfo:nil];
+      [self addTrackingArea:trackingArea];
+    }
+
+  return self;
+}
+
+-(void)setNeedsDisplay:(BOOL)needsDisplay
+{
+  for (id child in [self subviews])
+    [child setNeedsDisplay:needsDisplay];
+}
+
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+  /* Do nothing */
+}
+
+-(BOOL)acceptsFirstMouse
+{
+  return YES;
+}
+
+-(BOOL)mouseDownCanMoveWindow
+{
+  return NO;
+}
+
+-(BOOL)acceptsFirstResponder
+{
+  GDK_NOTE (EVENTS, g_message ("acceptsFirstResponder"));
+  return YES;
+}
+
+-(BOOL)becomeFirstResponder
+{
+  GDK_NOTE (EVENTS, g_message ("becomeFirstResponder"));
+  return YES;
+}
+
+-(BOOL)resignFirstResponder
+{
+  GDK_NOTE (EVENTS, g_message ("resignFirstResponder"));
+  return YES;
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate
+{
+  needsInvalidateShadow = invalidate;
+}
+
+-(NSTrackingArea *)trackingArea
+{
+  return trackingArea;
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+  return [(GdkMacosWindow *)[self window] gdkSurface];
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+  GdkMacosSurface *surface = [self gdkSurface];
+  GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (surface));
+
+  return GDK_MACOS_DISPLAY (display);
+}
+
+-(void)keyDown:(NSEvent *)theEvent
+{
+  /* NOTE: When user press Cmd+A, interpretKeyEvents: will call noop:
+   * method. When user press and hold A to show the accented char window,
+   * it consumed repeating key down events for key 'A' do NOT call
+   * any other method. We use this behavior to determine if this key
+   * down event is filtered by interpretKeyEvents.
+   */
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+  GDK_NOTE (EVENTS, g_message ("keyDown"));
+  [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
+}
+
+-(void)flagsChanged: (NSEvent *)theEvent
+{
+}
+
+-(NSUInteger)characterIndexForPoint:(NSPoint)aPoint
+{
+  GDK_NOTE (EVENTS, g_message ("characterIndexForPoint"));
+  return 0;
+}
+
+-(NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange: (NSRangePointer)actualRange
+{
+  GdkRectangle *rect;
+
+  GDK_NOTE (EVENTS, g_message ("firstRectForCharacterRange"));
+
+  if ((rect = g_object_get_data (G_OBJECT ([self gdkSurface]), GIC_CURSOR_RECT)))
+    {
+      GdkMacosDisplay *display = [self gdkDisplay];
+      int ns_x, ns_y;
+
+      _gdk_macos_display_to_display_coords (display,
+                                            rect->x, rect->y + rect->height,
+                                            &ns_x, &ns_y);
+
+      return NSMakeRect (ns_x, ns_y, rect->width, rect->height);
+    }
+
+  return NSMakeRect (0, 0, 0, 0);
+}
+
+-(NSArray *)validAttributesForMarkedText
+{
+  GDK_NOTE (EVENTS, g_message ("validAttributesForMarkedText"));
+  return [NSArray arrayWithObjects: NSUnderlineStyleAttributeName, nil];
+}
+
+-(NSAttributedString *)attributedSubstringForProposedRange: (NSRange)aRange actualRange: 
(NSRangePointer)actualRange
+{
+  GDK_NOTE (EVENTS, g_message ("attributedSubstringForProposedRange"));
+  return nil;
+}
+
+-(BOOL)hasMarkedText
+{
+  GDK_NOTE (EVENTS, g_message ("hasMarkedText"));
+  return markedRange.location != NSNotFound && markedRange.length != 0;
+}
+
+-(NSRange)markedRange
+{
+  GDK_NOTE (EVENTS, g_message ("markedRange"));
+  return markedRange;
+}
+
+-(NSRange)selectedRange
+{
+  GDK_NOTE (EVENTS, g_message ("selectedRange"));
+  return selectedRange;
+}
+
+-(void)unmarkText
+{
+  GDK_NOTE (EVENTS, g_message ("unmarkText"));
+
+  selectedRange = NSMakeRange (0, 0);
+  markedRange = NSMakeRange (NSNotFound, 0);
+
+  g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, NULL, g_free);
+}
+
+-(void)setMarkedText:(id)aString selectedRange: (NSRange)newSelection replacementRange: 
(NSRange)replacementRange
+{
+  const char *str;
+
+  GDK_NOTE (EVENTS, g_message ("setMarkedText"));
+
+  if (replacementRange.location == NSNotFound)
+    {
+      markedRange = NSMakeRange (newSelection.location, [aString length]);
+      selectedRange = NSMakeRange (newSelection.location, newSelection.length);
+    }
+  else
+    {
+      markedRange = NSMakeRange (replacementRange.location, [aString length]);
+      selectedRange = NSMakeRange (replacementRange.location + newSelection.location, newSelection.length);
+    }
+
+  if ([aString isKindOfClass: [NSAttributedString class]])
+    str = [[aString string] UTF8String];
+  else
+    str = [aString UTF8String];
+
+  g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, g_strdup (str), g_free);
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     TIC_SELECTED_POS,
+                     GUINT_TO_POINTER (selectedRange.location));
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     TIC_SELECTED_LEN,
+                     GUINT_TO_POINTER (selectedRange.length));
+
+  GDK_NOTE (EVENTS, g_message ("setMarkedText: set %s (%p, nsview %p): %s",
+                               TIC_MARKED_TEXT, [self gdkSurface], self,
+                               str ? str : "(empty)"));
+
+  /* handle text input changes by mouse events */
+  if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+    _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)doCommandBySelector:(SEL)aSelector
+{
+  GDK_NOTE (EVENTS, g_message ("doCommandBySelector"));
+
+  if ([self respondsToSelector: aSelector])
+    [self performSelector: aSelector];
+}
+
+-(void)insertText:(id)aString replacementRange: (NSRange)replacementRange
+{
+  const char *str;
+  NSString *string;
+
+  GDK_NOTE (EVENTS, g_message ("insertText"));
+
+  if ([self hasMarkedText])
+    [self unmarkText];
+
+  if ([aString isKindOfClass: [NSAttributedString class]])
+    string = [aString string];
+  else
+    string = aString;
+
+  NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
+  NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+  if ([string rangeOfCharacterFromSet:ctrlChars].length &&
+      [string rangeOfCharacterFromSet:wsnlChars].length == 0)
+    {
+      /* discard invalid text input with Chinese input methods */
+      str = "";
+      [self unmarkText];
+      [[NSTextInputContext currentInputContext] discardMarkedText];
+    }
+  else
+   {
+      str = [string UTF8String];
+   }
+
+  g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_INSERT_TEXT, g_strdup (str), g_free);
+  GDK_NOTE (EVENTS, g_message ("insertText: set %s (%p, nsview %p): %s",
+                               TIC_INSERT_TEXT, [self gdkSurface], self,
+                               str ? str : "(empty)"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+  /* handle text input changes by mouse events */
+  if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+    _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)deleteBackward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteBackward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteForward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteForward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToBeginningOfLine:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteToBeginningOfLine"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToEndOfLine:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteToEndOfLine"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordBackward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteWordBackward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordForward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteWordForward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertBacktab:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("insertBacktab"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertNewline:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("insertNewline"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertTab:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("insertTab"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveBackward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackwardAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveBackwardAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDown:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveDown"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDownAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveDownAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveForward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForwardAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveForwardAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeft:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveLeft"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeftAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveLeftAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRight:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveRight"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRightAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveRightAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocument:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocument"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocumentAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocumentAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLine:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLine"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLineAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLineAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocument:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToEndOfDocument"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocumentAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToEndOfDocumentAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLine:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToEndOfLine"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLineAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveToEndOfLineAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUp:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveUp"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUpAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveUpAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordBackward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackwardAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordBackwardAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForward:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordForward"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForwardAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordForwardAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeft:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordLeft"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeftAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordLeftAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRight:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordRight"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRightAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("moveWordRightAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDown:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("pageDown"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDownAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("pageDownAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUp:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("pageUp"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUpAndModifySelection:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("pageUpAndModifySelection"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectAll:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("selectAll"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectLine:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("selectLine"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectWord:(id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("selectWord"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)noop: (id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("noop"));
+
+  g_object_set_data (G_OBJECT ([self gdkSurface]),
+                     GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+@end
diff --git a/gdk/macos/GdkMacosBaseView.h b/gdk/macos/GdkMacosBaseView.h
new file mode 100644
index 0000000000..7fcfc7e43b
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -0,0 +1,46 @@
+/* GdkMacosBaseView.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+
+#define GDK_IS_MACOS_BASE_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosBaseView class]])
+
+@interface GdkMacosBaseView : NSView <NSTextInputClient>
+{
+  NSTrackingArea *trackingArea;
+  BOOL needsInvalidateShadow;
+  NSRange markedRange;
+  NSRange selectedRange;
+}
+
+-(GdkMacosSurface *)gdkSurface;
+-(GdkMacosDisplay *)gdkDisplay;
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(NSTrackingArea *)trackingArea;
+-(void)setOpaqueRegion:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoSubview.c b/gdk/macos/GdkMacosCairoSubview.c
new file mode 100644
index 0000000000..425b52ac78
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.c
@@ -0,0 +1,171 @@
+/* GdkMacosCairoSubview.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <cairo-quartz.h>
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoSubview.h"
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoSubview
+
+-(BOOL)isOpaque
+{
+  return _isOpaque;
+}
+
+-(BOOL)isFlipped
+{
+  return YES;
+}
+
+-(GdkSurface *)gdkSurface
+{
+  return GDK_SURFACE ([(GdkMacosBaseView *)[self superview] gdkSurface]);
+}
+
+-(void)drawRect:(NSRect)rect
+{
+  CGContextRef cgContext;
+  GdkSurface *gdk_surface;
+  cairo_surface_t *dest;
+  const NSRect *rects = NULL;
+  NSView *root_view;
+  NSInteger n_rects = 0;
+  NSRect abs_bounds;
+  cairo_t *cr;
+  CGSize scale;
+  int scale_factor;
+
+  if (self->cairoSurface == NULL)
+    return;
+
+  /* Acquire everything we need to do translations, drawing, etc */
+  gdk_surface = [self gdkSurface];
+  scale_factor = gdk_surface_get_scale_factor (gdk_surface);
+  root_view = [[self window] contentView];
+  cgContext = [[NSGraphicsContext currentContext] CGContext];
+  abs_bounds = [self convertRect:[self bounds] toView:root_view];
+
+  CGContextSaveGState (cgContext);
+
+  /* Translate scaling to remove HiDPI scaling from CGContext as
+   * cairo will be doing that for us already.
+   */
+  scale = CGSizeMake (1.0, 1.0);
+  scale = CGContextConvertSizeToDeviceSpace (cgContext, scale);
+  CGContextScaleCTM (cgContext, 1.0 / scale.width, 1.0 / scale.height);
+
+  /* Create the cairo surface to draw to the CGContext and translate
+   * coordinates so we can pretend we are in the same coordinate system
+   * as the GDK surface.
+   */
+  dest = cairo_quartz_surface_create_for_cg_context (cgContext,
+                                                     gdk_surface->width * scale_factor,
+                                                     gdk_surface->height * scale_factor);
+  cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+
+  /* Create cairo context and translate things into the origin of
+   * the topmost contentView so that we just draw at 0,0 with a
+   * clip region to paint the surface.
+   */
+  cr = cairo_create (dest);
+  cairo_translate (cr, -abs_bounds.origin.x, -abs_bounds.origin.y);
+
+  /* Clip the cairo context based on the rectangles to be drawn
+   * within the bounding box :rect.
+   */
+  [self getRectsBeingDrawn:&rects count:&n_rects];
+  for (NSInteger i = 0; i < n_rects; i++)
+    {
+      NSRect area = [self convertRect:rects[i] toView:root_view];
+      cairo_rectangle (cr,
+                       area.origin.x, area.origin.y,
+                       area.size.width, area.size.height);
+    }
+  cairo_clip (cr);
+
+  /* Now paint the surface (without blending) as we do not need
+   * any compositing here. The transparent regions (like shadows)
+   * are already on non-opaque layers.
+   */
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_set_source_surface (cr, self->cairoSurface, 0, 0);
+  cairo_paint (cr);
+
+  /* Cleanup state, flush the surface to the backing layer, and
+   * restore GState for future use.
+   */
+  cairo_destroy (cr);
+  cairo_surface_flush (dest);
+  cairo_surface_destroy (dest);
+  CGContextRestoreGState (cgContext);
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)surface
+            withDamage:(cairo_region_t *)region
+{
+  if (surface != self->cairoSurface)
+    {
+      g_clear_pointer (&self->cairoSurface, cairo_surface_destroy);
+      if (surface != NULL)
+        self->cairoSurface = cairo_surface_reference (surface);
+    }
+
+  if (region != NULL)
+    {
+      NSView *root_view = [[self window] contentView];
+      NSRect abs_bounds = [self convertRect:[self bounds] toView:root_view];
+      guint n_rects = cairo_region_num_rectangles (region);
+
+      for (guint i = 0; i < n_rects; i++)
+        {
+          cairo_rectangle_int_t rect;
+          NSRect nsrect;
+
+          cairo_region_get_rectangle (region, i, &rect);
+          nsrect = NSMakeRect (rect.x, rect.y, rect.width, rect.height);
+
+          if (NSIntersectsRect (abs_bounds, nsrect))
+            {
+              nsrect.origin.x -= abs_bounds.origin.x;
+              nsrect.origin.y -= abs_bounds.origin.y;
+              [self setNeedsDisplayInRect:nsrect];
+            }
+        }
+    }
+
+  for (id view in [self subviews])
+    [(GdkMacosCairoSubview *)view setCairoSurface:surface
+                                       withDamage:region];
+}
+
+-(void)setOpaque:(BOOL)opaque
+{
+  self->_isOpaque = opaque;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoSubview.h b/gdk/macos/GdkMacosCairoSubview.h
new file mode 100644
index 0000000000..9255347566
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.h
@@ -0,0 +1,35 @@
+/* GdkMacosCairoSubview.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <AppKit/AppKit.h>
+
+#define GDK_IS_MACOS_CAIRO_SUBVIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoSubview class]])
+
+@interface GdkMacosCairoSubview : NSView
+{
+  BOOL             _isOpaque;
+  cairo_surface_t *cairoSurface;
+}
+
+-(void)setOpaque:(BOOL)opaque;
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+            withDamage:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
new file mode 100644
index 0000000000..e6a31178b4
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -0,0 +1,170 @@
+/* GdkMacosCairoView.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <cairo-quartz.h>
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoView.h"
+#import "GdkMacosCairoSubview.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoView
+
+-(void)dealloc
+{
+  g_clear_pointer (&self->opaque, g_ptr_array_unref);
+  self->transparent = NULL;
+
+  [super dealloc];
+}
+
+-(BOOL)isOpaque
+{
+  if ([self window])
+    return [[self window] isOpaque];
+  return YES;
+}
+
+-(BOOL)isFlipped
+{
+  return YES;
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+            withDamage:(cairo_region_t *)cairoRegion
+{
+  for (id view in [self subviews])
+    [(GdkMacosCairoSubview *)view setCairoSurface:cairoSurface
+                                       withDamage:cairoRegion];
+}
+
+-(void)removeOpaqueChildren
+{
+  [[self->transparent subviews]
+    makeObjectsPerformSelector:@selector(removeFromSuperview)];
+
+  if (self->opaque->len)
+    g_ptr_array_remove_range (self->opaque, 0, self->opaque->len);
+}
+
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+  NSRect abs_bounds;
+  guint n_rects;
+
+  if (region == NULL)
+    return;
+
+  abs_bounds = [self convertRect:[self bounds] toView:nil];
+  n_rects = cairo_region_num_rectangles (region);
+
+  /* The common case (at least for opaque windows and CSD) is that we will
+   * have either one or two opaque rectangles. If we detect that the same
+   * number of them are available as the previous, we can just resize the
+   * previous ones to avoid adding/removing views at a fast rate while
+   * resizing.
+   */
+  if (n_rects == self->opaque->len)
+    {
+      for (guint i = 0; i < n_rects; i++)
+        {
+          GdkMacosCairoSubview *child;
+          cairo_rectangle_int_t rect;
+
+          child = g_ptr_array_index (self->opaque, i);
+          cairo_region_get_rectangle (region, i, &rect);
+
+          [child setFrame:NSMakeRect (rect.x - abs_bounds.origin.x,
+                                      rect.y - abs_bounds.origin.y,
+                                      rect.width,
+                                      rect.height)];
+        }
+
+      return;
+    }
+
+  [self removeOpaqueChildren];
+  for (guint i = 0; i < n_rects; i++)
+    {
+      GdkMacosCairoSubview *child;
+      cairo_rectangle_int_t rect;
+      NSRect nsrect;
+
+      cairo_region_get_rectangle (region, i, &rect);
+      nsrect = NSMakeRect (rect.x - abs_bounds.origin.x,
+                           rect.y - abs_bounds.origin.y,
+                           rect.width,
+                           rect.height);
+
+      child = [[GdkMacosCairoSubview alloc] initWithFrame:nsrect];
+      [child setOpaque:YES];
+      [child setWantsLayer:YES];
+      [self->transparent addSubview:child];
+      g_ptr_array_add (self->opaque, child);
+    }
+}
+
+-(NSView *)initWithFrame:(NSRect)frame
+{
+  if ((self = [super initWithFrame:frame]))
+    {
+      /* An array to track all the opaque children placed into
+       * the child self->transparent. This allows us to reuse them
+       * when we receive a new opaque area instead of discarding
+       * them on each draw.
+       */
+      self->opaque = g_ptr_array_new ();
+
+      /* Setup our primary subview which will render all content that is not
+       * within an opaque region (such as shadows for CSD windows). For opaque
+       * windows, this will all be obscurred by other views, so it doesn't
+       * matter much to have it here.
+       */
+      self->transparent = [[GdkMacosCairoSubview alloc] initWithFrame:frame];
+      [self addSubview:self->transparent];
+
+    }
+
+  return self;
+}
+
+-(void)setFrame:(NSRect)rect
+{
+  [super setFrame:rect];
+  [self->transparent setFrame:NSMakeRect (0, 0, rect.size.width, rect.size.height)];
+}
+
+-(BOOL)acceptsFirstMouse
+{
+  return YES;
+}
+
+-(BOOL)mouseDownCanMoveWindow
+{
+  return NO;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosCairoView.h
new file mode 100644
index 0000000000..1c28d83b39
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -0,0 +1,37 @@
+/* GdkMacosCairoView.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <cairo.h>
+
+#import "GdkMacosBaseView.h"
+
+#define GDK_IS_MACOS_CAIRO_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoView class]])
+
+@interface GdkMacosCairoView : GdkMacosBaseView
+{
+  NSView *transparent;
+  GPtrArray *opaque;
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+            withDamage:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosGLLayer.c b/gdk/macos/GdkMacosGLLayer.c
new file mode 100644
index 0000000000..a1ab55a4dc
--- /dev/null
+++ b/gdk/macos/GdkMacosGLLayer.c
@@ -0,0 +1,157 @@
+/* GdkMacosGLLayer.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/* Based on Chromium image_transport_surface_calayer_mac.mm
+ * See the BSD-style license above.
+ */
+
+#include "config.h"
+
+#include <OpenGL/gl.h>
+
+#import "GdkMacosGLLayer.h"
+
+@implementation GdkMacosGLLayer
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+-(id)initWithContext:(NSOpenGLContext *)shared
+{
+  [super init];
+  _shared = [shared retain];
+  return self;
+}
+
+-(void)dealloc
+{
+  [_shared release];
+  _shared = nil;
+
+  [super dealloc];
+}
+
+-(void)setContentsRect:(NSRect)bounds
+{
+  _pixelSize = bounds.size;
+  [super setContentsRect:bounds];
+}
+
+-(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
+{
+  return CGLRetainPixelFormat ([[_shared pixelFormat] CGLPixelFormatObj]);
+}
+
+-(CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
+{
+  CGLContextObj context = NULL;
+  CGLCreateContext (pixelFormat, [_shared CGLContextObj], &context);
+  return context;
+}
+
+-(BOOL)canDrawInCGLContext:(CGLContextObj)glContext
+               pixelFormat:(CGLPixelFormatObj)pixelFormat
+              forLayerTime:(CFTimeInterval)timeInterval
+               displayTime:(const CVTimeStamp*)timeStamp
+{
+  return YES;
+}
+
+-(void)drawInCGLContext:(CGLContextObj)glContext
+            pixelFormat:(CGLPixelFormatObj)pixelFormat
+           forLayerTime:(CFTimeInterval)timeInterval
+            displayTime:(const CVTimeStamp*)timeStamp
+{
+  if (_texture == 0)
+    return;
+
+  glClearColor (1, 0, 1, 1);
+  glClear (GL_COLOR_BUFFER_BIT);
+  GLint viewport[4] = {0, 0, 0, 0};
+  glGetIntegerv (GL_VIEWPORT, viewport);
+  NSSize viewportSize = NSMakeSize (viewport[2], viewport[3]);
+
+  /* Set the coordinate system to be one-to-one with pixels. */
+  glMatrixMode (GL_PROJECTION);
+  glLoadIdentity ();
+  glOrtho (0, viewportSize.width, 0, viewportSize.height, -1, 1);
+  glMatrixMode (GL_MODELVIEW);
+  glLoadIdentity ();
+
+  /* Draw a fullscreen quad. */
+  glColor4f (1, 1, 1, 1);
+  glEnable (GL_TEXTURE_RECTANGLE_ARB);
+  glBindTexture (GL_TEXTURE_RECTANGLE_ARB, _texture);
+  glBegin (GL_QUADS);
+  {
+    glTexCoord2f (0, 0);
+    glVertex2f (0, 0);
+    glTexCoord2f (0, _pixelSize.height);
+    glVertex2f (0, _pixelSize.height);
+    glTexCoord2f (_pixelSize.width, _pixelSize.height);
+    glVertex2f (_pixelSize.width, _pixelSize.height);
+    glTexCoord2f (_pixelSize.width, 0);
+    glVertex2f (_pixelSize.width, 0);
+  }
+  glEnd ();
+  glBindTexture (0, _texture);
+  glDisable (GL_TEXTURE_RECTANGLE_ARB);
+  [super drawInCGLContext:glContext
+              pixelFormat:pixelFormat
+             forLayerTime:timeInterval
+              displayTime:timeStamp];
+}
+
+-(void)setTexture:(GLuint)texture
+{
+  _texture = texture;
+  [self setNeedsDisplay];
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+@end
diff --git a/gdk/macos/GdkMacosGLLayer.h b/gdk/macos/GdkMacosGLLayer.h
new file mode 100644
index 0000000000..dd90c1ca08
--- /dev/null
+++ b/gdk/macos/GdkMacosGLLayer.h
@@ -0,0 +1,40 @@
+/* GdkMacosGLLayer.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <AppKit/AppKit.h>
+#include <glib.h>
+
+#define GDK_IS_MACOS_GL_LAYER(obj) ((obj) && [obj isKindOfClass:[GdkMacosGLLayer class]])
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+@interface GdkMacosGLLayer : CAOpenGLLayer
+{
+  NSOpenGLContext *_shared;
+  GLuint _texture;
+  NSSize _pixelSize;
+}
+
+-(id)initWithContext:(NSOpenGLContext *)shared;
+-(void)setTexture:(GLuint)texture;
+
+@end
+
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
new file mode 100644
index 0000000000..6ba8780bc8
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.c
@@ -0,0 +1,732 @@
+/* GdkMacosWindow.m
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdk.h>
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosCairoView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+
+#include "gdkmonitorprivate.h"
+#include "gdksurfaceprivate.h"
+
+@implementation GdkMacosWindow
+
+-(BOOL)windowShouldClose:(id)sender
+{
+  GdkDisplay *display;
+  GdkEvent *event;
+  GList *node;
+
+  display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+  event = gdk_delete_event_new (GDK_SURFACE (gdk_surface));
+  node = _gdk_event_queue_append (display, event);
+  _gdk_windowing_got_event (display, node, event,
+                            _gdk_display_get_next_serial (display));
+
+  return NO;
+}
+
+-(void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+  if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+    _gdk_macos_toplevel_surface_detach_from_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+  else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+    _gdk_macos_popup_surface_detach_from_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+}
+
+-(void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MINIMIZED);
+}
+
+-(void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+  if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+    _gdk_macos_toplevel_surface_attach_to_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+  else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+    _gdk_macos_popup_surface_attach_to_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MINIMIZED, 0);
+}
+
+-(void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_FOCUSED);
+  _gdk_macos_display_surface_became_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignKey:(NSNotification *)aNotification
+{
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_FOCUSED, 0);
+  _gdk_macos_display_surface_resigned_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+  if (![self isVisible])
+    {
+      /* Note: This is a hack needed because for unknown reasons, hidden
+       * windows get shown when clicking the dock icon when the application
+       * is not already active.
+       */
+      [self orderOut:nil];
+      return;
+    }
+
+  _gdk_macos_display_surface_became_main ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignMain:(NSNotification *)aNotification
+{
+  _gdk_macos_display_surface_resigned_main ([self gdkDisplay], gdk_surface);
+}
+
+/* Used in combination with NSLeftMouseUp in sendEvent to keep track
+ * of when the window is being moved with the mouse.
+ */
+-(void)windowWillMove:(NSNotification *)aNotification
+{
+  inMove = YES;
+}
+
+-(void)sendEvent:(NSEvent *)event
+{
+  NSEventType event_type = [event type];
+
+  switch ((int)event_type)
+    {
+    case NSEventTypeLeftMouseUp: {
+      GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+      double time = ((double)[event timestamp]) * 1000.0;
+
+      _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time);
+
+      inManualMove = NO;
+      inManualResize = NO;
+      inMove = NO;
+
+      /* We need to deliver the event to the proper drag gestures or we
+       * will leave the window in inconsistent state that requires clicking
+       * in the window to cancel the gesture.
+       *
+       * TODO: Can we improve grab breaking to fix this?
+       */
+      _gdk_macos_display_send_button_event ([self gdkDisplay], event);
+
+      break;
+    }
+
+    case NSEventTypeLeftMouseDragged:
+      if ([self trackManualMove] || [self trackManualResize])
+        return;
+      break;
+
+    default:
+      break;
+    }
+
+  [super sendEvent:event];
+}
+
+-(BOOL)isInMove
+{
+  return inMove;
+}
+
+-(void)checkSendEnterNotify
+{
+  /* When a new window has been created, and the mouse is in the window
+   * area, we will not receive an NSEventTypeMouseEntered event.
+   * Therefore, we synthesize an enter notify event manually.
+   */
+  if (!initialPositionKnown)
+    {
+      initialPositionKnown = YES;
+
+      if (NSPointInRect ([NSEvent mouseLocation], [self frame]))
+        {
+          GdkMacosBaseView *view = (GdkMacosBaseView *)[self contentView];
+          NSEvent *event;
+
+          event = [NSEvent enterExitEventWithType: NSEventTypeMouseEntered
+                                         location: [self mouseLocationOutsideOfEventStream]
+                                    modifierFlags: 0
+                                        timestamp: [[NSApp currentEvent] timestamp]
+                                     windowNumber: [self windowNumber]
+                                          context: NULL
+                                      eventNumber: 0
+                                   trackingNumber: (NSInteger)[view trackingArea]
+                                         userData: nil];
+
+          [NSApp postEvent:event atStart:NO];
+        }
+    }
+}
+
+-(void)windowDidUnmaximize
+{
+  NSWindowStyleMask style_mask = [self styleMask];
+
+  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+  /* If we are using CSD, then we transitioned to an opaque
+   * window while we were maximized. Now we need to drop that
+   * as we are leaving maximized state.
+   */
+  if ((style_mask & NSWindowStyleMaskTitled) == 0 && [self isOpaque])
+    [self setOpaque:NO];
+}
+
+-(void)windowDidMove:(NSNotification *)aNotification
+{
+  GdkSurface *surface = GDK_SURFACE (gdk_surface);
+  gboolean maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+
+  /* In case the window is changed when maximized remove the maximized state */
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+    [self windowDidUnmaximize];
+
+  _gdk_macos_surface_update_position (gdk_surface);
+  _gdk_macos_surface_reposition_children (gdk_surface);
+
+  [self checkSendEnterNotify];
+}
+
+-(void)windowDidResize:(NSNotification *)aNotification
+{
+  NSRect content_rect;
+  GdkSurface *surface;
+  GdkDisplay *display;
+  GdkEvent *event;
+  gboolean maximized;
+  GList *node;
+
+  surface = GDK_SURFACE (gdk_surface);
+  display = gdk_surface_get_display (surface);
+
+  content_rect = [self contentRectForFrameRect:[self frame]];
+  maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+
+  /* see same in windowDidMove */
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+    [self windowDidUnmaximize];
+
+  surface->width = content_rect.size.width;
+  surface->height = content_rect.size.height;
+
+  /* Certain resize operations (e.g. going fullscreen), also move the
+   * origin of the window.
+   */
+  _gdk_macos_surface_update_position (GDK_MACOS_SURFACE (surface));
+
+  [[self contentView] setFrame:NSMakeRect (0, 0, surface->width, surface->height)];
+
+  _gdk_surface_update_size (surface);
+
+  /* Synthesize a configure event */
+  event = gdk_configure_event_new (surface,
+                                   content_rect.size.width,
+                                   content_rect.size.height);
+  node = _gdk_event_queue_append (display, event);
+  _gdk_windowing_got_event (display, node, event,
+                            _gdk_display_get_next_serial (display));
+
+  _gdk_macos_surface_reposition_children (gdk_surface);
+
+  [self checkSendEnterNotify];
+}
+
+-(id)initWithContentRect:(NSRect)contentRect
+               styleMask:(NSWindowStyleMask)styleMask
+                 backing:(NSBackingStoreType)backingType
+                   defer:(BOOL)flag
+                  screen:(NSScreen *)screen
+{
+  GdkMacosCairoView *view;
+
+  self = [super initWithContentRect:contentRect
+                               styleMask:styleMask
+                                 backing:backingType
+                                   defer:flag
+                             screen:screen];
+
+  [self setAcceptsMouseMovedEvents:YES];
+  [self setDelegate:(id<NSWindowDelegate>)self];
+  [self setReleasedWhenClosed:YES];
+
+  view = [[GdkMacosCairoView alloc] initWithFrame:contentRect];
+  [self setContentView:view];
+  [view release];
+
+  return self;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+  return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+  return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(void)showAndMakeKey:(BOOL)makeKey
+{
+  inShowOrHide = YES;
+
+  if (makeKey && [self canBecomeKeyWindow])
+    [self makeKeyAndOrderFront:nil];
+  else
+    [self orderFront:nil];
+
+  inShowOrHide = NO;
+
+  [self checkSendEnterNotify];
+}
+
+-(void)hide
+{
+  inShowOrHide = YES;
+  [self orderOut:nil];
+  inShowOrHide = NO;
+
+  initialPositionKnown = NO;
+}
+
+-(BOOL)trackManualMove
+{
+  NSRect windowFrame;
+  NSPoint currentLocation;
+  GdkMonitor *monitor;
+  GdkRectangle geometry;
+  GdkRectangle workarea;
+  int shadow_top = 0;
+  int shadow_left = 0;
+  int shadow_right = 0;
+  int shadow_bottom = 0;
+  GdkRectangle window_gdk;
+  GdkPoint pointer_position;
+  GdkPoint new_origin;
+
+  if (!inManualMove)
+    return NO;
+
+  /* Get our shadow so we can adjust the window position sans-shadow */
+  _gdk_macos_surface_get_shadow (gdk_surface,
+                                 &shadow_top,
+                                 &shadow_right,
+                                 &shadow_bottom,
+                                 &shadow_left);
+
+  windowFrame = [self frame];
+  currentLocation = [NSEvent mouseLocation];
+
+  /* Update the snapping geometry to match the current monitor */
+  monitor = _gdk_macos_display_get_monitor_at_display_coords ([self gdkDisplay],
+                                                              currentLocation.x,
+                                                              currentLocation.y);
+  gdk_monitor_get_geometry (monitor, &geometry);
+  gdk_monitor_get_workarea (monitor, &workarea);
+  _edge_snapping_set_monitor (&self->snapping, &geometry, &workarea);
+
+  /* Convert origins to GDK coordinates */
+  _gdk_macos_display_from_display_coords ([self gdkDisplay],
+                                          currentLocation.x,
+                                          currentLocation.y,
+                                          &pointer_position.x,
+                                          &pointer_position.y);
+  _gdk_macos_display_from_display_coords ([self gdkDisplay],
+                                          windowFrame.origin.x,
+                                          windowFrame.origin.y + windowFrame.size.height,
+                                          &window_gdk.x,
+                                          &window_gdk.y);
+  window_gdk.width = windowFrame.size.width;
+  window_gdk.height = windowFrame.size.height;
+
+  /* Subtract our shadowin from the window */
+  window_gdk.x += shadow_left;
+  window_gdk.y += shadow_top;
+  window_gdk.width = window_gdk.width - shadow_left - shadow_right;
+  window_gdk.height = window_gdk.height - shadow_top - shadow_bottom;
+
+  /* Now place things on the monitor */
+  _edge_snapping_motion (&self->snapping, &pointer_position, &window_gdk);
+
+  /* And add our shadow back to the frame */
+  window_gdk.x -= shadow_left;
+  window_gdk.y -= shadow_top;
+  window_gdk.width += shadow_left + shadow_right;
+  window_gdk.height += shadow_top + shadow_bottom;
+
+  /* Convert to quartz coordiantes */
+  _gdk_macos_display_to_display_coords ([self gdkDisplay],
+                                        window_gdk.x,
+                                        window_gdk.y + window_gdk.height,
+                                        &new_origin.x, &new_origin.y);
+  windowFrame.origin.x = new_origin.x;
+  windowFrame.origin.y = new_origin.y;
+
+  /* And now apply the frame to the window */
+  [self setFrameOrigin:NSMakePoint(new_origin.x, new_origin.y)];
+
+  return YES;
+}
+
+/* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler
+ * above will see the event or if it will be subjected to standard processing
+ * by GDK.
+*/
+-(BOOL)isInManualResizeOrMove
+{
+  return inManualResize || inManualMove;
+}
+
+-(void)beginManualMove
+{
+  NSPoint initialMoveLocation;
+  GdkPoint point;
+  GdkMonitor *monitor;
+  GdkRectangle geometry;
+  GdkRectangle area;
+  GdkRectangle workarea;
+
+  if (inMove || inManualMove || inManualResize)
+    return;
+
+  inManualMove = YES;
+
+  monitor = _gdk_macos_surface_get_best_monitor ([self gdkSurface]);
+  gdk_monitor_get_geometry (monitor, &geometry);
+  gdk_monitor_get_workarea (monitor, &workarea);
+
+  initialMoveLocation = [NSEvent mouseLocation];
+
+  _gdk_macos_display_from_display_coords ([self gdkDisplay],
+                                          initialMoveLocation.x,
+                                          initialMoveLocation.y,
+                                          &point.x,
+                                          &point.y);
+
+  area.x = gdk_surface->root_x;
+  area.y = gdk_surface->root_y;
+  area.width = GDK_SURFACE (gdk_surface)->width;
+  area.height = GDK_SURFACE (gdk_surface)->height;
+
+  _edge_snapping_init (&self->snapping,
+                       &geometry,
+                       &workarea,
+                       &point,
+                       &area);
+}
+
+-(BOOL)trackManualResize
+{
+  NSPoint mouse_location;
+  NSRect new_frame;
+  float mdx, mdy, dw, dh, dx, dy;
+  NSSize min_size;
+
+  if (!inManualResize || inTrackManualResize)
+    return NO;
+
+  inTrackManualResize = YES;
+
+  mouse_location = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+  mdx = initialResizeLocation.x - mouse_location.x;
+  mdy = initialResizeLocation.y - mouse_location.y;
+
+  /* Set how a mouse location delta translates to changes in width,
+   * height and position.
+   */
+  dw = dh = dx = dy = 0.0;
+  if (resizeEdge == GDK_SURFACE_EDGE_EAST ||
+      resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST ||
+      resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+    {
+      dw = -1.0;
+    }
+  if (resizeEdge == GDK_SURFACE_EDGE_NORTH ||
+      resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+      resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST)
+    {
+      dh = -1.0;
+    }
+  if (resizeEdge == GDK_SURFACE_EDGE_SOUTH ||
+      resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST ||
+      resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+    {
+      dh = 1.0;
+      dy = -1.0;
+    }
+  if (resizeEdge == GDK_SURFACE_EDGE_WEST ||
+      resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+      resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST)
+    {
+      dw = 1.0;
+      dx = -1.0;
+    }
+
+  /* Apply changes to the frame captured when we started resizing */
+  new_frame = initialResizeFrame;
+  new_frame.origin.x += mdx * dx;
+  new_frame.origin.y += mdy * dy;
+  new_frame.size.width += mdx * dw;
+  new_frame.size.height += mdy * dh;
+
+  /* In case the resulting window would be too small reduce the
+   * change to both size and position.
+   */
+  min_size = [self contentMinSize];
+
+  if (new_frame.size.width < min_size.width)
+    {
+      if (dx)
+        new_frame.origin.x -= min_size.width - new_frame.size.width;
+      new_frame.size.width = min_size.width;
+    }
+
+  if (new_frame.size.height < min_size.height)
+    {
+      if (dy)
+        new_frame.origin.y -= min_size.height - new_frame.size.height;
+      new_frame.size.height = min_size.height;
+    }
+
+  /* We could also apply aspect ratio:
+     new_frame.size.height = new_frame.size.width / [self aspectRatio].width * [self aspectRatio].height;
+  */
+
+  [self setFrame:new_frame display:YES];
+
+  /* Let the resizing be handled by GTK+. */
+  if (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+
+  inTrackManualResize = NO;
+
+  return YES;
+}
+
+-(void)beginManualResize:(GdkSurfaceEdge)edge
+{
+  if (inMove || inManualMove || inManualResize)
+    return;
+
+  inManualResize = YES;
+  resizeEdge = edge;
+
+  initialResizeFrame = [self frame];
+  initialResizeLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+  return NSDragOperationNone;
+}
+
+-(void)draggingEnded:(id <NSDraggingInfo>)sender
+{
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+  return NSDragOperationNone;
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+  return YES;
+}
+
+-(BOOL)wantsPeriodicDraggingUpdates
+{
+  return NO;
+}
+
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+}
+
+-(void)setStyleMask:(NSWindowStyleMask)styleMask
+{
+  gboolean was_fullscreen;
+  gboolean is_fullscreen;
+  gboolean was_opaque;
+  gboolean is_opaque;
+
+  was_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+  was_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+  [super setStyleMask:styleMask];
+
+  is_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+  is_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+  if (was_fullscreen != is_fullscreen)
+    _gdk_macos_surface_update_fullscreen_state (gdk_surface);
+
+  if (was_opaque != is_opaque)
+    {
+      [self setOpaque:is_opaque];
+
+      if (!is_opaque)
+        [self setBackgroundColor:[NSColor clearColor]];
+    }
+}
+
+-(NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
+{
+  GdkMacosSurface *surface = gdk_surface;
+  NSRect rect;
+  gint shadow_top;
+
+  /* Allow the window to move up "shadow_top" more than normally allowed
+   * by the default impl. This makes it possible to move windows with
+   * client side shadow right up to the screen's menu bar. */
+  _gdk_macos_surface_get_shadow (surface, &shadow_top, NULL, NULL, NULL);
+  rect = [super constrainFrameRect:frameRect toScreen:screen];
+  if (frameRect.origin.y > rect.origin.y)
+    rect.origin.y = MIN (frameRect.origin.y, rect.origin.y + shadow_top);
+
+  return rect;
+}
+
+-(NSRect)windowWillUseStandardFrame:(NSWindow *)nsWindow
+                       defaultFrame:(NSRect)newFrame
+{
+  NSRect screenFrame = [[self screen] visibleFrame];
+  GdkMacosSurface *surface = gdk_surface;
+  gboolean maximized = GDK_SURFACE (surface)->state & GDK_SURFACE_STATE_MAXIMIZED;
+
+  if (!maximized)
+    return screenFrame;
+  else
+    return lastUnmaximizedFrame;
+}
+
+-(BOOL)windowShouldZoom:(NSWindow *)nsWindow
+                toFrame:(NSRect)newFrame
+{
+  GdkMacosSurface *surface = gdk_surface;
+  GdkSurfaceState state = GDK_SURFACE (surface)->state;
+
+  if (state & GDK_SURFACE_STATE_MAXIMIZED)
+    {
+      lastMaximizedFrame = newFrame;
+      [self windowDidUnmaximize];
+    }
+  else
+    {
+      lastUnmaximizedFrame = [nsWindow frame];
+      gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MAXIMIZED);
+    }
+
+  inMaximizeTransition = YES;
+
+  return YES;
+}
+
+-(void)windowDidEndLiveResize:(NSNotification *)aNotification
+{
+  gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_SURFACE_STATE_MAXIMIZED;
+
+  inMaximizeTransition = NO;
+
+  /* Even if this is CSD, we want to be opaque while maximized
+   * to speed up compositing by allowing the display server to
+   * avoid costly blends.
+   */
+  if (maximized)
+    [self setOpaque:YES];
+}
+
+-(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
+{
+  return [[window screen] frame].size;
+}
+
+-(void)windowWillEnterFullScreen:(NSNotification *)aNotification
+{
+  lastUnfullscreenFrame = [self frame];
+}
+
+-(void)windowWillExitFullScreen:(NSNotification *)aNotification
+{
+  [self setFrame:lastUnfullscreenFrame display:YES];
+}
+
+-(void)windowDidExitFullScreen:(NSNotification *)aNotification
+{
+}
+
+-(void)windowDidChangeScreen:(NSNotification *)aNotification
+{
+  _gdk_macos_surface_monitor_changed (gdk_surface);
+}
+
+-(void)setGdkSurface:(GdkMacosSurface *)surface
+{
+  self->gdk_surface = surface;
+}
+
+-(void)setDecorated:(BOOL)decorated
+{
+  NSWindowStyleMask style_mask = [self styleMask];
+
+  [self setHasShadow:decorated];
+
+  if (decorated)
+    style_mask |= NSWindowStyleMaskTitled;
+  else
+    style_mask &= ~NSWindowStyleMaskTitled;
+
+  [self setStyleMask:style_mask];
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+  return self->gdk_surface;
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+  return GDK_MACOS_DISPLAY (GDK_SURFACE (self->gdk_surface)->display);
+}
+
+-(BOOL)movableByWindowBackground
+{
+  return NO;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h
new file mode 100644
index 0000000000..61f546a78b
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.h
@@ -0,0 +1,70 @@
+/* GdkMacosWindow.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+#include "edgesnapping.h"
+
+#define GDK_IS_MACOS_WINDOW(obj) ([obj isKindOfClass:[GdkMacosWindow class]])
+
+@interface GdkMacosWindow : NSWindow {
+  GdkMacosSurface *gdk_surface;
+
+  BOOL             inMove;
+  BOOL             inShowOrHide;
+  BOOL             initialPositionKnown;
+
+  /* Manually triggered move/resize (not by the window manager) */
+  BOOL             inManualMove;
+  BOOL             inManualResize;
+  BOOL             inTrackManualResize;
+  NSPoint          initialResizeLocation;
+  NSRect           initialResizeFrame;
+  GdkSurfaceEdge   resizeEdge;
+
+  EdgeSnapping     snapping;
+
+  NSRect           lastUnmaximizedFrame;
+  NSRect           lastMaximizedFrame;
+  NSRect           lastUnfullscreenFrame;
+  BOOL             inMaximizeTransition;
+}
+
+-(void)beginManualMove;
+-(void)beginManualResize:(GdkSurfaceEdge)edge;
+-(void)hide;
+-(BOOL)isInManualResizeOrMove;
+-(BOOL)isInMove;
+-(GdkMacosDisplay *)gdkDisplay;
+-(GdkMacosSurface *)gdkSurface;
+-(void)setGdkSurface:(GdkMacosSurface *)surface;
+-(void)setStyleMask:(NSWindowStyleMask)styleMask;
+-(void)showAndMakeKey:(BOOL)makeKey;
+-(BOOL)trackManualMove;
+-(BOOL)trackManualResize;
+-(void)setDecorated:(BOOL)decorated;
+
+@end
diff --git a/gdk/macos/edgesnapping.c b/gdk/macos/edgesnapping.c
new file mode 100644
index 0000000000..bf9347265a
--- /dev/null
+++ b/gdk/macos/edgesnapping.c
@@ -0,0 +1,229 @@
+/* edgesnapping.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkrectangle.h"
+#include "edgesnapping.h"
+
+#define LEAVE_THRESHOLD 3.0
+#define ENTER_THRESHOLD 2.0
+
+#define X1(r) ((r)->x)
+#define X2(r) ((r)->x + (r)->width)
+#define Y1(r) ((r)->y)
+#define Y2(r) ((r)->y + (r)->height)
+
+void
+_edge_snapping_init (EdgeSnapping       *self,
+                     const GdkRectangle *geometry,
+                     const GdkRectangle *workarea,
+                     const GdkPoint     *pointer_position,
+                     const GdkRectangle *window)
+{
+  g_assert (self != NULL);
+  g_assert (geometry != NULL);
+  g_assert (workarea != NULL);
+  g_assert (pointer_position != NULL);
+
+  self->geometry = *geometry;
+  self->workarea = *workarea;
+  self->last_pointer_position = *pointer_position;
+  self->pointer_offset_in_window.x = pointer_position->x - window->x;
+  self->pointer_offset_in_window.y = pointer_position->y - window->y;
+}
+
+static void
+edge_snapping_constrain_left (EdgeSnapping       *self,
+                              int                 change,
+                              const GdkRectangle *geometry,
+                              GdkRectangle       *window)
+{
+  if (change < 0)
+    {
+      if (X1 (window) < X1 (geometry) &&
+          X1 (window) > X1 (geometry) - LEAVE_THRESHOLD &&
+          ABS (change) < LEAVE_THRESHOLD)
+        window->x = geometry->x;
+    }
+
+  /* We don't constrain when returning from left edge */
+}
+
+static void
+edge_snapping_constrain_right (EdgeSnapping       *self,
+                               int                 change,
+                               const GdkRectangle *geometry,
+                               GdkRectangle       *window)
+{
+  if (change > 0)
+    {
+      if (X2 (window) > X2 (geometry) &&
+          X2 (window) < X2 (geometry) + LEAVE_THRESHOLD &&
+          ABS (change) < LEAVE_THRESHOLD)
+        window->x = X2 (geometry) - window->width;
+    }
+
+  /* We don't constrain when returning from right edge */
+}
+
+static void
+edge_snapping_constrain_top (EdgeSnapping       *self,
+                             int                 change,
+                             const GdkRectangle *geometry,
+                             GdkRectangle       *window)
+{
+  if (change < 0)
+    {
+      if (Y1 (window) < Y1 (geometry))
+        window->y = geometry->y;
+    }
+
+  /* We don't constrain when returning from top edge */
+}
+
+static void
+edge_snapping_constrain_bottom (EdgeSnapping       *self,
+                                int                 change,
+                                const GdkRectangle *geometry,
+                                GdkRectangle       *window)
+{
+  if (change > 0)
+    {
+      if (Y2 (window) > Y2 (geometry) &&
+          Y2 (window) < Y2 (geometry) + LEAVE_THRESHOLD &&
+          ABS (change) < LEAVE_THRESHOLD)
+        window->y = Y2 (geometry) - window->height;
+    }
+  else if (change < 0)
+    {
+      if (Y2 (window) < Y2 (geometry) &&
+          Y2 (window) > Y2 (geometry) - ENTER_THRESHOLD &&
+          ABS (change) < ENTER_THRESHOLD)
+        window->y = Y2 (geometry) - window->height;
+    }
+
+}
+
+static void
+edge_snapping_constrain_horizontal (EdgeSnapping       *self,
+                                    int                 change,
+                                    const GdkRectangle *geometry,
+                                    GdkRectangle       *window)
+{
+  g_assert (self != NULL);
+  g_assert (geometry != NULL);
+  g_assert (window != NULL);
+  g_assert (change != 0);
+
+  if (ABS (X1 (geometry) - X1 (window)) < ABS (X2 (geometry)) - ABS (X2 (window)))
+    edge_snapping_constrain_left (self, change, geometry, window);
+  else
+    edge_snapping_constrain_right (self, change, geometry, window);
+}
+
+static void
+edge_snapping_constrain_vertical (EdgeSnapping       *self,
+                                  int                 change,
+                                  const GdkRectangle *geometry,
+                                  GdkRectangle       *window,
+                                  gboolean            bottom_only)
+{
+  g_assert (self != NULL);
+  g_assert (geometry != NULL);
+  g_assert (window != NULL);
+  g_assert (change != 0);
+
+  if (!bottom_only &&
+      ABS (Y1 (geometry) - Y1 (window)) < ABS (Y2 (geometry)) - ABS (Y2 (window)))
+    edge_snapping_constrain_top (self, change, geometry, window);
+  else
+    edge_snapping_constrain_bottom (self, change, geometry, window);
+}
+
+void
+_edge_snapping_motion (EdgeSnapping   *self,
+                       const GdkPoint *pointer_position,
+                       GdkRectangle   *window)
+{
+  GdkRectangle new_window;
+  GdkRectangle overlap;
+  GdkPoint change;
+
+  g_assert (self != NULL);
+  g_assert (pointer_position != NULL);
+
+  change.x = pointer_position->x - self->last_pointer_position.x;
+  change.y = pointer_position->y - self->last_pointer_position.y;
+
+  self->last_pointer_position = *pointer_position;
+
+  window->x += change.x;
+  window->y += change.y;
+
+  new_window = *window;
+
+  /* First constrain horizontal */
+  if (change.x)
+    {
+      edge_snapping_constrain_horizontal (self, change.x, &self->workarea, &new_window);
+      if (gdk_rectangle_equal (&new_window, window))
+        edge_snapping_constrain_horizontal (self, change.x, &self->geometry, &new_window);
+    }
+
+  /* Now constrain veritcally */
+  if (change.y)
+    {
+      edge_snapping_constrain_vertical (self, change.y, &self->workarea, &new_window, FALSE);
+      if (new_window.y == window->y)
+        edge_snapping_constrain_vertical (self, change.y, &self->geometry, &new_window, TRUE);
+    }
+
+  /* If the window is not placed in the monitor at all, then we need to
+   * just move the window onto the new screen using the original offset
+   * of the pointer within the window.
+   */
+  if (!gdk_rectangle_intersect (&self->geometry, &new_window, &overlap))
+    {
+      new_window.x = pointer_position->x - self->pointer_offset_in_window.x;
+      new_window.y = pointer_position->y - self->pointer_offset_in_window.y;
+    }
+
+  /* And finally make sure we aren't underneath the top bar of the
+   * particular monitor.
+   */
+  if (Y1 (&new_window) < Y1 (&self->workarea))
+    new_window.y = self->workarea.y;
+
+  *window = new_window;
+}
+
+void
+_edge_snapping_set_monitor (EdgeSnapping       *self,
+                            const GdkRectangle *geometry,
+                            const GdkRectangle *workarea)
+{
+  g_assert (self != NULL);
+  g_assert (geometry != NULL);
+  g_assert (workarea != NULL);
+
+  self->geometry = *geometry;
+  self->workarea = *workarea;
+}
diff --git a/gdk/macos/edgesnapping.h b/gdk/macos/edgesnapping.h
new file mode 100644
index 0000000000..8769ea7e47
--- /dev/null
+++ b/gdk/macos/edgesnapping.h
@@ -0,0 +1,50 @@
+/* edgesnapping.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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __EDGE_SNAPPING_H__
+#define __EDGE_SNAPPING_H__
+
+#include "gdktypes.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  GdkRectangle geometry;
+  GdkRectangle workarea;
+  GdkPoint     last_pointer_position;
+  GdkPoint     pointer_offset_in_window;
+} EdgeSnapping;
+
+void _edge_snapping_init        (EdgeSnapping       *self,
+                                 const GdkRectangle *geometry,
+                                 const GdkRectangle *workarea,
+                                 const GdkPoint     *pointer_position,
+                                 const GdkRectangle *window);
+void _edge_snapping_motion      (EdgeSnapping       *self,
+                                 const GdkPoint     *pointer_position,
+                                 GdkRectangle       *window);
+void _edge_snapping_set_monitor (EdgeSnapping       *self,
+                                 const GdkRectangle *geometry,
+                                 const GdkRectangle *workarea);
+
+G_END_DECLS
+
+#endif /* __EDGE_SNAPPING_H__ */
diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c
new file mode 100644
index 0000000000..ba8731de5a
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.c
@@ -0,0 +1,254 @@
+/* gdkdisplaylinksource.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *   Christian Hergert <christian hergert me>
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+#include <mach/mach_time.h>
+
+#include "gdkdisplaylinksource.h"
+
+#include "gdkmacoseventsource-private.h"
+
+static gint64 host_to_frame_clock_time (gint64 host_time);
+
+static gboolean
+gdk_display_link_source_prepare (GSource *source,
+                                 gint    *timeout_)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+  gint64 now;
+
+  now = g_source_get_time (source);
+
+  if (now < impl->presentation_time)
+    *timeout_ = (impl->presentation_time - now) / 1000L;
+  else
+    *timeout_ = -1;
+
+  return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_check (GSource *source)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+  return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_dispatch (GSource     *source,
+                                  GSourceFunc  callback,
+                                  gpointer     user_data)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+  gboolean ret = G_SOURCE_CONTINUE;
+
+  impl->needs_dispatch = FALSE;
+
+  if (callback != NULL)
+    ret = callback (user_data);
+
+  return ret;
+}
+
+static void
+gdk_display_link_source_finalize (GSource *source)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+
+  CVDisplayLinkStop (impl->display_link);
+  CVDisplayLinkRelease (impl->display_link);
+}
+
+static GSourceFuncs gdk_display_link_source_funcs = {
+  gdk_display_link_source_prepare,
+  gdk_display_link_source_check,
+  gdk_display_link_source_dispatch,
+  gdk_display_link_source_finalize
+};
+
+void
+gdk_display_link_source_pause (GdkDisplayLinkSource *source)
+{
+  CVDisplayLinkStop (source->display_link);
+}
+
+void
+gdk_display_link_source_unpause (GdkDisplayLinkSource *source)
+{
+  CVDisplayLinkStart (source->display_link);
+}
+
+static CVReturn
+gdk_display_link_source_frame_cb (CVDisplayLinkRef   display_link,
+                                  const CVTimeStamp *inNow,
+                                  const CVTimeStamp *inOutputTime,
+                                  CVOptionFlags      flagsIn,
+                                  CVOptionFlags     *flagsOut,
+                                  void              *user_data)
+{
+  GdkDisplayLinkSource *impl = user_data;
+  gint64 presentation_time;
+  gboolean needs_wakeup;
+
+  needs_wakeup = !g_atomic_int_get (&impl->needs_dispatch);
+
+  presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
+
+  impl->presentation_time = presentation_time;
+  impl->needs_dispatch = TRUE;
+
+  if (needs_wakeup)
+    {
+      NSEvent *event;
+
+      /* Post a message so we'll break out of the message loop.
+       *
+       * We don't use g_main_context_wakeup() here because that
+       * would result in sending a message to the pipe(2) fd in
+       * the select thread which would then send this message as
+       * well. Lots of extra work.
+       */
+      event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+                                 location: NSZeroPoint
+                            modifierFlags: 0
+                                timestamp: 0
+                             windowNumber: 0
+                                  context: nil
+                                  subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+                                    data1: 0
+                                    data2: 0];
+
+      [NSApp postEvent:event atStart:YES];
+    }
+
+  return kCVReturnSuccess;
+}
+
+/**
+ * gdk_display_link_source_new:
+ *
+ * Creates a new #GSource that will activate the dispatch function upon
+ * notification from a CVDisplayLink that a new frame should be drawn.
+ *
+ * Effort is made to keep the transition from the high-priority
+ * CVDisplayLink thread into this GSource lightweight. However, this is
+ * somewhat non-ideal since the best case would be to do the drawing
+ * from the high-priority thread.
+ *
+ * Returns: (transfer full): A newly created #GSource.
+ */
+GSource *
+gdk_display_link_source_new (void)
+{
+  GdkDisplayLinkSource *impl;
+  GSource *source;
+  CVReturn ret;
+  double period;
+
+  source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl);
+  impl = (GdkDisplayLinkSource *)source;
+
+  /*
+   * Create our link based on currently connected displays.
+   * If there are multiple displays, this will be something that tries
+   * to work for all of them. In the future, we may want to explore multiple
+   * links based on the connected displays.
+   */
+  ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link);
+  if (ret != kCVReturnSuccess)
+    {
+      g_warning ("Failed to initialize CVDisplayLink!");
+      return source;
+    }
+
+  /*
+   * Determine our nominal period between frames.
+   */
+  period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link);
+  if (period == 0.0)
+    period = 1.0 / 60.0;
+  impl->refresh_interval = period * 1000000L;
+  impl->refresh_rate = 1.0 / period * 1000L;
+
+  /*
+   * Wire up our callback to be executed within the high-priority thread.
+   */
+  CVDisplayLinkSetOutputCallback (impl->display_link,
+                                  gdk_display_link_source_frame_cb,
+                                  source);
+
+  g_source_set_name (source, "[gdk] quartz frame clock");
+
+  return source;
+}
+
+static gint64
+host_to_frame_clock_time (gint64 host_time)
+{
+  static mach_timebase_info_data_t timebase_info;
+
+  /*
+   * NOTE:
+   *
+   * This code is taken from GLib to match g_get_monotonic_time().
+   */
+  if (G_UNLIKELY (timebase_info.denom == 0))
+    {
+      /* This is a fraction that we must use to scale
+       * mach_absolute_time() by in order to reach nanoseconds.
+       *
+       * We've only ever observed this to be 1/1, but maybe it could be
+       * 1000/1 if mach time is microseconds already, or 1/1000 if
+       * picoseconds.  Try to deal nicely with that.
+       */
+      mach_timebase_info (&timebase_info);
+
+      /* We actually want microseconds... */
+      if (timebase_info.numer % 1000 == 0)
+        timebase_info.numer /= 1000;
+      else
+        timebase_info.denom *= 1000;
+
+      /* We want to make the numer 1 to avoid having to multiply... */
+      if (timebase_info.denom % timebase_info.numer == 0)
+        {
+          timebase_info.denom /= timebase_info.numer;
+          timebase_info.numer = 1;
+        }
+      else
+        {
+          /* We could just multiply by timebase_info.numer below, but why
+           * bother for a case that may never actually exist...
+           *
+           * Plus -- performing the multiplication would risk integer
+           * overflow.  If we ever actually end up in this situation, we
+           * should more carefully evaluate the correct course of action.
+           */
+          mach_timebase_info (&timebase_info); /* Get a fresh copy for a better message */
+          g_error ("Got weird mach timebase info of %d/%d.  Please file a bug against GLib.",
+                   timebase_info.numer, timebase_info.denom);
+        }
+    }
+
+  return host_time / timebase_info.denom;
+}
diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h
new file mode 100644
index 0000000000..ed769b59f8
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.h
@@ -0,0 +1,49 @@
+/* gdkdisplaylinksource.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *   Christian Hergert <christian hergert me>
+ */
+
+#ifndef GDK_DISPLAY_LINK_SOURCE_H
+#define GDK_DISPLAY_LINK_SOURCE_H
+
+#include <glib.h>
+
+#include <QuartzCore/QuartzCore.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  GSource          source;
+
+  CVDisplayLinkRef display_link;
+  gint64           refresh_interval;
+  guint            refresh_rate;
+
+  volatile gint64  presentation_time;
+  volatile guint   needs_dispatch;
+} GdkDisplayLinkSource;
+
+GSource *gdk_display_link_source_new     (void);
+void     gdk_display_link_source_pause   (GdkDisplayLinkSource *source);
+void     gdk_display_link_source_unpause (GdkDisplayLinkSource *source);
+
+G_END_DECLS
+
+#endif /* GDK_DISPLAY_LINK_SOURCE_H */
diff --git a/gdk/macos/gdkmacos.h b/gdk/macos/gdkmacos.h
new file mode 100644
index 0000000000..7d7d348752
--- /dev/null
+++ b/gdk/macos/gdkmacos.h
@@ -0,0 +1,32 @@
+/*
+ * 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.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_H__
+#define __GDK_MACOS_H__
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay.h"
+#include "gdkmacosglcontext.h"
+#include "gdkmacoskeymap.h"
+#include "gdkmacosmonitor.h"
+#include "gdkmacossurface.h"
+
+#endif /* __GDK_MACOS_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext-private.h b/gdk/macos/gdkmacoscairocontext-private.h
new file mode 100644
index 0000000000..1afede8591
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext-private.h
@@ -0,0 +1,38 @@
+/*
+ * 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.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_CAIRO_CONTEXT_PRIVATE_H__
+#define __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__
+
+#include "gdkcairocontextprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosCairoContext      GdkMacosCairoContext;
+typedef struct _GdkMacosCairoContextClass GdkMacosCairoContextClass;
+
+#define GDK_TYPE_MACOS_CAIRO_CONTEXT       (_gdk_macos_cairo_context_get_type())
+#define GDK_MACOS_CAIRO_CONTEXT(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GDK_TYPE_MACOS_CAIRO_CONTEXT, GdkMacosCairoContext))
+#define GDK_IS_MACOS_CAIRO_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GDK_TYPE_MACOS_CAIRO_CONTEXT))
+
+GType _gdk_macos_cairo_context_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c
new file mode 100644
index 0000000000..e70d2bf00f
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright © 2016 Benjamin Otte
+ * 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.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 "gdkconfig.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <cairo-quartz.h>
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosCairoContext
+{
+  GdkCairoContext  parent_instance;
+
+  cairo_surface_t *window_surface;
+};
+
+struct _GdkMacosCairoContextClass
+{
+  GdkCairoContextClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT)
+
+static cairo_surface_t *
+create_cairo_surface_for_surface (GdkSurface *surface)
+{
+  cairo_surface_t *cairo_surface;
+  int scale;
+  int width;
+  int height;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  scale = gdk_surface_get_scale_factor (surface);
+  width = scale * gdk_surface_get_width (surface);
+  height = scale * gdk_surface_get_height (surface);
+
+  cairo_surface = cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+  if (cairo_surface != NULL)
+    cairo_surface_set_device_scale (cairo_surface, scale, scale);
+
+  return cairo_surface;
+}
+
+static cairo_t *
+_gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)cairo_context;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+  return cairo_create (self->window_surface);
+}
+
+static void
+_gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
+                                      cairo_region_t *region)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+  GdkSurface *surface;
+  NSWindow *nswindow;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+  surface = gdk_draw_context_get_surface (draw_context);
+  nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+  if (self->window_surface == NULL)
+    {
+      self->window_surface = create_cairo_surface_for_surface (surface);
+    }
+  else
+    {
+      if (![nswindow isOpaque])
+        {
+          cairo_t *cr = cairo_create (self->window_surface);
+          gdk_cairo_region (cr, region);
+          cairo_set_source_rgba (cr, 0, 0, 0, 0);
+          cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+          cairo_fill (cr);
+          cairo_destroy (cr);
+        }
+    }
+}
+
+static void
+_gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context,
+                                    cairo_region_t *painted)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+  GdkSurface *surface;
+  NSView *nsview;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+  g_assert (self->window_surface != NULL);
+
+  surface = gdk_draw_context_get_surface (draw_context);
+  nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface));
+
+  if (GDK_IS_MACOS_CAIRO_VIEW (nsview))
+    [(GdkMacosCairoView *)nsview setCairoSurface:self->window_surface
+                                      withDamage:painted];
+}
+
+static void
+_gdk_macos_cairo_context_surface_resized (GdkDrawContext *draw_context)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+  g_clear_pointer (&self->window_surface, cairo_surface_destroy);
+}
+
+static void
+_gdk_macos_cairo_context_class_init (GdkMacosCairoContextClass *klass)
+{
+  GdkCairoContextClass *cairo_context_class = GDK_CAIRO_CONTEXT_CLASS (klass);
+  GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+  draw_context_class->begin_frame = _gdk_macos_cairo_context_begin_frame;
+  draw_context_class->end_frame = _gdk_macos_cairo_context_end_frame;
+  draw_context_class->surface_resized = _gdk_macos_cairo_context_surface_resized;
+
+  cairo_context_class->cairo_create = _gdk_macos_cairo_context_cairo_create;
+}
+
+static void
+_gdk_macos_cairo_context_init (GdkMacosCairoContext *self)
+{
+}
diff --git a/gdk/macos/gdkmacosclipboard-private.h b/gdk/macos/gdkmacosclipboard-private.h
new file mode 100644
index 0000000000..ab37d707a5
--- /dev/null
+++ b/gdk/macos/gdkmacosclipboard-private.h
@@ -0,0 +1,54 @@
+/*
+ * 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.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_CLIPBOARD_PRIVATE_H__
+#define __GDK_MACOS_CLIPBOARD_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkclipboardprivate.h"
+#include "gdkmacosdisplay-private.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_CLIPBOARD (_gdk_macos_clipboard_get_type())
+
+G_DECLARE_FINAL_TYPE (GdkMacosClipboard, _gdk_macos_clipboard, GDK, MACOS_CLIPBOARD, GdkClipboard)
+
+GdkClipboard     *_gdk_macos_clipboard_new                       (GdkMacosDisplay   *display);
+void              _gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self);
+NSPasteboardType  _gdk_macos_clipboard_to_ns_type                (const char        *mime_type,
+                                                                  NSPasteboardType  *alternate);
+const char       *_gdk_macos_clipboard_from_ns_type              (NSPasteboardType   ns_type);
+
+@interface GdkMacosClipboardDataProvider : NSObject <NSPasteboardItemDataProvider>
+{
+  GCancellable  *cancellable;
+  GdkClipboard  *clipboard;
+  char         **mimeTypes;
+}
+
+-(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
+-(NSArray<NSPasteboardType> *)types;
+
+@end
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CLIPBOARD_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosclipboard.c b/gdk/macos/gdkmacosclipboard.c
new file mode 100644
index 0000000000..7297045b76
--- /dev/null
+++ b/gdk/macos/gdkmacosclipboard.c
@@ -0,0 +1,576 @@
+/*
+ * 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.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 "gdkmacosclipboard-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosClipboard
+{
+  GdkClipboard  parent_instance;
+  NSPasteboard *pasteboard;
+  NSInteger     last_change_count;
+};
+
+typedef struct
+{
+  GMemoryOutputStream *stream;
+  NSPasteboardItem    *item;
+  NSPasteboardType     type;
+  GMainContext        *main_context;
+  guint                done : 1;
+} WriteRequest;
+
+G_DEFINE_TYPE (GdkMacosClipboard, _gdk_macos_clipboard, GDK_TYPE_CLIPBOARD)
+
+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:NSPasteboardTypeString] ||
+      [type isEqualToString:NSStringPboardType])
+    return g_intern_string ("text/plain;charset=utf-8");
+  else if ([type isEqualToString:NSPasteboardTypeURL] ||
+           [type isEqualToString:NSPasteboardTypeFileURL])
+    return g_intern_string ("text/uri-list");
+  else if ([type isEqualToString:NSPasteboardTypeColor])
+    return g_intern_string ("application/x-color");
+  else if ([type isEqualToString:NSPasteboardTypeTIFF])
+    return g_intern_string ("image/tiff");
+  else if ([type isEqualToString:NSPasteboardTypePNG])
+    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 NSPasteboardTypeString;
+    }
+  else if (g_strcmp0 (mime_type, "text/uri-list") == 0)
+    {
+      if (alternate)
+        *alternate = NSPasteboardTypeURL;
+      return NSPasteboardTypeFileURL;
+    }
+  else if (g_strcmp0 (mime_type, "application/x-color") == 0)
+    {
+      return NSPasteboardTypeColor;
+    }
+  else if (g_strcmp0 (mime_type, "image/tiff") == 0)
+    {
+      return NSPasteboardTypeTIFF;
+    }
+  else if (g_strcmp0 (mime_type, "image/png") == 0)
+    {
+      return NSPasteboardTypePNG;
+    }
+
+  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 (GdkMacosClipboard *self)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkContentFormatsBuilder *builder;
+  GdkContentFormats *formats;
+
+  g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+
+  builder = gdk_content_formats_builder_new ();
+  for (NSPasteboardType type in [self->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)
+{
+  GdkContentFormats *formats;
+  NSInteger change_count;
+
+  g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+
+  change_count = [self->pasteboard changeCount];
+
+  formats = load_offer_formats (self);
+  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_memdup (bytes, len), len, g_free);
+}
+
+static void
+_gdk_macos_clipboard_read_async (GdkClipboard        *clipboard,
+                                 GdkContentFormats   *formats,
+                                 int                  io_priority,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosClipboard *self = (GdkMacosClipboard *)clipboard;
+  GdkContentFormats *offer_formats = NULL;
+  const char *mime_type;
+  GInputStream *stream = NULL;
+  GTask *task = NULL;
+
+  g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+  g_assert (formats != NULL);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, _gdk_macos_clipboard_read_async);
+  g_task_set_priority (task, io_priority);
+
+  offer_formats = load_offer_formats (GDK_MACOS_CLIPBOARD (clipboard));
+  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 = [self->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 ([[self->pasteboard types] containsObject:NSPasteboardTypeFileURL])
+        {
+          GString *str = g_string_new (NULL);
+          NSArray *files = [self->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:self->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_memdup (&color, sizeof color),
+                                                    sizeof color,
+                                                    g_free);
+    }
+  else if (strcmp (mime_type, "image/tiff") == 0)
+    {
+      NSData *data = [self->pasteboard dataForType:NSPasteboardTypeTIFF];
+      stream = create_stream_from_nsdata (data);
+    }
+  else if (strcmp (mime_type, "image/png") == 0)
+    {
+      NSData *data = [self->pasteboard dataForType:NSPasteboardTypePNG];
+      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;
+}
+
+static GInputStream *
+_gdk_macos_clipboard_read_finish (GdkClipboard  *clipboard,
+                                  GAsyncResult  *result,
+                                  const char   **out_mime_type,
+                                  GError       **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_assert (GDK_IS_MACOS_CLIPBOARD (clipboard));
+  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);
+}
+
+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));
+
+  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");
+
+  self->last_change_count = [self->pasteboard changeCount];
+
+  g_clear_pointer (&serializable, gdk_content_formats_unref);
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+static gboolean
+_gdk_macos_clipboard_claim (GdkClipboard       *clipboard,
+                            GdkContentFormats  *formats,
+                            gboolean            local,
+                            GdkContentProvider *provider)
+{
+  GdkMacosClipboard *self = (GdkMacosClipboard *)clipboard;
+  gboolean ret;
+
+  g_assert (GDK_IS_CLIPBOARD (clipboard));
+  g_assert (formats != NULL);
+  g_assert (!provider || GDK_IS_CONTENT_PROVIDER (provider));
+
+  ret = GDK_CLIPBOARD_CLASS (_gdk_macos_clipboard_parent_class)->claim (clipboard, formats, local, provider);
+
+  if (local)
+    _gdk_macos_clipboard_send_to_pasteboard (self, provider);
+
+  return ret;
+}
+
+static void
+_gdk_macos_clipboard_constructed (GObject *object)
+{
+  GdkMacosClipboard *self = (GdkMacosClipboard *)object;
+
+  if (self->pasteboard == nil)
+    self->pasteboard = [[NSPasteboard generalPasteboard] retain];
+
+  G_OBJECT_CLASS (_gdk_macos_clipboard_parent_class)->constructed (object);
+}
+
+static void
+_gdk_macos_clipboard_finalize (GObject *object)
+{
+  GdkMacosClipboard *self = (GdkMacosClipboard *)object;
+
+  if (self->pasteboard != nil)
+    {
+      [self->pasteboard release];
+      self->pasteboard = nil;
+    }
+
+  G_OBJECT_CLASS (_gdk_macos_clipboard_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_clipboard_class_init (GdkMacosClipboardClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (klass);
+
+  object_class->constructed = _gdk_macos_clipboard_constructed;
+  object_class->finalize = _gdk_macos_clipboard_finalize;
+
+  clipboard_class->claim = _gdk_macos_clipboard_claim;
+  clipboard_class->read_async = _gdk_macos_clipboard_read_async;
+  clipboard_class->read_finish = _gdk_macos_clipboard_read_finish;
+}
+
+static void
+_gdk_macos_clipboard_init (GdkMacosClipboard *self)
+{
+}
+
+GdkClipboard *
+_gdk_macos_clipboard_new (GdkMacosDisplay *display)
+{
+  GdkMacosClipboard *self;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  self = g_object_new (GDK_TYPE_MACOS_CLIPBOARD,
+                       "display", display,
+                       NULL);
+
+  _gdk_macos_clipboard_load_contents (self);
+
+  return GDK_CLIPBOARD (self);
+}
+
+void
+_gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self)
+{
+  g_return_if_fail (GDK_IS_MACOS_CLIPBOARD (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<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,
+                  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);
+}
+
+@end
diff --git a/gdk/macos/gdkmacoscursor-private.h b/gdk/macos/gdkmacoscursor-private.h
new file mode 100644
index 0000000000..b3e5bac70d
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor-private.h
@@ -0,0 +1,32 @@
+/* gdkmacoscursor-private.h
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_MACOS_CURSOR_PRIVATE_H__
+#define __GDK_MACOS_CURSOR_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+NSCursor *_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CURSOR_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscursor.c b/gdk/macos/gdkmacoscursor.c
new file mode 100644
index 0000000000..a6da5c922b
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor.c
@@ -0,0 +1,181 @@
+/* gdkcursor-macos.c
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gdkmacoscursor-private.h"
+
+/* OS X only exports a number of cursor types in its public NSCursor interface.
+ * By overriding the private _coreCursorType method, we can tell OS X to load
+ * one of its internal cursors instead (since cursor images are loaded on demand
+ * instead of in advance). WebKit does this too.
+ */
+
+@interface gdkCoreCursor : NSCursor {
+@private
+        int type;
+        BOOL override;
+}
+@end
+
+@implementation gdkCoreCursor
+
+- (long long)_coreCursorType
+{
+  if (self->override)
+    return self->type;
+  return [super _coreCursorType];
+}
+
+#define CUSTOM_CURSOR_CTOR(name, id) \
+        + (gdkCoreCursor *)name \
+        { \
+                gdkCoreCursor *obj; \
+                obj = [self new]; \
+                if (obj) { \
+                        obj->override = YES; \
+                        obj->type = id; \
+                } \
+                return obj; \
+        }
+CUSTOM_CURSOR_CTOR(gdkHelpCursor, 40)
+CUSTOM_CURSOR_CTOR(gdkProgressCursor, 4)
+/* TODO OS X doesn't seem to have a way to get this. There is an undocumented
+ * method +[NSCursor _waitCursor], but it doesn't actually return this cursor,
+ * but rather some odd low-quality non-animating version of this cursor. Use
+ * the progress cursor instead for now.
+ */
+CUSTOM_CURSOR_CTOR(gdkWaitCursor, 4)
+CUSTOM_CURSOR_CTOR(gdkAliasCursor, 2)
+CUSTOM_CURSOR_CTOR(gdkMoveCursor, 39)
+/* TODO OS X doesn't seem to provide one; copy the move cursor for now
+ *  since it looks similar to what we want. */
+CUSTOM_CURSOR_CTOR(gdkAllScrollCursor, 39)
+CUSTOM_CURSOR_CTOR(gdkNEResizeCursor, 29)
+CUSTOM_CURSOR_CTOR(gdkNWResizeCursor, 33)
+CUSTOM_CURSOR_CTOR(gdkSEResizeCursor, 35)
+CUSTOM_CURSOR_CTOR(gdkSWResizeCursor, 37)
+CUSTOM_CURSOR_CTOR(gdkEWResizeCursor, 28)
+CUSTOM_CURSOR_CTOR(gdkNSResizeCursor, 32)
+CUSTOM_CURSOR_CTOR(gdkNESWResizeCursor, 30)
+CUSTOM_CURSOR_CTOR(gdkNWSEResizeCursor, 34)
+CUSTOM_CURSOR_CTOR(gdkZoomInCursor, 42)
+CUSTOM_CURSOR_CTOR(gdkZoomOutCursor, 43)
+#undef CUSTOM_CURSOR_CTOR
+
+@end
+
+struct CursorsByName {
+  const gchar *name;
+  NSString *selector;
+};
+
+static const struct CursorsByName cursors_by_name[] = {
+  /* Link & Status */
+  { "context-menu", @"contextualMenuCursor" },
+  { "help", @"gdkHelpCursor" },
+  { "pointer", @"pointingHandCursor" },
+  { "progress", @"gdkProgressCursor" },
+  { "wait", @"gdkWaitCursor" },
+  /* Selection */
+  { "cell", @"crosshairCursor" },
+  { "crosshair", @"crosshairCursor" },
+  { "text", @"IBeamCursor" },
+  { "vertical-text", @"IBeamCursorForVerticalLayout" },
+  /* Drag & Drop */
+  { "alias", @"gdkAliasCursor" },
+  { "copy", @"dragCopyCursor" },
+  { "move", @"gdkMoveCursor" },
+  { "no-drop", @"operationNotAllowedCursor" },
+  { "not-allowed", @"operationNotAllowedCursor" },
+  { "grab", @"openHandCursor" },
+  { "grabbing", @"closedHandCursor" },
+  /* Resize & Scrolling */
+  { "all-scroll", @"gdkAllScrollCursor" },
+  { "col-resize", @"resizeLeftRightCursor" },
+  { "row-resize", @"resizeUpDownCursor" },
+
+  /* Undocumented cursors to match native resizing */
+  { "e-resize", @"_windowResizeEastWestCursor" },
+  { "w-resize", @"_windowResizeEastWestCursor" },
+  { "n-resize", @"_windowResizeNorthSouthCursor" },
+  { "s-resize", @"_windowResizeNorthSouthCursor" },
+
+  { "ne-resize", @"gdkNEResizeCursor" },
+  { "nw-resize", @"gdkNWResizeCursor" },
+  { "se-resize", @"gdkSEResizeCursor" },
+  { "sw-resize", @"gdkSWResizeCursor" },
+  { "ew-resize", @"gdkEWResizeCursor" },
+  { "ns-resize", @"gdkNSResizeCursor" },
+  { "nesw-resize", @"gdkNESWResizeCursor" },
+  { "nwse-resize", @"gdkNWSEResizeCursor" },
+  /* Zoom */
+  { "zoom-in", @"gdkZoomInCursor" },
+  { "zoom-out", @"gdkZoomOutCursor" },
+};
+
+static NSCursor *
+create_blank_cursor (void)
+{
+  NSCursor *nscursor;
+  NSImage *nsimage;
+  NSSize size = { 1.0, 1.0 };
+
+  nsimage = [[NSImage alloc] initWithSize:size];
+  nscursor = [[NSCursor alloc] initWithImage:nsimage
+                               hotSpot:NSMakePoint(0.0, 0.0)];
+  [nsimage release];
+
+  return nscursor;
+}
+
+NSCursor *
+_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor)
+{
+  const char *name = NULL;
+  NSCursor *nscursor;
+  SEL selector = @selector(arrowCursor);
+
+  g_return_val_if_fail (!cursor || GDK_IS_CURSOR (cursor), NULL);
+
+  if (cursor != NULL)
+    name = gdk_cursor_get_name (cursor);
+
+  if (name == NULL)
+    goto load_cursor;
+
+  if (strcmp (name, "none") == 0)
+    return create_blank_cursor ();
+
+  for (guint i = 0; i < G_N_ELEMENTS (cursors_by_name); i++)
+    {
+      if (strcmp (cursors_by_name[i].name, name) == 0)
+        {
+          selector = NSSelectorFromString (cursors_by_name[i].selector);
+          break;
+        }
+    }
+
+load_cursor:
+  nscursor = [[gdkCoreCursor class] performSelector:selector];
+  [nscursor retain];
+  return nscursor;
+}
diff --git a/gdk/macos/gdkmacosdevice.c b/gdk/macos/gdkmacosdevice.c
new file mode 100644
index 0000000000..8e621fa6fe
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2009 Carlos Garnacho <carlosg gnome org>
+ * Copyright 2010 Kristian Rietveld <kris gtk org>
+ * 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.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 <gdk/gdk.h>
+
+#include "gdkdeviceprivate.h"
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacoscursor-private.h"
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosDevice
+{
+  GdkDevice parent_instance;
+};
+
+struct _GdkMacosDeviceClass
+{
+  GdkDeviceClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosDevice, gdk_macos_device, GDK_TYPE_DEVICE)
+
+static void
+gdk_macos_device_set_surface_cursor (GdkDevice  *device,
+                                     GdkSurface *surface,
+                                     GdkCursor  *cursor)
+{
+  NSCursor *nscursor;
+
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (!cursor || GDK_IS_CURSOR (cursor));
+
+  nscursor = _gdk_macos_cursor_get_ns_cursor (cursor);
+
+  if (nscursor != NULL)
+    {
+      [nscursor set];
+      [nscursor release];
+    }
+}
+
+static GdkSurface *
+gdk_macos_device_surface_at_position (GdkDevice       *device,
+                                      gdouble         *win_x,
+                                      gdouble         *win_y,
+                                      GdkModifierType *state)
+{
+  GdkMacosDisplay *display;
+  GdkMacosSurface *surface;
+  NSPoint point;
+  gint x;
+  gint y;
+
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (win_x != NULL);
+  g_assert (win_y != NULL);
+
+  point = [NSEvent mouseLocation];
+  display = GDK_MACOS_DISPLAY (gdk_device_get_display (device));
+
+  if (state != NULL)
+    *state = (_gdk_macos_display_get_current_keyboard_modifiers (display) |
+              _gdk_macos_display_get_current_mouse_modifiers (display));
+
+  surface = _gdk_macos_display_get_surface_at_display_coords (display, point.x, point.y, &x, &y);
+
+  *win_x = x;
+  *win_y = y;
+
+  return GDK_SURFACE (surface);
+}
+
+static GdkGrabStatus
+gdk_macos_device_grab (GdkDevice    *device,
+                       GdkSurface   *window,
+                       gboolean      owner_events,
+                       GdkEventMask  event_mask,
+                       GdkSurface   *confine_to,
+                       GdkCursor    *cursor,
+                       guint32       time_)
+{
+  /* Should remain empty */
+  return GDK_GRAB_SUCCESS;
+}
+
+static void
+gdk_macos_device_ungrab (GdkDevice *device,
+                         guint32    time_)
+{
+  GdkMacosDevice *self = (GdkMacosDevice *)device;
+  GdkDeviceGrabInfo *grab;
+  GdkDisplay *display;
+
+  g_assert (GDK_IS_MACOS_DEVICE (self));
+
+  display = gdk_device_get_display (device);
+  grab = _gdk_display_get_last_device_grab (display, device);
+
+  if (grab != NULL)
+    grab->serial_end = 0;
+
+  _gdk_display_device_grab_update (display, device, device, 0);
+}
+
+static void
+gdk_macos_device_get_state (GdkDevice       *device,
+                            GdkSurface      *surface,
+                            gdouble         *axes,
+                            GdkModifierType *mask)
+{
+  gdouble x_pos, y_pos;
+
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  gdk_surface_get_device_position (surface, device, &x_pos, &y_pos, mask);
+
+  if (axes != NULL)
+    {
+      axes[0] = x_pos;
+      axes[1] = y_pos;
+    }
+}
+
+static void
+gdk_macos_device_query_state (GdkDevice        *device,
+                              GdkSurface       *surface,
+                              GdkSurface      **child_surface,
+                              gdouble          *win_x,
+                              gdouble          *win_y,
+                              GdkModifierType  *mask)
+{
+  GdkDisplay *display;
+  NSPoint point;
+  int sx = 0;
+  int sy = 0;
+  int x_tmp;
+  int y_tmp;
+
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (!surface || GDK_IS_MACOS_SURFACE (surface));
+
+  if (child_surface)
+    *child_surface = surface;
+
+  display = gdk_device_get_display (device);
+  point = [NSEvent mouseLocation];
+  _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+                                          point.x, point.y,
+                                          &x_tmp, &y_tmp);
+
+  if (surface)
+    _gdk_macos_surface_get_root_coords (GDK_MACOS_SURFACE (surface), &sx, &sy);
+
+  if (win_x)
+    *win_x = x_tmp - sx;
+
+  if (win_y)
+    *win_y = y_tmp - sy;
+
+  if (mask)
+    *mask = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display)) |
+            _gdk_macos_display_get_current_mouse_modifiers (GDK_MACOS_DISPLAY (display));
+}
+
+static void
+gdk_macos_device_class_init (GdkMacosDeviceClass *klass)
+{
+  GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
+
+  device_class->get_state = gdk_macos_device_get_state;
+  device_class->grab = gdk_macos_device_grab;
+  device_class->query_state = gdk_macos_device_query_state;
+  device_class->set_surface_cursor = gdk_macos_device_set_surface_cursor;
+  device_class->surface_at_position = gdk_macos_device_surface_at_position;
+  device_class->ungrab = gdk_macos_device_ungrab;
+}
+
+static void
+gdk_macos_device_init (GdkMacosDevice *self)
+{
+  _gdk_device_add_axis (GDK_DEVICE (self), GDK_AXIS_X, 0, 0, 1);
+  _gdk_device_add_axis (GDK_DEVICE (self), GDK_AXIS_Y, 0, 0, 1);
+}
diff --git a/gdk/macos/gdkmacosdevice.h b/gdk/macos/gdkmacosdevice.h
new file mode 100644
index 0000000000..c81fde5d1a
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.h
@@ -0,0 +1,43 @@
+/*
+ * 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.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_DEVICE_H__
+#define __GDK_MACOS_DEVICE_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDevice      GdkMacosDevice;
+typedef struct _GdkMacosDeviceClass GdkMacosDeviceClass;
+
+#define GDK_TYPE_MACOS_DEVICE       (gdk_macos_device_get_type())
+#define GDK_MACOS_DEVICE(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DEVICE, 
GdkMacosDevice))
+#define GDK_IS_MACOS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DEVICE))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_device_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DEVICE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h
new file mode 100644
index 0000000000..9f3d55853b
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -0,0 +1,154 @@
+/*
+ * 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.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_DISPLAY_PRIVATE_H__
+#define __GDK_MACOS_DISPLAY_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacoskeymap.h"
+#include "gdkmacossurface.h"
+
+G_BEGIN_DECLS
+
+struct _GdkMacosDisplay
+{
+  GdkDisplay parent_instance;
+
+  char *name;
+  GdkMacosKeymap *keymap;
+
+  /* An list of GdkMacosMonitor. The first instance is always the primary
+   * monitor. This contains the 0,0 coordinate in quartz coordinates, but may
+   * not be 0,0 in GDK coordinates.
+   */
+  GListStore *monitors;
+
+  /* A queue of surfaces that have been made "main" so that we can update the
+   * main status to the next surface when a window has lost main status (such
+   * as when destroyed). This uses the GdkMacosSurface main link.
+   */
+  GQueue main_surfaces;
+
+  /* A queue of surfaces sorted by their front-to-back ordering on the screen.
+   * This is updated occasionally when we know that the data we have cached
+   * has been invalidated. This uses the GdkMacosSurface.sorted link.
+   */
+  GQueue sorted_surfaces;
+
+  /* Our CVDisplayLink based GSource which we use to freeze/thaw the
+   * GdkFrameClock for the surface.
+   */
+  GSource *frame_source;
+
+  /* A queue of surfaces which we know are awaiting frames to be drawn. This
+   * uses the GdkMacosSurface.frame link.
+   */
+  GQueue awaiting_frames;
+
+  /* The surface that is receiving keyboard events */
+  GdkMacosSurface *keyboard_surface;
+
+  /* Used to translate from quartz coordinate space to GDK */
+  int width;
+  int height;
+  int min_x;
+  int min_y;
+  int max_x;
+  int max_y;
+};
+
+struct _GdkMacosDisplayClass
+{
+  GdkDisplayClass parent_class;
+};
+
+
+GdkDisplay      *_gdk_macos_display_open                           (const gchar     *display_name);
+int              _gdk_macos_display_get_fd                         (GdkMacosDisplay *self);
+void             _gdk_macos_display_queue_events                   (GdkMacosDisplay *self);
+void             _gdk_macos_display_to_display_coords              (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y,
+                                                                    int             *out_x,
+                                                                    int             *out_y);
+void             _gdk_macos_display_from_display_coords            (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y,
+                                                                    int             *out_x,
+                                                                    int             *out_y);
+NSScreen        *_gdk_macos_display_get_screen_at_display_coords   (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y);
+GdkMonitor      *_gdk_macos_display_get_monitor_at_coords          (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y);
+GdkMonitor      *_gdk_macos_display_get_monitor_at_display_coords  (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y);
+GdkEvent        *_gdk_macos_display_translate                      (GdkMacosDisplay *self,
+                                                                    NSEvent         *event);
+void             _gdk_macos_display_break_all_grabs                (GdkMacosDisplay *self,
+                                                                    guint32          time);
+GdkModifierType  _gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self);
+GdkModifierType  _gdk_macos_display_get_current_mouse_modifiers    (GdkMacosDisplay *self);
+GdkMacosSurface *_gdk_macos_display_get_surface_at_display_coords  (GdkMacosDisplay *self,
+                                                                    double           x,
+                                                                    double           y,
+                                                                    int             *surface_x,
+                                                                    int             *surface_y);
+void             _gdk_macos_display_reload_monitors                (GdkMacosDisplay *self);
+void             _gdk_macos_display_surface_removed                (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_add_frame_callback             (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_remove_frame_callback          (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_synthesize_motion              (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+NSWindow        *_gdk_macos_display_find_native_under_pointer      (GdkMacosDisplay *self,
+                                                                    int             *x,
+                                                                    int             *y);
+gboolean         _gdk_macos_display_get_setting                    (GdkMacosDisplay *self,
+                                                                    const gchar     *setting,
+                                                                    GValue          *value);
+void             _gdk_macos_display_reload_settings                (GdkMacosDisplay *self);
+void             _gdk_macos_display_surface_resigned_main          (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_surface_became_main            (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_surface_resigned_key           (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+void             _gdk_macos_display_surface_became_key             (GdkMacosDisplay *self,
+                                                                    GdkMacosSurface *surface);
+int              _gdk_macos_display_get_nominal_refresh_rate       (GdkMacosDisplay *self);
+void             _gdk_macos_display_clear_sorting                  (GdkMacosDisplay *self);
+const GList     *_gdk_macos_display_get_surfaces                   (GdkMacosDisplay *self);
+void             _gdk_macos_display_send_button_event              (GdkMacosDisplay *self,
+                                                                    NSEvent         *nsevent);
+void             _gdk_macos_display_warp_pointer                   (GdkMacosDisplay *self,
+                                                                    int              x,
+                                                                    int              y);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-settings.c b/gdk/macos/gdkmacosdisplay-settings.c
new file mode 100644
index 0000000000..558e0b81e0
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-settings.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright © 1998-2002 Tor Lillqvist
+ * Copyright © 2005-2008 Imendio AB
+ * 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.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 <AppKit/AppKit.h>
+
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosutils-private.h"
+
+typedef struct
+{
+  const char *font_name;
+  int         xft_dpi;
+  int         double_click_time;
+  int         cursor_blink_timeout;
+  guint       enable_animations : 1;
+  guint       shell_shows_desktop : 1;
+  guint       shell_shows_menubar : 1;
+  guint       primary_button_warps_slider : 1;
+} GdkMacosSettings;
+
+static GdkMacosSettings current_settings;
+static gboolean current_settings_initialized;
+
+static void
+_gdk_macos_settings_load (GdkMacosSettings *settings)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+  NSString *name;
+  NSInteger ival;
+  float fval;
+  char *str;
+  int pt_size;
+
+  g_assert (settings != NULL);
+
+  settings->shell_shows_desktop = TRUE;
+  settings->shell_shows_menubar = TRUE;
+  settings->enable_animations = TRUE;
+  settings->xft_dpi = 72 * 1024;
+
+  ival = [defaults integerForKey:@"NSTextInsertionPointBlinkPeriod"];
+  if (ival > 0)
+    settings->cursor_blink_timeout = ival;
+  else
+    settings->cursor_blink_timeout = 1000;
+
+  settings->primary_button_warps_slider =
+      [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"] == YES;
+
+  fval = [defaults floatForKey:@"com.apple.mouse.doubleClickThreshold"];
+  if (fval == 0.0)
+    fval = 0.5;
+  settings->double_click_time = fval * 1000;
+
+  name = [[NSFont systemFontOfSize:0] familyName];
+  pt_size = (gint)[[NSFont userFontOfSize:0] pointSize];
+  /* Let's try to use the "views" font size (12pt) by default. This is
+   * used for lists/text/other "content" which is the largest parts of
+   * apps, using the "regular control" size (13pt) looks a bit out of
+   * place. We might have to tweak this.
+   *
+   * The size has to be hardcoded as there doesn't seem to be a way to
+   * get the views font size programmatically.
+   */
+  str = g_strdup_printf ("%s %d", [name UTF8String], pt_size);
+  settings->font_name = g_intern_string (str);
+  g_free (str);
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+gboolean
+_gdk_macos_display_get_setting (GdkMacosDisplay *self,
+                                const gchar     *setting,
+                                GValue          *value)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), FALSE);
+  g_return_val_if_fail (setting != NULL, FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  if (!current_settings_initialized)
+    {
+      _gdk_macos_settings_load (&current_settings);
+      current_settings_initialized = TRUE;
+    }
+
+  if (FALSE) {}
+  else if (strcmp (setting, "gtk-enable-animations") == 0)
+    {
+      g_value_set_boolean (value, current_settings.enable_animations);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-xft-dpi") == 0)
+    {
+      g_value_set_int (value, current_settings.xft_dpi);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-cursor-blink-timeout") == 0)
+    {
+      g_value_set_int (value, current_settings.cursor_blink_timeout);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-double-click-time") == 0)
+    {
+      g_value_set_int (value, current_settings.double_click_time);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-font-name") == 0)
+    {
+      g_value_set_static_string (value, current_settings.font_name);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-primary-button-warps-slider") == 0)
+    {
+      g_value_set_boolean (value, current_settings.primary_button_warps_slider);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-shell-shows-desktop") == 0)
+    {
+      g_value_set_boolean (value, current_settings.shell_shows_desktop);
+      ret = TRUE;
+    }
+  else if (strcmp (setting, "gtk-shell-shows-menubar") == 0)
+    {
+      g_value_set_boolean (value, current_settings.shell_shows_menubar);
+      ret = TRUE;
+    }
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return ret;
+}
+
+void
+_gdk_macos_display_reload_settings (GdkMacosDisplay *self)
+{
+  GdkMacosSettings old_settings;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  old_settings = current_settings;
+  _gdk_macos_settings_load (&current_settings);
+  current_settings_initialized = TRUE;
+
+  if (old_settings.xft_dpi != current_settings.xft_dpi)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-xft-dpi");
+
+  if (old_settings.double_click_time != current_settings.double_click_time)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-double-click-time");
+
+  if (old_settings.enable_animations != current_settings.enable_animations)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-enable-animations");
+
+  if (old_settings.font_name != current_settings.font_name)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-font-name");
+
+  if (old_settings.primary_button_warps_slider != current_settings.primary_button_warps_slider)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-primary-button-warps-slider");
+
+  if (old_settings.shell_shows_menubar != current_settings.shell_shows_menubar)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-shell-shows-menubar");
+
+  if (old_settings.shell_shows_desktop != current_settings.shell_shows_desktop)
+    gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-shell-shows-desktop");
+}
diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c
new file mode 100644
index 0000000000..8e86459987
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -0,0 +1,1219 @@
+/*
+ * Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright 1998-2002 Tor Lillqvist
+ * Copyright 2005-2008 Imendio AB
+ * 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.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"
+
+#import "GdkMacosWindow.h"
+#import "GdkMacosBaseView.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacossurface-private.h"
+
+#define GDK_MOD2_MASK (1 << 4)
+#define GRIP_WIDTH 15
+#define GRIP_HEIGHT 15
+#define GDK_LION_RESIZE 5
+
+static gboolean
+test_resize (NSEvent         *event,
+             GdkMacosSurface *surface,
+             int              x,
+             int              y)
+{
+  NSWindow *window;
+
+  g_assert (event != NULL);
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  window = _gdk_macos_surface_get_native (surface);
+
+  /* Resizing from the resize indicator only begins if an NSLeftMouseButton
+   * event is received in the resizing area.
+   */
+  if ([event type] == NSEventTypeLeftMouseDown &&
+      [window showsResizeIndicator])
+    {
+      NSRect frame;
+
+      /* If the resize indicator is visible and the event is in the lower
+       * right 15x15 corner, we leave these events to Cocoa as to be
+       * handled as resize events.  Applications may have widgets in this
+       * area.  These will most likely be larger than 15x15 and for scroll
+       * bars there are also other means to move the scroll bar.  Since
+       * the resize indicator is the only way of resizing windows on Mac
+       * OS, it is too important to not make functional.
+       */
+      frame = [[window contentView] bounds];
+      if (x > frame.size.width - GRIP_WIDTH &&
+          x < frame.size.width &&
+          y > frame.size.height - GRIP_HEIGHT &&
+          y < frame.size.height)
+        return TRUE;
+     }
+
+  /* If we're on Lion and within 5 pixels of an edge, then assume that the
+   * user wants to resize, and return NULL to let Quartz get on with it.
+   * We check the selector isRestorable to see if we're on 10.7.  This
+   * extra check is in case the user starts dragging before GDK recognizes
+   * the grab.
+   *
+   * We perform this check for a button press of all buttons, because we
+   * do receive, for instance, a right mouse down event for a GDK surface
+   * for x-coordinate range [-3, 0], but we do not want to forward this
+   * into GDK. Forwarding such events into GDK will confuse the pointer
+   * window finding code, because there are no GdkSurfaces present in
+   * the range [-3, 0].
+   */
+  if (([event type] == NSEventTypeLeftMouseDown ||
+       [event type] == NSEventTypeRightMouseDown ||
+       [event type] == NSEventTypeOtherMouseDown))
+    {
+      if (x < GDK_LION_RESIZE ||
+          x > GDK_SURFACE (surface)->width - GDK_LION_RESIZE ||
+          y > GDK_SURFACE (surface)->height - GDK_LION_RESIZE)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static guint32
+get_time_from_ns_event (NSEvent *event)
+{
+  double time = [event timestamp];
+  /* cast via double->uint64 conversion to make sure that it is
+   * wrapped on 32-bit machines when it overflows
+   */
+  return (guint32) (guint64) (time * 1000.0);
+}
+
+static int
+get_mouse_button_from_ns_event (NSEvent *event)
+{
+  NSInteger button = [event buttonNumber];
+
+  switch (button)
+    {
+    case 0:
+      return 1;
+    case 1:
+      return 3;
+    case 2:
+      return 2;
+    default:
+      return button + 1;
+    }
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_buttons (NSUInteger nsbuttons)
+{
+  GdkModifierType modifiers = 0;
+
+  if (nsbuttons & (1 << 0))
+    modifiers |= GDK_BUTTON1_MASK;
+  if (nsbuttons & (1 << 1))
+    modifiers |= GDK_BUTTON3_MASK;
+  if (nsbuttons & (1 << 2))
+    modifiers |= GDK_BUTTON2_MASK;
+  if (nsbuttons & (1 << 3))
+    modifiers |= GDK_BUTTON4_MASK;
+  if (nsbuttons & (1 << 4))
+    modifiers |= GDK_BUTTON5_MASK;
+
+  return modifiers;
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_event (NSEvent *event)
+{
+  GdkModifierType state = 0;
+  int button;
+
+  /* This maps buttons 1 to 5 to GDK_BUTTON[1-5]_MASK */
+  button = get_mouse_button_from_ns_event (event);
+  if (button >= 1 && button <= 5)
+    state = (1 << (button + 7));
+
+  return state;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_flags (NSUInteger nsflags)
+{
+  GdkModifierType modifiers = 0;
+
+  if (nsflags & NSEventModifierFlagCapsLock)
+    modifiers |= GDK_LOCK_MASK;
+  if (nsflags & NSEventModifierFlagShift)
+    modifiers |= GDK_SHIFT_MASK;
+  if (nsflags & NSEventModifierFlagControl)
+    modifiers |= GDK_CONTROL_MASK;
+  if (nsflags & NSEventModifierFlagOption)
+    modifiers |= GDK_ALT_MASK;
+  if (nsflags & NSEventModifierFlagCommand)
+    modifiers |= GDK_MOD2_MASK;
+
+  return modifiers;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_event (NSEvent *nsevent)
+{
+  return get_keyboard_modifiers_from_ns_flags ([nsevent modifierFlags]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_mouse_modifiers (GdkMacosDisplay *self)
+{
+  return get_mouse_button_modifiers_from_ns_buttons ([NSEvent pressedMouseButtons]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self)
+{
+  return get_keyboard_modifiers_from_ns_flags ([NSEvent modifierFlags]);
+}
+
+static GdkEvent *
+fill_button_event (GdkMacosDisplay *display,
+                   GdkMacosSurface *surface,
+                   NSEvent         *nsevent,
+                   int              x,
+                   int              y)
+{
+  GdkSeat *seat;
+  GdkEventType type;
+  GdkModifierType state;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  /* Ignore button events outside the window coords */
+  if (x < 0 || x > GDK_SURFACE (surface)->width ||
+      y < 0 || y > GDK_SURFACE (surface)->height)
+    return NULL;
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+  state = get_keyboard_modifiers_from_ns_event (nsevent) |
+         _gdk_macos_display_get_current_mouse_modifiers (display);
+
+  switch ((int)[nsevent type])
+    {
+    case NSEventTypeLeftMouseDown:
+    case NSEventTypeRightMouseDown:
+    case NSEventTypeOtherMouseDown:
+      type = GDK_BUTTON_PRESS;
+      state &= ~get_mouse_button_modifiers_from_ns_event (nsevent);
+      break;
+
+    case NSEventTypeLeftMouseUp:
+    case NSEventTypeRightMouseUp:
+    case NSEventTypeOtherMouseUp:
+      type = GDK_BUTTON_RELEASE;
+      state |= get_mouse_button_modifiers_from_ns_event (nsevent);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  return gdk_button_event_new (type,
+                               GDK_SURFACE (surface),
+                               gdk_seat_get_pointer (seat),
+                               NULL,
+                               NULL,
+                               get_time_from_ns_event (nsevent),
+                               state,
+                               get_mouse_button_from_ns_event (nsevent),
+                               x,
+                               y,
+                               NULL);
+}
+
+static GdkEvent *
+synthesize_crossing_event (GdkMacosDisplay *display,
+                           GdkMacosSurface *surface,
+                           NSEvent         *nsevent,
+                           int              x,
+                           int              y)
+{
+  GdkEventType event_type;
+  GdkModifierType state;
+  GdkSeat *seat;
+
+  switch ((int)[nsevent type])
+    {
+    case NSEventTypeMouseEntered:
+      event_type = GDK_ENTER_NOTIFY;
+      break;
+
+    case NSEventTypeMouseExited:
+      event_type = GDK_LEAVE_NOTIFY;
+      break;
+
+    default:
+      g_return_val_if_reached (NULL);
+    }
+
+  state = get_keyboard_modifiers_from_ns_event (nsevent) |
+         _gdk_macos_display_get_current_mouse_modifiers (display);
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+  return gdk_crossing_event_new (event_type,
+                                 GDK_SURFACE (surface),
+                                 gdk_seat_get_pointer (seat),
+                                 NULL,
+                                 get_time_from_ns_event (nsevent),
+                                 state,
+                                 x,
+                                 y,
+                                 GDK_CROSSING_NORMAL,
+                                 GDK_NOTIFY_NONLINEAR);
+}
+
+static inline guint
+get_group_from_ns_event (NSEvent *nsevent)
+{
+  return ([nsevent modifierFlags] & NSEventModifierFlagOption) ? 1 : 0;
+}
+
+static void
+add_virtual_modifiers (GdkModifierType *state)
+{
+  if (*state & GDK_MOD2_MASK)
+    *state |= GDK_META_MASK;
+}
+
+static GdkEvent *
+fill_key_event (GdkMacosDisplay *display,
+                GdkMacosSurface *surface,
+                NSEvent         *nsevent,
+                GdkEventType     type)
+{
+  GdkTranslatedKey translated = {0};
+  GdkTranslatedKey no_lock = {0};
+  GdkModifierType consumed;
+  GdkModifierType state;
+  GdkKeymap *keymap;
+  gboolean is_modifier;
+  GdkSeat *seat;
+  guint keycode;
+  guint keyval;
+  guint group;
+  int layout;
+  int level;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (nsevent != NULL);
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+  keymap = gdk_display_get_keymap (GDK_DISPLAY (display));
+  keycode = [nsevent keyCode];
+  keyval = GDK_KEY_VoidSymbol;
+  state = get_keyboard_modifiers_from_ns_event (nsevent);
+  group = get_group_from_ns_event (nsevent);
+  is_modifier = _gdk_macos_keymap_is_modifier (keycode);
+
+  gdk_keymap_translate_keyboard_state (keymap, keycode, state, group,
+                                       &keyval, &layout, &level, &consumed);
+
+  /* If the key press is a modifier, the state should include the mask for
+   * that modifier but only for releases, not presses. This matches the
+   * X11 backend behavior.
+   */
+  if (is_modifier)
+    {
+      guint mask = 0;
+
+      switch (keyval)
+        {
+        case GDK_KEY_Meta_R:
+        case GDK_KEY_Meta_L:
+          mask = GDK_MOD2_MASK;
+          break;
+        case GDK_KEY_Shift_R:
+        case GDK_KEY_Shift_L:
+          mask = GDK_SHIFT_MASK;
+          break;
+        case GDK_KEY_Caps_Lock:
+          mask = GDK_LOCK_MASK;
+          break;
+        case GDK_KEY_Alt_R:
+        case GDK_KEY_Alt_L:
+          mask = GDK_ALT_MASK;
+          break;
+        case GDK_KEY_Control_R:
+        case GDK_KEY_Control_L:
+          mask = GDK_CONTROL_MASK;
+          break;
+        default:
+          mask = 0;
+        }
+
+      if (type == GDK_KEY_PRESS)
+        state &= ~mask;
+      else if (type == GDK_KEY_RELEASE)
+        state |= mask;
+    }
+
+  state |= _gdk_macos_display_get_current_mouse_modifiers (display);
+  add_virtual_modifiers (&state);
+
+  translated.keyval = keyval;
+  translated.consumed = consumed;
+  translated.layout = layout;
+  translated.level = level;
+
+  if (state & GDK_LOCK_MASK)
+    {
+      gdk_keymap_translate_keyboard_state (keymap,
+                                           keycode,
+                                           state & ~GDK_LOCK_MASK,
+                                           group,
+                                           &keyval,
+                                           &layout,
+                                           &level,
+                                           &consumed);
+
+      no_lock.keyval = keycode;
+      no_lock.consumed = consumed;
+      no_lock.layout = layout;
+      no_lock.level = level;
+    }
+  else
+    {
+      no_lock = translated;
+    }
+
+  return gdk_key_event_new (type,
+                            GDK_SURFACE (surface),
+                            gdk_seat_get_keyboard (seat),
+                            NULL,
+                            get_time_from_ns_event (nsevent),
+                            [nsevent keyCode],
+                            state,
+                            is_modifier,
+                            &translated,
+                            &no_lock);
+}
+
+static GdkEvent *
+fill_pinch_event (GdkMacosDisplay *display,
+                  GdkMacosSurface *surface,
+                  NSEvent         *nsevent,
+                  int              x,
+                  int              y)
+{
+  static double last_scale = 1.0;
+  static enum {
+    FP_STATE_IDLE,
+    FP_STATE_UPDATE
+  } last_state = FP_STATE_IDLE;
+  GdkSeat *seat;
+  GdkTouchpadGesturePhase phase;
+  gdouble angle_delta = 0.0;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  /* fill_pinch_event handles the conversion from the two OSX gesture events
+   * NSEventTypeMagnfiy and NSEventTypeRotate to the GDK_TOUCHPAD_PINCH event.
+   * The normal behavior of the OSX events is that they produce as sequence of
+   *   1 x NSEventPhaseBegan,
+   *   n x NSEventPhaseChanged,
+   *   1 x NSEventPhaseEnded
+   * This can happen for both the Magnify and the Rotate events independently.
+   * As both events are summarized in one GDK_TOUCHPAD_PINCH event sequence, a
+   * little state machine handles the case of two NSEventPhaseBegan events in
+   * a sequence, e.g. Magnify(Began), Magnify(Changed)..., Rotate(Began)...
+   * such that PINCH(STARTED), PINCH(UPDATE).... will not show a second
+   * PINCH(STARTED) event.
+   */
+
+  switch ((int)[nsevent phase])
+    {
+    case NSEventPhaseBegan:
+      switch (last_state)
+        {
+        case FP_STATE_IDLE:
+          phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
+          last_state = FP_STATE_UPDATE;
+          last_scale = 1.0;
+          break;
+        case FP_STATE_UPDATE:
+          /* We have already received a PhaseBegan event but no PhaseEnded
+             event. This can happen, e.g. Magnify(Began), Magnify(Change)...
+             Rotate(Began), Rotate (Change),...., Magnify(End) Rotate(End)
+          */
+          phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+          break;
+        }
+      break;
+
+    case NSEventPhaseChanged:
+      phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+      break;
+
+    case NSEventPhaseEnded:
+      phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
+      switch (last_state)
+        {
+        case FP_STATE_IDLE:
+          /* We are idle but have received a second PhaseEnded event.
+             This can happen because we have Magnify and Rotate OSX
+             event sequences. We just send a second end GDK_PHASE_END.
+          */
+          break;
+        case FP_STATE_UPDATE:
+          last_state = FP_STATE_IDLE;
+          break;
+        }
+      break;
+
+    case NSEventPhaseCancelled:
+      phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+      last_state = FP_STATE_IDLE;
+      break;
+
+    case NSEventPhaseMayBegin:
+    case NSEventPhaseStationary:
+      phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  switch ((int)[nsevent type])
+    {
+    case NSEventTypeMagnify:
+      last_scale *= [nsevent magnification] + 1.0;
+      angle_delta = 0.0;
+      break;
+
+    case NSEventTypeRotate:
+      angle_delta = - [nsevent rotation] * G_PI / 180.0;
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+  return gdk_touchpad_event_new_pinch (GDK_SURFACE (surface),
+                                       gdk_seat_get_pointer (seat),
+                                       NULL,
+                                       get_time_from_ns_event (nsevent),
+                                       get_keyboard_modifiers_from_ns_event (nsevent),
+                                       phase,
+                                       x,
+                                       y,
+                                       2,
+                                       0.0,
+                                       0.0,
+                                       last_scale,
+                                       angle_delta);
+
+}
+
+static gboolean
+is_motion_event (NSEventType event_type)
+{
+  return (event_type == NSEventTypeLeftMouseDragged ||
+          event_type == NSEventTypeRightMouseDragged ||
+          event_type == NSEventTypeOtherMouseDragged ||
+          event_type == NSEventTypeMouseMoved);
+}
+
+static GdkEvent *
+fill_motion_event (GdkMacosDisplay *display,
+                   GdkMacosSurface *surface,
+                   NSEvent         *nsevent,
+                   int              x,
+                   int              y)
+{
+  GdkSeat *seat;
+  GdkModifierType state;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (nsevent != NULL);
+  g_assert (is_motion_event ([nsevent type]));
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+  state = get_keyboard_modifiers_from_ns_event (nsevent) |
+          _gdk_macos_display_get_current_mouse_modifiers (display);
+
+  return gdk_motion_event_new (GDK_SURFACE (surface),
+                               gdk_seat_get_pointer (seat),
+                               NULL,
+                               NULL,
+                               get_time_from_ns_event (nsevent),
+                               state,
+                               x,
+                               y,
+                               NULL);
+}
+
+static GdkEvent *
+fill_scroll_event (GdkMacosDisplay *self,
+                   GdkMacosSurface *surface,
+                   NSEvent         *nsevent,
+                   int              x,
+                   int              y)
+{
+  GdkScrollDirection direction = 0;
+  GdkModifierType state;
+  GdkDevice *pointer;
+  GdkEvent *ret = NULL;
+  GdkSeat *seat;
+  gdouble dx;
+  gdouble dy;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (nsevent != NULL);
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  pointer = gdk_seat_get_pointer (seat);
+  state = _gdk_macos_display_get_current_mouse_modifiers (self) |
+          _gdk_macos_display_get_current_keyboard_modifiers (self);
+
+  dx = [nsevent deltaX];
+  dy = [nsevent deltaY];
+
+  if ([nsevent hasPreciseScrollingDeltas])
+    {
+      gdouble sx;
+      gdouble sy;
+
+      /*
+       * TODO: We probably need another event type for the
+       *       high precision scroll events since sx and dy
+       *       are in a unit we don't quite support. For now,
+       *       to slow it down multiply by .1.
+       */
+
+      sx = [nsevent scrollingDeltaX] * .1;
+      sy = [nsevent scrollingDeltaY] * .1;
+
+      if (sx != 0.0 || dx != 0.0)
+        ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+                                    pointer,
+                                    NULL,
+                                    NULL,
+                                    get_time_from_ns_event (nsevent),
+                                    state,
+                                    -sx,
+                                    -sy,
+                                    FALSE);
+
+      /* Fall through for scroll emulation */
+    }
+
+  if (dy != 0.0)
+    {
+      if (dy < 0.0)
+        direction = GDK_SCROLL_DOWN;
+      else
+        direction = GDK_SCROLL_UP;
+
+      dx = 0.0;
+    }
+  else if (dx != 0.0)
+    {
+      if (dx < 0.0)
+        direction = GDK_SCROLL_RIGHT;
+      else
+        direction = GDK_SCROLL_LEFT;
+
+      dy = 0.0;
+    }
+
+  if (dx != 0.0 || dy != 0.0)
+    {
+      if ([nsevent hasPreciseScrollingDeltas])
+        {
+          GdkEvent *emulated;
+
+          emulated = gdk_scroll_event_new_discrete (GDK_SURFACE (surface),
+                                                    pointer,
+                                                    NULL,
+                                                    NULL,
+                                                    get_time_from_ns_event (nsevent),
+                                                    state,
+                                                    direction,
+                                                    TRUE);
+          _gdk_event_queue_append (GDK_DISPLAY (self), emulated);
+        }
+      else
+        {
+          g_assert (ret == NULL);
+
+          ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+                                      pointer,
+                                      NULL,
+                                      NULL,
+                                      get_time_from_ns_event (nsevent),
+                                      state,
+                                      dx,
+                                      dy,
+                                      FALSE);
+        }
+    }
+
+  return g_steal_pointer (&ret);
+}
+
+static gboolean
+is_mouse_button_press_event (NSEventType type)
+{
+  switch ((int)type)
+    {
+    case NSEventTypeLeftMouseDown:
+    case NSEventTypeRightMouseDown:
+    case NSEventTypeOtherMouseDown:
+      return TRUE;
+
+    default:
+      return FALSE;
+    }
+}
+
+static void
+get_surface_point_from_screen_point (GdkSurface *surface,
+                                     NSPoint     screen_point,
+                                     int        *x,
+                                     int        *y)
+{
+  NSWindow *nswindow;
+  NSPoint point;
+
+  nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+  point = [nswindow convertPointFromScreen:screen_point];
+
+  *x = point.x;
+  *y = surface->height - point.y;
+}
+
+static GdkSurface *
+find_surface_under_pointer (GdkMacosDisplay *self,
+                            NSPoint          screen_point,
+                            int             *x,
+                            int             *y)
+{
+  GdkPointerSurfaceInfo *info;
+  GdkSurface *surface;
+  GdkSeat *seat;
+  int x_tmp, y_tmp;
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  info = _gdk_display_get_pointer_info (GDK_DISPLAY (self),
+                                        gdk_seat_get_pointer (seat));
+  surface = info->surface_under_pointer;
+
+  if (surface == NULL)
+    {
+      GdkMacosSurface *found;
+
+      found = _gdk_macos_display_get_surface_at_display_coords (self,
+                                                                screen_point.x,
+                                                                screen_point.y,
+                                                                &x_tmp, &y_tmp);
+
+      if (found)
+        {
+          surface = GDK_SURFACE (found);
+          info->surface_under_pointer = g_object_ref (surface);
+        }
+    }
+
+  if (surface)
+    {
+      _gdk_macos_display_from_display_coords (self,
+                                              screen_point.x,
+                                              screen_point.y,
+                                              &x_tmp, &y_tmp);
+      *x = x_tmp - GDK_MACOS_SURFACE (surface)->root_x;
+      *y = y_tmp - GDK_MACOS_SURFACE (surface)->root_y;
+      /* If the coordinates are out of window bounds, this surface is not
+       * under the pointer and we thus return NULL. This can occur when
+       * surface under pointer has not yet been updated due to a very recent
+       * window resize. Alternatively, we should no longer be relying on
+       * the surface_under_pointer value which is maintained in gdkwindow.c.
+       */
+      if (*x < 0 || *y < 0 || *x >= surface->width || *y >= surface->height)
+        return NULL;
+    }
+
+  return surface;
+}
+
+static GdkSurface *
+get_surface_from_ns_event (GdkMacosDisplay *self,
+                           NSEvent         *nsevent,
+                           NSPoint         *screen_point,
+                           int             *x,
+                           int             *y)
+{
+  GdkSurface *surface = NULL;
+  NSWindow *nswindow = [nsevent window];
+
+  if (nswindow)
+    {
+      GdkMacosBaseView *view;
+      NSPoint point, view_point;
+      NSRect view_frame;
+
+      view = (GdkMacosBaseView *)[nswindow contentView];
+      surface = GDK_SURFACE ([view gdkSurface]);
+
+      point = [nsevent locationInWindow];
+      view_point = [view convertPoint:point fromView:nil];
+      view_frame = [view frame];
+
+      /* NSEvents come in with a window set, but with window coordinates
+       * out of window bounds. For e.g. moved events this is fine, we use
+       * this information to properly handle enter/leave notify and motion
+       * events. For mouse button press/release, we want to avoid forwarding
+       * these events however, because the window they relate to is not the
+       * window set in the event. This situation appears to occur when button
+       * presses come in just before (or just after?) a window is resized and
+       * also when a button press occurs on the OS X window titlebar.
+       *
+       * By setting surface to NULL, we do another attempt to get the right
+       * surface window below.
+       */
+      if (is_mouse_button_press_event ([nsevent type]) &&
+          (view_point.x < view_frame.origin.x ||
+           view_point.x >= view_frame.origin.x + view_frame.size.width ||
+           view_point.y < view_frame.origin.y ||
+           view_point.y >= view_frame.origin.y + view_frame.size.height))
+        {
+          NSRect windowRect = [nswindow frame];
+
+          surface = NULL;
+
+          /* This is a hack for button presses to break all grabs. E.g. if
+           * a menu is open and one clicks on the title bar (or anywhere
+           * out of window bounds), we really want to pop down the menu (by
+           * breaking the grabs) before OS X handles the action of the title
+           * bar button.
+           *
+           * Because we cannot ingest this event into GDK, we have to do it
+           * here, not very nice.
+           */
+          _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+
+          /* If the X,Y is on the frame itself, then we don't want to discover
+           * the surface under the pointer at all so that we let OS X handle
+           * it instead. We add padding to include resize operations too.
+           */
+          windowRect.origin.x = -GDK_LION_RESIZE;
+          windowRect.origin.y = -GDK_LION_RESIZE;
+          windowRect.size.width += (2 * GDK_LION_RESIZE);
+          windowRect.size.height += (2 * GDK_LION_RESIZE);
+          if (NSPointInRect (point, windowRect))
+            return NULL;
+        }
+      else
+        {
+          *screen_point = [(GdkMacosWindow *)[nsevent window] convertPointToScreen:point];
+          *x = point.x;
+          *y = surface->height - point.y;
+        }
+    }
+
+  if (!surface)
+    {
+      /* Fallback used when no NSSurface set.  This happens e.g. when
+       * we allow motion events without a window set in gdk_event_translate()
+       * that occur immediately after the main menu bar was clicked/used.
+       * This fallback will not return coordinates contained in a window's
+       * titlebar.
+       */
+      *screen_point = [NSEvent mouseLocation];
+      surface = find_surface_under_pointer (self, *screen_point, x, y);
+    }
+
+  return surface;
+}
+
+static GdkMacosSurface *
+find_surface_for_keyboard_event (NSEvent *nsevent)
+{
+  GdkMacosBaseView *view = (GdkMacosBaseView *)[[nsevent window] contentView];
+  GdkSurface *surface = GDK_SURFACE ([view gdkSurface]);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  GdkSeat *seat = gdk_display_get_default_seat (display);
+  GdkDevice *device = gdk_seat_get_keyboard (seat);
+  GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (display, device);
+
+  if (grab && grab->surface && !grab->owner_events)
+    return GDK_MACOS_SURFACE (grab->surface);
+
+  return GDK_MACOS_SURFACE (surface);
+}
+
+static GdkMacosSurface *
+find_surface_for_mouse_event (GdkMacosDisplay *self,
+                              NSEvent         *nsevent,
+                              int             *x,
+                              int             *y)
+{
+  NSPoint point;
+  NSEventType event_type;
+  GdkSurface *surface;
+  GdkDisplay *display;
+  GdkDevice *pointer;
+  GdkDeviceGrabInfo *grab;
+  GdkSeat *seat;
+
+  surface = get_surface_from_ns_event (self, nsevent, &point, x, y);
+  display = gdk_surface_get_display (surface);
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  pointer = gdk_seat_get_pointer (seat);
+
+  event_type = [nsevent type];
+
+  /* From the docs for XGrabPointer:
+   *
+   * If owner_events is True and if a generated pointer event
+   * would normally be reported to this client, it is reported
+   * as usual. Otherwise, the event is reported with respect to
+   * the grab_window and is reported only if selected by
+   * event_mask. For either value of owner_events, unreported
+   * events are discarded.
+   */
+  if ((grab = _gdk_display_get_last_device_grab (display, pointer)))
+    {
+      if (grab->owner_events)
+        {
+          /* For owner events, we need to use the surface under the
+           * pointer, not the window from the NSEvent, since that is
+           * reported with respect to the key window, which could be
+           * wrong.
+           */
+          GdkSurface *surface_under_pointer;
+          int x_tmp, y_tmp;
+
+          surface_under_pointer = find_surface_under_pointer (self, point, &x_tmp, &y_tmp);
+          if (surface_under_pointer)
+            {
+              surface = surface_under_pointer;
+              *x = x_tmp;
+              *y = y_tmp;
+            }
+
+          return GDK_MACOS_SURFACE (surface);
+        }
+      else
+        {
+          /* Finally check the grab window. */
+          GdkSurface *grab_surface = grab->surface;
+          get_surface_point_from_screen_point (grab_surface, point, x, y);
+          return GDK_MACOS_SURFACE (grab_surface);
+        }
+
+      return NULL;
+    }
+  else
+    {
+      /* The non-grabbed case. */
+      GdkSurface *surface_under_pointer;
+      int x_tmp, y_tmp;
+
+      /* Ignore all events but mouse moved that might be on the title
+       * bar (above the content view). The reason is that otherwise
+       * gdk gets confused about getting e.g. button presses with no
+       * window (the title bar is not known to it).
+       */
+      if (event_type != NSEventTypeMouseMoved)
+        {
+          if (*y < 0)
+            return NULL;
+        }
+
+      /* As for owner events, we need to use the surface under the
+       * pointer, not the window from the NSEvent.
+       */
+      surface_under_pointer = find_surface_under_pointer (self, point, &x_tmp, &y_tmp);
+      if (surface_under_pointer != NULL)
+        {
+          surface = surface_under_pointer;
+          *x = x_tmp;
+          *y = y_tmp;
+        }
+
+      return GDK_MACOS_SURFACE (surface);
+    }
+
+  return NULL;
+}
+
+/* This function finds the correct window to send an event to, taking
+ * into account grabs, event propagation, and event masks.
+ */
+static GdkMacosSurface *
+find_surface_for_ns_event (GdkMacosDisplay *self,
+                           NSEvent         *nsevent,
+                           int             *x,
+                           int             *y)
+{
+  GdkMacosBaseView *view;
+  GdkSurface *surface;
+  NSPoint point;
+  int x_tmp;
+  int y_tmp;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (nsevent != NULL);
+  g_assert (x != NULL);
+  g_assert (y != NULL);
+
+  view = (GdkMacosBaseView *)[[nsevent window] contentView];
+
+  if (!(surface = get_surface_from_ns_event (self, nsevent, &point, x, y)))
+    return NULL;
+
+  _gdk_macos_display_from_display_coords (self, point.x, point.y, &x_tmp, &y_tmp);
+
+  switch ((int)[nsevent type])
+    {
+    case NSEventTypeLeftMouseDown:
+    case NSEventTypeRightMouseDown:
+    case NSEventTypeOtherMouseDown:
+    case NSEventTypeLeftMouseUp:
+    case NSEventTypeRightMouseUp:
+    case NSEventTypeOtherMouseUp:
+    case NSEventTypeLeftMouseDragged:
+    case NSEventTypeRightMouseDragged:
+    case NSEventTypeOtherMouseDragged:
+    case NSEventTypeMouseMoved:
+    case NSEventTypeScrollWheel:
+    case NSEventTypeMagnify:
+    case NSEventTypeRotate:
+      return find_surface_for_mouse_event (self, nsevent, x, y);
+
+    case NSEventTypeMouseEntered:
+    case NSEventTypeMouseExited:
+      /* Only handle our own entered/exited events, not the ones for the
+       * titlebar buttons.
+       */
+      if ([nsevent trackingArea] == [view trackingArea])
+        return GDK_MACOS_SURFACE (surface);
+      else
+        return NULL;
+
+    case NSEventTypeKeyDown:
+    case NSEventTypeKeyUp:
+    case NSEventTypeFlagsChanged:
+      return find_surface_for_keyboard_event (nsevent);
+
+    default:
+      /* Ignore everything else. */
+      return NULL;
+    }
+}
+
+GdkEvent *
+_gdk_macos_display_translate (GdkMacosDisplay *self,
+                              NSEvent         *nsevent)
+{
+  GdkMacosSurface *surface;
+  GdkMacosWindow *window;
+  NSEventType event_type;
+  GdkEvent *ret = NULL;
+  int x;
+  int y;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+  g_return_val_if_fail (nsevent != NULL, NULL);
+
+  /* There is no support for real desktop wide grabs, so we break
+   * grabs when the application loses focus (gets deactivated).
+   */
+  event_type = [nsevent type];
+  if (event_type == NSEventTypeAppKitDefined)
+    {
+      if ([nsevent subtype] == NSEventSubtypeApplicationDeactivated)
+        _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+
+      /* This could potentially be used to break grabs when clicking
+       * on the title. The subtype 20 is undocumented so it's probably
+       * not a good idea: else if (subtype == 20) break_all_grabs ();
+       */
+
+      /* Leave all AppKit events to AppKit. */
+      return NULL;
+    }
+
+  if (!(surface = find_surface_for_ns_event (self, nsevent, &x, &y)))
+    return NULL;
+
+  if (!(window = (GdkMacosWindow *)_gdk_macos_surface_get_native (surface)))
+    return NULL;
+
+  /* Ignore events and break grabs while the window is being
+   * dragged. This is a workaround for the window getting events for
+   * the window title.
+   */
+  if ([window isInMove])
+    {
+      _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+      return NULL;
+    }
+
+  /* Also when in a manual resize or move , we ignore events so that
+   * these are pushed to GdkMacosNSWindow's sendEvent handler.
+   */
+  if ([window isInManualResizeOrMove])
+    return NULL;
+
+  /* Make sure we have a GdkSurface */
+  if (!(surface = [window gdkSurface]))
+    return NULL;
+
+  /* Quartz handles resizing on its own, so stay out of the way. */
+  if (test_resize (nsevent, surface, x, y))
+    return NULL;
+
+  if ((event_type == NSEventTypeRightMouseDown ||
+       event_type == NSEventTypeOtherMouseDown ||
+       event_type == NSEventTypeLeftMouseDown))
+    {
+      if (![NSApp isActive])
+        [NSApp activateIgnoringOtherApps:YES];
+
+      if (![window isKeyWindow])
+        [window makeKeyWindow];
+    }
+
+  switch ((int)event_type)
+    {
+    case NSEventTypeLeftMouseDown:
+    case NSEventTypeRightMouseDown:
+    case NSEventTypeOtherMouseDown:
+    case NSEventTypeLeftMouseUp:
+    case NSEventTypeRightMouseUp:
+    case NSEventTypeOtherMouseUp:
+      ret = fill_button_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSEventTypeLeftMouseDragged:
+    case NSEventTypeRightMouseDragged:
+    case NSEventTypeOtherMouseDragged:
+    case NSEventTypeMouseMoved:
+      ret = fill_motion_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSEventTypeMagnify:
+    case NSEventTypeRotate:
+      ret = fill_pinch_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSEventTypeMouseExited:
+      [[NSCursor arrowCursor] set];
+      /* fallthrough */
+    case NSEventTypeMouseEntered:
+      ret = synthesize_crossing_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSEventTypeKeyDown:
+    case NSEventTypeKeyUp:
+    case NSEventTypeFlagsChanged: {
+      GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent);
+
+      if (type)
+        ret = fill_key_event (self, surface, nsevent, type);
+
+      break;
+    }
+
+    case NSEventTypeScrollWheel:
+      ret = fill_scroll_event (self, surface, nsevent, x, y);
+      break;
+
+    default:
+      break;
+    }
+
+  return ret;
+}
+
+void
+_gdk_macos_display_synthesize_motion (GdkMacosDisplay *self,
+                                      GdkMacosSurface *surface)
+{
+  GdkModifierType state;
+  GdkEvent *event;
+  GdkSeat *seat;
+  NSPoint point;
+  GList *node;
+  int x;
+  int y;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  point = [NSEvent mouseLocation];
+  _gdk_macos_display_from_display_coords (self, point.x, point.y, &x, &y);
+
+  state = _gdk_macos_display_get_current_keyboard_modifiers (self) |
+          _gdk_macos_display_get_current_mouse_modifiers (self);
+
+  event = gdk_motion_event_new (GDK_SURFACE (surface),
+                                gdk_seat_get_pointer (seat),
+                                NULL,
+                                NULL,
+                                get_time_from_ns_event ([NSApp currentEvent]),
+                                state,
+                                x,
+                                y,
+                                NULL);
+  node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+  _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+}
+
+void
+_gdk_macos_display_send_button_event (GdkMacosDisplay *self,
+                                      NSEvent         *nsevent)
+{
+  GdkMacosSurface *surface;
+  GdkEvent *event;
+  int x;
+  int y;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (nsevent != NULL);
+
+  if ((surface = find_surface_for_ns_event (self, nsevent, &x, &y)) &&
+      (event = fill_button_event (self, surface, nsevent, x, y)))
+    _gdk_windowing_got_event (GDK_DISPLAY (self),
+                              _gdk_event_queue_append (GDK_DISPLAY (self), event),
+                              event,
+                              _gdk_display_get_next_serial (GDK_DISPLAY (self)));
+}
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
new file mode 100644
index 0000000000..800696eb09
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -0,0 +1,1071 @@
+/*
+ * 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.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 <AppKit/AppKit.h>
+
+#import "GdkMacosWindow.h"
+
+#include "gdkdisplayprivate.h"
+#include "gdkeventsprivate.h"
+
+#include "gdkdisplaylinksource.h"
+#include "gdkmacosclipboard-private.h"
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosglcontext-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacosseat-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacosutils-private.h"
+
+/**
+ * SECTION:macos_interaction
+ * @Short_description: macOS backend-specific functions
+ * @Title: macOS Interaction
+ * @Include: gdk/macos/gdkmacos.h
+ *
+ * The functions in this section are specific to the GDK macOS backend.
+ * To use them, you need to include the `<gdk/macos/gdkmacos.h>` header and
+ * use the macOS-specific pkg-config `gtk4-macos` file to build your
+ * application.
+ *
+ * To make your code compile with other GDK backends, guard backend-specific
+ * calls by an ifdef as follows. Since GDK may be built with multiple
+ * backends, you should also check for the backend that is in use (e.g. by
+ * using the GDK_IS_MACOS_DISPLAY() macro).
+ * |[<!-- language="C" -->
+ * #ifdef GDK_WINDOWING_MACOS
+ *   if (GDK_IS_MACOS_DISPLAY (display))
+ *     {
+ *       // make macOS-specific calls here
+ *     }
+ *   else
+ * #endif
+ * #ifdef GDK_WINDOWING_X11
+ *   if (GDK_IS_X11_DISPLAY (display))
+ *     {
+ *       // make X11-specific calls here
+ *     }
+ *   else
+ * #endif
+ *   g_error ("Unsupported GDK backend");
+ * ]|
+ */
+
+G_DEFINE_TYPE (GdkMacosDisplay, gdk_macos_display, GDK_TYPE_DISPLAY)
+
+static GSource *event_source;
+
+static GdkMacosMonitor *
+get_monitor (GdkMacosDisplay *self,
+             guint            position)
+{
+  GdkMacosMonitor *monitor;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  /* Get the monitor but return a borrowed reference */
+  monitor = g_list_model_get_item (G_LIST_MODEL (self->monitors), position);
+  if (monitor != NULL)
+    g_object_unref (monitor);
+
+  return monitor;
+}
+
+static gboolean
+gdk_macos_display_get_setting (GdkDisplay  *display,
+                               const gchar *setting,
+                               GValue      *value)
+{
+  return _gdk_macos_display_get_setting (GDK_MACOS_DISPLAY (display), setting, value);
+}
+
+static GListModel *
+gdk_macos_display_get_monitors (GdkDisplay *display)
+{
+  return G_LIST_MODEL (GDK_MACOS_DISPLAY (display)->monitors);
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor_at_surface (GdkDisplay *display,
+                                          GdkSurface *surface)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+  CGDirectDisplayID screen_id;
+  guint n_monitors;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  screen_id = _gdk_macos_surface_get_screen_id (GDK_MACOS_SURFACE (surface));
+  n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+  for (guint i = 0; i < n_monitors; i++)
+    {
+      GdkMacosMonitor *monitor = get_monitor (self, i);
+
+      if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+        return GDK_MONITOR (monitor);
+    }
+
+  return GDK_MONITOR (get_monitor (self, 0));
+}
+
+static GdkMacosMonitor *
+gdk_macos_display_find_monitor (GdkMacosDisplay   *self,
+                                CGDirectDisplayID  screen_id)
+{
+  guint n_monitors;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+  for (guint i = 0; i < n_monitors; i++)
+    {
+      GdkMacosMonitor *monitor = get_monitor (self, i);
+
+      if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+        return monitor;
+    }
+
+  return NULL;
+}
+
+static void
+gdk_macos_display_update_bounds (GdkMacosDisplay *self)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  self->min_x = G_MAXINT;
+  self->min_y = G_MAXINT;
+
+  self->max_x = G_MININT;
+  self->max_y = G_MININT;
+
+  for (id obj in [NSScreen screens])
+    {
+      NSRect geom = [(NSScreen *)obj frame];
+
+      self->min_x = MIN (self->min_x, geom.origin.x);
+      self->min_y = MIN (self->min_y, geom.origin.y);
+      self->max_x = MAX (self->max_x, geom.origin.x + geom.size.width);
+      self->max_y = MAX (self->max_y, geom.origin.y + geom.size.height);
+    }
+
+  self->width = self->max_x - self->min_x;
+  self->height = self->max_y - self->min_y;
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef  center,
+                                       void                    *observer,
+                                       CFStringRef              name,
+                                       const void              *object,
+                                       CFDictionaryRef          userInfo)
+{
+  GdkMacosDisplay *self = observer;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  _gdk_macos_display_reload_monitors (self);
+
+  /* Now we need to update all our surface positions since they
+   * probably just changed origins. We ignore the popup surfaces
+   * since we can rely on the toplevel surfaces to handle that.
+   */
+  for (const GList *iter = _gdk_macos_display_get_surfaces (self);
+       iter != NULL;
+       iter = iter->next)
+    {
+      GdkMacosSurface *surface = iter->data;
+
+      g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+      if (GDK_IS_TOPLEVEL (surface))
+        _gdk_macos_surface_update_position (surface);
+    }
+}
+
+static void
+gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef  center,
+                                            void                    *observer,
+                                            CFStringRef              name,
+                                            const void              *object,
+                                            CFDictionaryRef          userInfo)
+{
+  GdkMacosDisplay *self = observer;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  _gdk_macos_display_reload_settings (self);
+}
+
+void
+_gdk_macos_display_reload_monitors (GdkMacosDisplay *self)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GArray *seen;
+  guint n_monitors;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  gdk_macos_display_update_bounds (self);
+
+  seen = g_array_new (FALSE, FALSE, sizeof (CGDirectDisplayID));
+
+  for (id obj in [NSScreen screens])
+    {
+      CGDirectDisplayID screen_id;
+      GdkMacosMonitor *monitor;
+
+      screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+      g_array_append_val (seen, screen_id);
+
+      if ((monitor = gdk_macos_display_find_monitor (self, screen_id)))
+        {
+          _gdk_macos_monitor_reconfigure (monitor);
+        }
+      else
+        {
+          monitor = _gdk_macos_monitor_new (self, screen_id);
+          g_list_store_append (self->monitors, monitor);
+          g_object_unref (monitor);
+        }
+    }
+
+  n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+  for (guint i = n_monitors; i > 0; i--)
+    {
+      GdkMacosMonitor *monitor = get_monitor (self, i - 1);
+      CGDirectDisplayID screen_id = _gdk_macos_monitor_get_screen_id (monitor);
+      gboolean found = FALSE;
+
+      for (guint j = 0; j < seen->len; j++)
+        {
+          if (screen_id == g_array_index (seen, CGDirectDisplayID, j))
+            {
+              found = TRUE;
+              break;
+            }
+        }
+
+      if (!found)
+        g_list_store_remove (self->monitors, i - 1);
+    }
+
+  g_array_unref (seen);
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_load_seat (GdkMacosDisplay *self)
+{
+  GdkSeat *seat;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  seat = _gdk_macos_seat_new (self);
+  gdk_display_add_seat (GDK_DISPLAY (self), seat);
+  g_object_unref (seat);
+}
+
+static gboolean
+gdk_macos_display_frame_cb (gpointer data)
+{
+  GdkMacosDisplay *self = data;
+  GdkDisplayLinkSource *source;
+  gint64 presentation_time;
+  gint64 now;
+  GList *iter;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  source = (GdkDisplayLinkSource *)self->frame_source;
+
+  presentation_time = source->presentation_time;
+  now = g_source_get_time ((GSource *)source);
+
+  iter = self->awaiting_frames.head;
+
+  while (iter != NULL)
+    {
+      GdkMacosSurface *surface = iter->data;
+
+      g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+      iter = iter->next;
+
+      _gdk_macos_display_remove_frame_callback (self, surface);
+      _gdk_macos_surface_thaw (surface,
+                               source->presentation_time,
+                               source->refresh_interval);
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gdk_macos_display_load_display_link (GdkMacosDisplay *self)
+{
+  self->frame_source = gdk_display_link_source_new ();
+  g_source_set_callback (self->frame_source,
+                         gdk_macos_display_frame_cb,
+                         self,
+                         NULL);
+  g_source_attach (self->frame_source, NULL);
+}
+
+static const gchar *
+gdk_macos_display_get_name (GdkDisplay *display)
+{
+  return GDK_MACOS_DISPLAY (display)->name;
+}
+
+static void
+gdk_macos_display_beep (GdkDisplay *display)
+{
+  NSBeep ();
+}
+
+static void
+gdk_macos_display_flush (GdkDisplay *display)
+{
+  /* Not Supported */
+}
+
+static void
+gdk_macos_display_sync (GdkDisplay *display)
+{
+  /* Not Supported */
+}
+
+static gulong
+gdk_macos_display_get_next_serial (GdkDisplay *display)
+{
+  return 0;
+}
+
+static gboolean
+gdk_macos_display_has_pending (GdkDisplay *display)
+{
+  return _gdk_event_queue_find_first (display) ||
+         _gdk_macos_event_source_check_pending ();
+}
+
+static void
+gdk_macos_display_notify_startup_complete (GdkDisplay  *display,
+                                           const gchar *startup_notification_id)
+{
+  /* Not Supported */
+}
+
+static void
+gdk_macos_display_queue_events (GdkDisplay *display)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+  NSEvent *nsevent;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  if ((nsevent = _gdk_macos_event_source_get_pending ()))
+    {
+      GdkEvent *event = _gdk_macos_display_translate (self, nsevent);
+
+      if (event != NULL)
+        _gdk_windowing_got_event (GDK_DISPLAY (self),
+                                  _gdk_event_queue_append (GDK_DISPLAY (self), event),
+                                  event,
+                                  0);
+      else
+        [NSApp sendEvent:nsevent];
+
+      [nsevent release];
+    }
+}
+
+static void
+_gdk_macos_display_surface_added (GdkMacosDisplay *self,
+                                  GdkMacosSurface *surface)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (!queue_contains (&self->sorted_surfaces, &surface->sorted));
+  g_assert (!queue_contains (&self->main_surfaces, &surface->main));
+  g_assert (!queue_contains (&self->awaiting_frames, &surface->frame));
+  g_assert (surface->sorted.data == surface);
+  g_assert (surface->main.data == surface);
+  g_assert (surface->frame.data == surface);
+
+  if (GDK_IS_TOPLEVEL (surface))
+    g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+
+  _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_removed (GdkMacosDisplay *self,
+                                    GdkMacosSurface *surface)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (self->keyboard_surface == surface)
+    _gdk_macos_display_surface_resigned_key (self, surface);
+
+  g_queue_unlink (&self->sorted_surfaces, &surface->sorted);
+
+  if (queue_contains (&self->main_surfaces, &surface->main))
+    _gdk_macos_display_surface_resigned_main (self, surface);
+
+  if (queue_contains (&self->awaiting_frames, &surface->frame))
+    g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+  g_return_if_fail (self->keyboard_surface != surface);
+}
+
+void
+_gdk_macos_display_surface_became_key (GdkMacosDisplay *self,
+                                       GdkMacosSurface *surface)
+{
+  GdkDevice *keyboard;
+  GdkEvent *event;
+  GdkSeat *seat;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+  g_return_if_fail (self->keyboard_surface == NULL);
+
+  self->keyboard_surface = surface;
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  keyboard = gdk_seat_get_keyboard (seat);
+  event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, TRUE);
+  _gdk_event_queue_append (GDK_DISPLAY (self), event);
+
+  /* We just became the active window.  Unlike X11, Mac OS X does
+   * not send us motion events while the window does not have focus
+   * ("is not key").  We send a dummy motion notify event now, so that
+   * everything in the window is set to correct state.
+   */
+  _gdk_macos_display_synthesize_motion (self, surface);
+}
+
+void
+_gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self,
+                                         GdkMacosSurface *surface)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (self->keyboard_surface == surface)
+    {
+      GdkDevice *keyboard;
+      GdkEvent *event;
+      GdkSeat *seat;
+
+      seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+      keyboard = gdk_seat_get_keyboard (seat);
+      event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, FALSE);
+      _gdk_event_queue_append (GDK_DISPLAY (self), event);
+    }
+
+  self->keyboard_surface = NULL;
+
+  _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_became_main (GdkMacosDisplay *self,
+                                        GdkMacosSurface *surface)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (queue_contains (&self->main_surfaces, &surface->main))
+    g_queue_unlink (&self->main_surfaces, &surface->main);
+
+  g_queue_push_head_link (&self->main_surfaces, &surface->main);
+
+  _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self,
+                                          GdkMacosSurface *surface)
+{
+  GdkMacosSurface *new_surface = NULL;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (queue_contains (&self->main_surfaces, &surface->main))
+    g_queue_unlink (&self->main_surfaces, &surface->main);
+
+  _gdk_macos_display_clear_sorting (self);
+
+  if (GDK_SURFACE (surface)->transient_for &&
+      gdk_surface_get_mapped (GDK_SURFACE (surface)->transient_for))
+    {
+      new_surface = GDK_MACOS_SURFACE (GDK_SURFACE (surface)->transient_for);
+    }
+  else
+    {
+      const GList *surfaces = _gdk_macos_display_get_surfaces (self);
+
+      for (const GList *iter = surfaces; iter; iter = iter->next)
+        {
+          GdkMacosSurface *item = iter->data;
+
+          g_assert (GDK_IS_MACOS_SURFACE (item));
+
+          if (item == surface)
+            continue;
+
+          if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (item)))
+            {
+              new_surface = item;
+              break;
+            }
+        }
+    }
+
+  if (new_surface != NULL)
+    {
+      NSWindow *nswindow = _gdk_macos_surface_get_native (new_surface);
+      [nswindow makeKeyAndOrderFront:nswindow];
+    }
+
+  _gdk_macos_display_clear_sorting (self);
+}
+
+static GdkSurface *
+gdk_macos_display_create_surface (GdkDisplay     *display,
+                                  GdkSurfaceType  surface_type,
+                                  GdkSurface     *parent,
+                                  int             x,
+                                  int             y,
+                                  int             width,
+                                  int             height)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+  GdkMacosSurface *surface;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+  surface = _gdk_macos_surface_new (self, surface_type, parent, x, y, width, height);
+
+  if (surface != NULL)
+    _gdk_macos_display_surface_added (self, surface);
+
+  return GDK_SURFACE (surface);
+}
+
+static GdkKeymap *
+gdk_macos_display_get_keymap (GdkDisplay *display)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  return GDK_KEYMAP (self->keymap);
+}
+
+static void
+gdk_macos_display_load_clipboard (GdkMacosDisplay *self)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  GDK_DISPLAY (self)->clipboard = _gdk_macos_clipboard_new (self);
+}
+
+static gboolean
+gdk_macos_display_make_gl_context_current (GdkDisplay   *display,
+                                           GdkGLContext *gl_context)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (GDK_IS_MACOS_GL_CONTEXT (gl_context));
+
+  return _gdk_macos_gl_context_make_current (GDK_MACOS_GL_CONTEXT (gl_context));
+}
+
+static void
+gdk_macos_display_finalize (GObject *object)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)object;
+
+  CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+                                      self,
+                                      CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+                                      NULL);
+
+  CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+                                      self,
+                                      CFSTR ("NSUserDefaultsDidChangeNotification"),
+                                      NULL);
+
+  g_clear_object (&GDK_DISPLAY (self)->clipboard);
+  g_clear_pointer (&self->frame_source, g_source_unref);
+  g_clear_object (&self->monitors);
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (gdk_macos_display_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_display_class_init (GdkMacosDisplayClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDisplayClass *display_class = GDK_DISPLAY_CLASS (klass);
+
+  object_class->finalize = gdk_macos_display_finalize;
+
+  display_class->cairo_context_type = GDK_TYPE_MACOS_CAIRO_CONTEXT;
+
+  display_class->beep = gdk_macos_display_beep;
+  display_class->create_surface = gdk_macos_display_create_surface;
+  display_class->flush = gdk_macos_display_flush;
+  display_class->get_keymap = gdk_macos_display_get_keymap;
+  display_class->get_monitors = gdk_macos_display_get_monitors;
+  display_class->get_monitor_at_surface = gdk_macos_display_get_monitor_at_surface;
+  display_class->get_next_serial = gdk_macos_display_get_next_serial;
+  display_class->get_name = gdk_macos_display_get_name;
+  display_class->get_setting = gdk_macos_display_get_setting;
+  display_class->has_pending = gdk_macos_display_has_pending;
+  display_class->make_gl_context_current = gdk_macos_display_make_gl_context_current;
+  display_class->notify_startup_complete = gdk_macos_display_notify_startup_complete;
+  display_class->queue_events = gdk_macos_display_queue_events;
+  display_class->sync = gdk_macos_display_sync;
+}
+
+static void
+gdk_macos_display_init (GdkMacosDisplay *self)
+{
+  self->monitors = g_list_store_new (GDK_TYPE_MONITOR);
+
+  gdk_display_set_composited (GDK_DISPLAY (self), TRUE);
+  gdk_display_set_input_shapes (GDK_DISPLAY (self), FALSE);
+  gdk_display_set_rgba (GDK_DISPLAY (self), TRUE);
+}
+
+GdkDisplay *
+_gdk_macos_display_open (const gchar *display_name)
+{
+  static GdkMacosDisplay *self;
+  ProcessSerialNumber psn = { 0, kCurrentProcess };
+
+  /* Until we can have multiple GdkMacosEventSource instances
+   * running concurrently, we can't exactly support multiple
+   * display connections. So just short-circuit if we already
+   * have one active.
+   */
+  if (self != NULL)
+    return NULL;
+
+  GDK_NOTE (MISC, g_message ("opening display %s", display_name ? display_name : ""));
+
+  /* Make the current process a foreground application, i.e. an app
+   * with a user interface, in case we're not running from a .app bundle
+   */
+  TransformProcessType (&psn, kProcessTransformToForegroundApplication);
+
+  [NSApplication sharedApplication];
+
+  self = g_object_new (GDK_TYPE_MACOS_DISPLAY, NULL);
+  self->name = g_strdup (display_name);
+  self->keymap = _gdk_macos_keymap_new (self);
+
+  gdk_macos_display_load_seat (self);
+  gdk_macos_display_load_clipboard (self);
+
+  /* Load CVDisplayLink before monitors to access refresh rates */
+  gdk_macos_display_load_display_link (self);
+  _gdk_macos_display_reload_monitors (self);
+
+  CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (),
+                                   self,
+                                   gdk_macos_display_monitors_changed_cb,
+                                   CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+                                   NULL,
+                                   CFNotificationSuspensionBehaviorDeliverImmediately);
+
+  CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+                                   self,
+                                   gdk_macos_display_user_defaults_changed_cb,
+                                   CFSTR ("NSUserDefaultsDidChangeNotification"),
+                                   NULL,
+                                   CFNotificationSuspensionBehaviorDeliverImmediately);
+
+  if (event_source == NULL)
+    {
+      event_source = _gdk_macos_event_source_new (self);
+      g_source_attach (event_source, NULL);
+    }
+
+  g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+  gdk_display_emit_opened (GDK_DISPLAY (self));
+
+  return GDK_DISPLAY (self);
+}
+
+void
+_gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
+                                      int              x,
+                                      int              y,
+                                      int             *out_x,
+                                      int             *out_y)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  if (out_y)
+    *out_y = self->height - y + self->min_y;
+
+  if (out_x)
+    *out_x = x + self->min_x;
+}
+
+void
+_gdk_macos_display_from_display_coords (GdkMacosDisplay *self,
+                                        int              x,
+                                        int              y,
+                                        int             *out_x,
+                                        int             *out_y)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  if (out_y != NULL)
+    *out_y = self->height - y + self->min_y;
+
+  if (out_x != NULL)
+    *out_x = x - self->min_x;
+}
+
+GdkMonitor *
+_gdk_macos_display_get_monitor_at_coords (GdkMacosDisplay *self,
+                                          int              x,
+                                          int              y)
+{
+  guint n_monitors;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+  n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+  for (guint i = 0; i < n_monitors; i++)
+    {
+      GdkMacosMonitor *monitor = get_monitor (self, i);
+
+      if (gdk_rectangle_contains_point (&GDK_MONITOR (monitor)->geometry, x, y))
+        return GDK_MONITOR (monitor);
+    }
+
+  return NULL;
+}
+
+GdkMonitor *
+_gdk_macos_display_get_monitor_at_display_coords (GdkMacosDisplay *self,
+                                                  int              x,
+                                                  int              y)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+  _gdk_macos_display_from_display_coords (self, x, y, &x, &y);
+
+  return _gdk_macos_display_get_monitor_at_coords (self, x, y);
+}
+
+NSScreen *
+_gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
+                                                 int              x,
+                                                 int              y)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSArray *screens;
+  NSScreen *screen = NULL;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+  screens = [NSScreen screens];
+
+  for (id obj in screens)
+    {
+      NSRect geom = [obj frame];
+
+      if (x >= geom.origin.x && x <= geom.origin.x + geom.size.width &&
+          y >= geom.origin.y && y <= geom.origin.y + geom.size.height)
+        {
+          screen = obj;
+          break;
+        }
+    }
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return screen;
+}
+
+void
+_gdk_macos_display_break_all_grabs (GdkMacosDisplay *self,
+                                    guint32          time)
+{
+  GdkDevice *devices[2];
+  GdkSeat *seat;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+  devices[0] = gdk_seat_get_keyboard (seat);
+  devices[1] = gdk_seat_get_pointer (seat);
+
+  for (guint i = 0; i < G_N_ELEMENTS (devices); i++)
+    {
+      GdkDevice *device = devices[i];
+      GdkDeviceGrabInfo *grab;
+
+      grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), device);
+
+      if (grab != NULL)
+        {
+          GdkEvent *event;
+          GList *node;
+
+          event = gdk_grab_broken_event_new (grab->surface,
+                                             device,
+                                             NULL,
+                                             grab->surface,
+                                             TRUE);
+          node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+          _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+        }
+    }
+}
+
+void
+_gdk_macos_display_queue_events (GdkMacosDisplay *self)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  gdk_macos_display_queue_events (GDK_DISPLAY (self));
+}
+
+static GdkMacosSurface *
+_gdk_macos_display_get_surface_at_coords (GdkMacosDisplay *self,
+                                          int              x,
+                                          int              y,
+                                          int             *surface_x,
+                                          int             *surface_y)
+{
+  const GList *surfaces;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+  g_return_val_if_fail (surface_x != NULL, NULL);
+  g_return_val_if_fail (surface_y != NULL, NULL);
+
+  surfaces = _gdk_macos_display_get_surfaces (self);
+
+  for (const GList *iter = surfaces; iter; iter = iter->next)
+    {
+      GdkSurface *surface = iter->data;
+      NSWindow *nswindow;
+
+      g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+      if (!gdk_surface_get_mapped (surface))
+        continue;
+
+      nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+      if (x >= GDK_MACOS_SURFACE (surface)->root_x &&
+          y >= GDK_MACOS_SURFACE (surface)->root_y &&
+          x <= (GDK_MACOS_SURFACE (surface)->root_x + surface->width) &&
+          y <= (GDK_MACOS_SURFACE (surface)->root_y + surface->height))
+        {
+          *surface_x = x - GDK_MACOS_SURFACE (surface)->root_x;
+          *surface_y = y - GDK_MACOS_SURFACE (surface)->root_y;
+
+          return GDK_MACOS_SURFACE (surface);
+        }
+    }
+
+  *surface_x = 0;
+  *surface_y = 0;
+
+  return NULL;
+}
+
+GdkMacosSurface *
+_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self,
+                                                  double           x,
+                                                  double           y,
+                                                  int             *surface_x,
+                                                  int             *surface_y)
+{
+  int x_gdk;
+  int y_gdk;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+  g_return_val_if_fail (surface_x != NULL, NULL);
+  g_return_val_if_fail (surface_y != NULL, NULL);
+
+  _gdk_macos_display_from_display_coords (self, x, y, &x_gdk, &y_gdk);
+
+  return _gdk_macos_display_get_surface_at_coords (self, x_gdk, y_gdk, surface_x, surface_y);
+}
+
+void
+_gdk_macos_display_add_frame_callback (GdkMacosDisplay *self,
+                                       GdkMacosSurface *surface)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (!queue_contains (&self->awaiting_frames, &surface->frame))
+    {
+      g_queue_push_tail_link (&self->awaiting_frames, &surface->frame);
+
+      if (self->awaiting_frames.length == 1)
+        gdk_display_link_source_unpause ((GdkDisplayLinkSource *)self->frame_source);
+    }
+}
+
+void
+_gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self,
+                                          GdkMacosSurface *surface)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  if (queue_contains (&self->awaiting_frames, &surface->frame))
+    {
+      g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+      if (self->awaiting_frames.length == 0)
+        gdk_display_link_source_pause ((GdkDisplayLinkSource *)self->frame_source);
+    }
+}
+
+NSWindow *
+_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self,
+                                              int             *x,
+                                              int             *y)
+{
+  GdkMacosSurface *surface;
+  NSPoint point;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  point = [NSEvent mouseLocation];
+
+  surface = _gdk_macos_display_get_surface_at_display_coords (self, point.x, point.y, x, y);
+  if (surface != NULL)
+    return _gdk_macos_surface_get_native (surface);
+
+  return NULL;
+}
+
+int
+_gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), 60 * 1000);
+
+  if (self->frame_source == NULL)
+    return 60 * 1000;
+
+  return ((GdkDisplayLinkSource *)self->frame_source)->refresh_rate;
+}
+
+void
+_gdk_macos_display_clear_sorting (GdkMacosDisplay *self)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  self->sorted_surfaces.head = NULL;
+  self->sorted_surfaces.tail = NULL;
+  self->sorted_surfaces.length = 0;
+}
+
+const GList *
+_gdk_macos_display_get_surfaces (GdkMacosDisplay *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+  if (self->sorted_surfaces.length == 0)
+    {
+      GDK_BEGIN_MACOS_ALLOC_POOL;
+
+      NSArray *array = [NSApp orderedWindows];
+      GQueue sorted = G_QUEUE_INIT;
+
+      for (id obj in array)
+        {
+          NSWindow *nswindow = (NSWindow *)obj;
+          GdkMacosSurface *surface;
+
+          if (!GDK_IS_MACOS_WINDOW (nswindow))
+            continue;
+
+          surface = [(GdkMacosWindow *)nswindow gdkSurface];
+
+          surface->sorted.prev = NULL;
+          surface->sorted.next = NULL;
+
+          g_queue_push_tail_link (&sorted, &surface->sorted);
+        }
+
+      self->sorted_surfaces = sorted;
+
+      /* We don't get notification of clipboard changes from the system so we
+       * instead update it every time the foreground changes (and thusly
+       * rebuild the sorted list).  Things could change other ways, such as
+       * with scripts, but that is currently out of scope for us.
+       */
+      _gdk_macos_clipboard_check_externally_modified (
+        GDK_MACOS_CLIPBOARD (GDK_DISPLAY (self)->clipboard));
+
+      GDK_END_MACOS_ALLOC_POOL;
+    }
+
+  return self->sorted_surfaces.head;
+}
+
+void
+_gdk_macos_display_warp_pointer (GdkMacosDisplay *self,
+                                 int              x,
+                                 int              y)
+{
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  _gdk_macos_display_to_display_coords (self, x, y, &x, &y);
+
+  CGWarpMouseCursorPosition ((CGPoint) { x, y });
+}
diff --git a/gdk/macos/gdkmacosdisplay.h b/gdk/macos/gdkmacosdisplay.h
new file mode 100644
index 0000000000..7c5730d0c7
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.h
@@ -0,0 +1,47 @@
+/*
+ * 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.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_DISPLAY_H__
+#define __GDK_MACOS_DISPLAY_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#ifdef GTK_COMPILATION
+typedef struct _GdkMacosDisplay GdkMacosDisplay;
+#else
+typedef GdkDisplay GdkMacosDisplay;
+#endif
+typedef struct _GdkMacosDisplayClass GdkMacosDisplayClass;
+
+#define GDK_TYPE_MACOS_DISPLAY       (gdk_macos_display_get_type())
+#define GDK_MACOS_DISPLAY(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DISPLAY, 
GdkMacosDisplay))
+#define GDK_IS_MACOS_DISPLAY(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DISPLAY))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_display_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_H__ */
diff --git a/gdk/macos/gdkmacosdrag-private.h b/gdk/macos/gdkmacosdrag-private.h
new file mode 100644
index 0000000000..98075f27ef
--- /dev/null
+++ b/gdk/macos/gdkmacosdrag-private.h
@@ -0,0 +1,70 @@
+/*
+ * 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.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_DRAG_PRIVATE_H__
+#define __GDK_MACOS_DRAG_PRIVATE_H__
+
+#include "gdkdragprivate.h"
+
+#include "gdkmacosdragsurface-private.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_DRAG            (gdk_macos_drag_get_type ())
+#define GDK_MACOS_DRAG(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DRAG, 
GdkMacosDrag))
+#define GDK_MACOS_DRAG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_MACOS_DRAG, 
GdkMacosDragClass))
+#define GDK_IS_MACOS_DRAG(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DRAG))
+#define GDK_IS_MACOS_DRAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_MACOS_DRAG))
+#define GDK_MACOS_DRAG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_MACOS_DRAG, 
GdkMacosDragClass))
+
+typedef struct _GdkMacosDrag GdkMacosDrag;
+typedef struct _GdkMacosDragClass GdkMacosDragClass;
+
+struct _GdkMacosDrag
+{
+  GdkDrag parent_instance;
+
+  GdkMacosDragSurface *drag_surface;
+  GdkSeat *drag_seat;
+  GdkCursor *cursor;
+
+  int hot_x;
+  int hot_y;
+
+  int last_x;
+  int last_y;
+
+  int start_x;
+  int start_y;
+
+  guint did_update : 1;
+  guint cancelled : 1;
+};
+
+struct _GdkMacosDragClass
+{
+  GdkDragClass parent_class;
+};
+
+GType    gdk_macos_drag_get_type (void) G_GNUC_CONST;
+gboolean _gdk_macos_drag_begin   (GdkMacosDrag *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DRAG_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdrag.c b/gdk/macos/gdkmacosdrag.c
new file mode 100644
index 0000000000..de1324c674
--- /dev/null
+++ b/gdk/macos/gdkmacosdrag.c
@@ -0,0 +1,617 @@
+/*
+ * 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.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 "gdkdeviceprivate.h"
+#include "gdkintl.h"
+
+#include "gdkmacoscursor-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdrag-private.h"
+#include "gdkmacosdragsurface-private.h"
+
+#define BIG_STEP 20
+#define SMALL_STEP 1
+#define ANIM_TIME 500000 /* .5 seconds */
+
+typedef struct
+{
+  GdkMacosDrag  *drag;
+  GdkFrameClock *frame_clock;
+  gint64         start_time;
+} GdkMacosZoomback;
+
+G_DEFINE_TYPE (GdkMacosDrag, gdk_macos_drag, GDK_TYPE_DRAG)
+
+enum {
+  PROP_0,
+  PROP_DRAG_SURFACE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static double
+ease_out_cubic (double t)
+{
+  double p = t - 1;
+  return p * p * p + 1;
+}
+
+static void
+gdk_macos_zoomback_destroy (GdkMacosZoomback *zb)
+{
+  gdk_surface_hide (GDK_SURFACE (zb->drag->drag_surface));
+  g_clear_object (&zb->drag);
+  g_slice_free (GdkMacosZoomback, zb);
+}
+
+static gboolean
+gdk_macos_zoomback_timeout (gpointer data)
+{
+  GdkMacosZoomback *zb = data;
+  GdkFrameClock *frame_clock;
+  GdkMacosDrag *drag;
+  gint64 current_time;
+  double f;
+  double t;
+
+  g_assert (zb != NULL);
+  g_assert (GDK_IS_MACOS_DRAG (zb->drag));
+
+  drag = zb->drag;
+  frame_clock = zb->frame_clock;
+
+  if (!frame_clock)
+    return G_SOURCE_REMOVE;
+
+  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;
+
+  t = ease_out_cubic (f);
+
+  _gdk_macos_surface_move (GDK_MACOS_SURFACE (drag->drag_surface),
+                           (drag->last_x - drag->hot_x) +
+                           (drag->start_x - drag->last_x) * t,
+                           (drag->last_y - drag->hot_y) +
+                           (drag->start_y - drag->last_y) * t);
+  _gdk_macos_surface_set_opacity (GDK_MACOS_SURFACE (drag->drag_surface), 1.0 - f);
+
+  /* Make sure we're topmost */
+  _gdk_macos_surface_show (GDK_MACOS_SURFACE (drag->drag_surface));
+
+  return G_SOURCE_CONTINUE;
+}
+
+static GdkSurface *
+gdk_macos_drag_get_drag_surface (GdkDrag *drag)
+{
+  return GDK_SURFACE (GDK_MACOS_DRAG (drag)->drag_surface);
+}
+
+static void
+gdk_macos_drag_set_hotspot (GdkDrag *drag,
+                            int      hot_x,
+                            int      hot_y)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)drag;
+  int change_x;
+  int change_y;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  change_x = hot_x - self->hot_x;
+  change_y = hot_y - self->hot_y;
+
+  self->hot_x = hot_x;
+  self->hot_y = hot_y;
+
+  if (change_x || change_y)
+    _gdk_macos_surface_move (GDK_MACOS_SURFACE (self->drag_surface),
+                             GDK_SURFACE (self->drag_surface)->x + change_x,
+                             GDK_SURFACE (self->drag_surface)->y + change_y);
+}
+
+static void
+gdk_macos_drag_drop_done (GdkDrag  *drag,
+                          gboolean  success)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)drag;
+  GdkMacosZoomback *zb;
+  guint id;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  if (success)
+    {
+      gdk_surface_hide (GDK_SURFACE (self->drag_surface));
+      g_object_unref (drag);
+      return;
+    }
+
+  /* Apple HIG suggests doing a "zoomback" animation of the surface back
+   * towards the original position.
+   */
+  zb = g_slice_new0 (GdkMacosZoomback);
+  zb->drag = g_object_ref (self);
+  zb->frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self->drag_surface));
+  zb->start_time = gdk_frame_clock_get_frame_time (zb->frame_clock);
+
+  id = g_timeout_add_full (G_PRIORITY_DEFAULT, 17,
+                           gdk_macos_zoomback_timeout,
+                           zb,
+                           (GDestroyNotify) gdk_macos_zoomback_destroy);
+  g_source_set_name_by_id (id, "[gtk] gdk_macos_zoomback_timeout");
+  g_object_unref (drag);
+}
+
+static void
+gdk_macos_drag_set_cursor (GdkDrag   *drag,
+                           GdkCursor *cursor)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)drag;
+  NSCursor *nscursor;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+  g_assert (!cursor || GDK_IS_CURSOR (cursor));
+
+  g_set_object (&self->cursor, cursor);
+
+  nscursor = _gdk_macos_cursor_get_ns_cursor (cursor);
+
+  if (nscursor != NULL)
+    [nscursor set];
+}
+
+static gboolean
+drag_grab (GdkMacosDrag *self)
+{
+  GdkSeat *seat;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  seat = gdk_device_get_seat (gdk_drag_get_device (GDK_DRAG (self)));
+
+  if (gdk_seat_grab (seat,
+                     GDK_SURFACE (self->drag_surface),
+                     GDK_SEAT_CAPABILITY_ALL_POINTING,
+                     FALSE,
+                     self->cursor,
+                     NULL,
+                     NULL,
+                     NULL) != GDK_GRAB_SUCCESS)
+    return FALSE;
+
+  g_set_object (&self->drag_seat, seat);
+
+  return TRUE;
+}
+
+static void
+drag_ungrab (GdkMacosDrag *self)
+{
+  GdkDisplay *display;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  display = gdk_drag_get_display (GDK_DRAG (self));
+  _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), GDK_CURRENT_TIME);
+}
+
+static void
+gdk_macos_drag_cancel (GdkDrag             *drag,
+                       GdkDragCancelReason  reason)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)drag;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  if (self->cancelled)
+    return;
+
+  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));
+
+  drag_ungrab (self);
+  g_signal_emit_by_name (drag, "dnd-finished");
+  gdk_drag_drop_done (drag, TRUE);
+}
+
+static void
+gdk_drag_get_current_actions (GdkModifierType  state,
+                              gint             button,
+                              GdkDragAction    actions,
+                              GdkDragAction   *suggested_action,
+                              GdkDragAction   *possible_actions)
+{
+  *suggested_action = 0;
+  *possible_actions = 0;
+
+  if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
+    {
+      *suggested_action = GDK_ACTION_ASK;
+      *possible_actions = actions;
+    }
+  else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
+    {
+      if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
+        {
+          if (actions & GDK_ACTION_LINK)
+            {
+              *suggested_action = GDK_ACTION_LINK;
+              *possible_actions = GDK_ACTION_LINK;
+            }
+        }
+      else if (state & GDK_CONTROL_MASK)
+        {
+          if (actions & GDK_ACTION_COPY)
+            {
+              *suggested_action = GDK_ACTION_COPY;
+              *possible_actions = GDK_ACTION_COPY;
+            }
+        }
+      else
+        {
+          if (actions & GDK_ACTION_MOVE)
+            {
+              *suggested_action = GDK_ACTION_MOVE;
+              *possible_actions = GDK_ACTION_MOVE;
+            }
+        }
+    }
+  else
+    {
+      *possible_actions = actions;
+
+      if ((state & (GDK_ALT_MASK)) && (actions & GDK_ACTION_ASK))
+        *suggested_action = GDK_ACTION_ASK;
+      else if (actions & GDK_ACTION_COPY)
+        *suggested_action =  GDK_ACTION_COPY;
+      else if (actions & GDK_ACTION_MOVE)
+        *suggested_action = GDK_ACTION_MOVE;
+      else if (actions & GDK_ACTION_LINK)
+        *suggested_action = GDK_ACTION_LINK;
+    }
+}
+
+static void
+gdk_drag_update (GdkDrag         *drag,
+                 gdouble          x_root,
+                 gdouble          y_root,
+                 GdkModifierType  mods,
+                 guint32          evtime)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)drag;
+  GdkDragAction suggested_action;
+  GdkDragAction possible_actions;
+
+  g_assert (GDK_IS_MACOS_DRAG (self));
+
+  self->last_x = x_root;
+  self->last_y = y_root;
+
+  gdk_drag_get_current_actions (mods,
+                                GDK_BUTTON_PRIMARY,
+                                gdk_drag_get_actions (drag),
+                                &suggested_action,
+                                &possible_actions);
+
+  _gdk_macos_drag_surface_drag_motion (self->drag_surface,
+                                       x_root - self->hot_x,
+                                       y_root - self->hot_y,
+                                       suggested_action,
+                                       possible_actions,
+                                       evtime);
+
+  if (!self->did_update)
+    {
+      self->start_x = self->last_x;
+      self->start_y = self->last_y;
+      self->did_update = TRUE;
+    }
+}
+
+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;
+  gint dx, dy;
+
+  dx = dy = 0;
+  state = gdk_event_get_modifier_state (event);
+  pointer = gdk_device_get_associated_device (gdk_event_get_device (event));
+
+  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_device_query_state (pointer, NULL, NULL, NULL, NULL, &state);
+
+  if (dx != 0 || dy != 0)
+    {
+      GdkDisplay *display = gdk_event_get_display ((GdkEvent *)event);
+
+      self->last_x += dx;
+      self->last_y += dy;
+
+      _gdk_macos_display_warp_pointer (GDK_MACOS_DISPLAY (display),
+                                       self->last_x,
+                                       self->last_y);
+    }
+
+  gdk_drag_update (drag,
+                   self->last_x, self->last_y,
+                   state,
+                   gdk_event_get_time (event));
+
+  return TRUE;
+}
+
+static gboolean
+gdk_macos_drag_handle_event (GdkDrag  *drag,
+                             GdkEvent *event)
+{
+  g_assert (GDK_IS_MACOS_DRAG (drag));
+  g_assert (event != NULL);
+
+  switch ((guint) event->event_type)
+    {
+    case GDK_MOTION_NOTIFY:
+      return gdk_dnd_handle_motion_event (drag, event);
+
+    case GDK_BUTTON_RELEASE:
+      return gdk_dnd_handle_button_event (drag, event);
+
+    case GDK_KEY_PRESS:
+    case GDK_KEY_RELEASE:
+      return gdk_dnd_handle_key_event (drag, event);
+
+    case GDK_GRAB_BROKEN:
+      return gdk_dnd_handle_grab_broken_event (drag, event);
+
+    default:
+      return FALSE;
+    }
+}
+
+static void
+gdk_macos_drag_finalize (GObject *object)
+{
+  GdkMacosDrag *self = (GdkMacosDrag *)object;
+  GdkMacosDragSurface *drag_surface = g_steal_pointer (&self->drag_surface);
+
+  g_clear_object (&self->cursor);
+  g_clear_object (&self->drag_seat);
+
+  G_OBJECT_CLASS (gdk_macos_drag_parent_class)->finalize (object);
+
+  if (drag_surface)
+    gdk_surface_destroy (GDK_SURFACE (drag_surface));
+}
+
+static void
+gdk_macos_drag_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GdkMacosDrag *self = GDK_MACOS_DRAG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRAG_SURFACE:
+      g_value_set_object (value, self->drag_surface);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gdk_macos_drag_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GdkMacosDrag *self = GDK_MACOS_DRAG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRAG_SURFACE:
+      self->drag_surface = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gdk_macos_drag_class_init (GdkMacosDragClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDragClass *drag_class = GDK_DRAG_CLASS (klass);
+
+  object_class->finalize = gdk_macos_drag_finalize;
+  object_class->get_property = gdk_macos_drag_get_property;
+  object_class->set_property = gdk_macos_drag_set_property;
+
+  drag_class->get_drag_surface = gdk_macos_drag_get_drag_surface;
+  drag_class->set_hotspot = gdk_macos_drag_set_hotspot;
+  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;
+
+  properties [PROP_DRAG_SURFACE] =
+    g_param_spec_object ("drag-surface",
+                         P_("Drag Surface"),
+                         P_("Drag Surface"),
+                         GDK_TYPE_MACOS_DRAG_SURFACE,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gdk_macos_drag_init (GdkMacosDrag *self)
+{
+}
+
+gboolean
+_gdk_macos_drag_begin (GdkMacosDrag *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_DRAG (self), FALSE);
+
+  _gdk_macos_surface_show (GDK_MACOS_SURFACE (self->drag_surface));
+
+  return drag_grab (self);
+}
diff --git a/gdk/macos/gdkmacosdragsurface-private.h b/gdk/macos/gdkmacosdragsurface-private.h
new file mode 100644
index 0000000000..cf7408f308
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface-private.h
@@ -0,0 +1,50 @@
+/*
+ * 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.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_DRAG_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDragSurface      GdkMacosDragSurface;
+typedef struct _GdkMacosDragSurfaceClass GdkMacosDragSurfaceClass;
+
+#define GDK_TYPE_MACOS_DRAG_SURFACE       (_gdk_macos_drag_surface_get_type())
+#define GDK_MACOS_DRAG_SURFACE(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GDK_TYPE_MACOS_DRAG_SURFACE, GdkMacosDragSurface))
+#define GDK_IS_MACOS_DRAG_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GDK_TYPE_MACOS_DRAG_SURFACE))
+
+GType            _gdk_macos_drag_surface_get_type    (void);
+GdkMacosSurface *_gdk_macos_drag_surface_new         (GdkMacosDisplay *display,
+                                                      GdkFrameClock   *frame_clock,
+                                                      int              x,
+                                                      int              y,
+                                                      int              width,
+                                                      int              height);
+void             _gdk_macos_drag_surface_drag_motion (GdkMacosDragSurface *self,
+                                                      int                  x_root,
+                                                      int                  y_root,
+                                                      GdkDragAction        suggested_action,
+                                                      GdkDragAction        possible_actions,
+                                                      guint32              evtime);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdragsurface.c b/gdk/macos/gdkmacosdragsurface.c
new file mode 100644
index 0000000000..2b03583604
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface.c
@@ -0,0 +1,138 @@
+/*
+ * 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.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 "gdkdragsurfaceprivate.h"
+
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosDragSurface
+{
+  GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosDragSurfaceClass
+{
+  GdkMacosSurfaceClass parent_instance;
+};
+
+static gboolean
+_gdk_macos_drag_surface_present (GdkDragSurface *surface,
+                                 int             width,
+                                 int             height)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  _gdk_macos_surface_move_resize (GDK_MACOS_SURFACE (surface),
+                                  -1, -1,
+                                  width, height);
+
+  if (!GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface)))
+    _gdk_macos_surface_show (GDK_MACOS_SURFACE (surface));
+
+  return GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface));
+}
+
+static void
+drag_surface_iface_init (GdkDragSurfaceInterface *iface)
+{
+  iface->present = _gdk_macos_drag_surface_present;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosDragSurface, _gdk_macos_drag_surface, GDK_TYPE_MACOS_SURFACE,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_DRAG_SURFACE, drag_surface_iface_init))
+
+static void
+_gdk_macos_drag_surface_class_init (GdkMacosDragSurfaceClass *klass)
+{
+}
+
+static void
+_gdk_macos_drag_surface_init (GdkMacosDragSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+                             GdkFrameClock   *frame_clock,
+                             int              x,
+                             int              y,
+                             int              width,
+                             int              height)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosWindow *window;
+  GdkMacosSurface *self;
+  NSScreen *screen;
+  NSUInteger style_mask;
+  NSRect content_rect;
+  NSRect screen_rect;
+  int nx;
+  int ny;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+  g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+
+  style_mask = NSWindowStyleMaskBorderless;
+
+  _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+  screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+  screen_rect = [screen frame];
+  nx -= screen_rect.origin.x;
+  ny -= screen_rect.origin.y;
+  content_rect = NSMakeRect (nx, ny - height, width, height);
+
+  window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+                                             styleMask:style_mask
+                                               backing:NSBackingStoreBuffered
+                                                 defer:NO
+                                                screen:screen];
+
+  [window setOpaque:NO];
+  [window setBackgroundColor:[NSColor clearColor]];
+  [window setDecorated:NO];
+
+  self = g_object_new (GDK_TYPE_MACOS_DRAG_SURFACE,
+                       "display", display,
+                       "frame-clock", frame_clock,
+                       "native", window,
+                       NULL);
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_drag_surface_drag_motion (GdkMacosDragSurface *self,
+                                     int                  x_root,
+                                     int                  y_root,
+                                     GdkDragAction        suggested_action,
+                                     GdkDragAction        possible_actions,
+                                     guint32              evtime)
+{
+  g_return_if_fail (GDK_IS_MACOS_DRAG_SURFACE (self));
+
+  _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x_root, y_root);
+}
diff --git a/gdk/macos/gdkmacoseventsource-private.h b/gdk/macos/gdkmacoseventsource-private.h
new file mode 100644
index 0000000000..09853a18ac
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource-private.h
@@ -0,0 +1,40 @@
+/*
+ * 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.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_EVENT_SOURCE_PRIVATE_H__
+#define __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP,
+} GdkMacosEventSubType;
+
+GSource  *_gdk_macos_event_source_new           (GdkMacosDisplay *display);
+NSEvent  *_gdk_macos_event_source_get_pending   (void);
+gboolean  _gdk_macos_event_source_check_pending (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoseventsource.c b/gdk/macos/gdkmacoseventsource.c
new file mode 100644
index 0000000000..f173433de4
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource.c
@@ -0,0 +1,1094 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "gdkdisplayprivate.h"
+#include "gdkinternals.h"
+
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-private.h"
+
+/*
+ * This file implementations integration between the GLib main loop and
+ * the native system of the Core Foundation run loop and Cocoa event
+ * handling. There are basically two different cases that we need to
+ * handle: either the GLib main loop is in control (the application
+ * has called gtk_main(), or is otherwise iterating the main loop), or
+ * CFRunLoop is in control (we are in a modal operation such as window
+ * resizing or drag-and-drop.)
+ *
+ * When the GLib main loop is in control we integrate in native event
+ * handling in two ways: first we add a GSource that handles checking
+ * whether there are native events available, translating native events
+ * to GDK events, and dispatching GDK events. Second we replace the
+ * "poll function" of the GLib main loop with our own version that knows
+ * how to wait for both the file descriptors and timeouts that GLib is
+ * interested in and also for incoming native events.
+ *
+ * When CFRunLoop is in control, we integrate in GLib main loop handling
+ * by adding a "run loop observer" that gives us notification at various
+ * points in the run loop cycle. We map these points onto the corresponding
+ * stages of the GLib main loop (prepare, check, dispatch), and make the
+ * appropriate calls into GLib.
+ *
+ * Both cases share a single problem: the OS X API’s don’t allow us to
+ * wait simultaneously for file descriptors and for events. So when we
+ * need to do a blocking wait that includes file descriptor activity, we
+ * push the actual work of calling select() to a helper thread (the
+ * "select thread") and wait for native events in the main thread.
+ *
+ * The main known limitation of this code is that if a callback is triggered
+ * via the OS X run loop while we are "polling" (in either case described
+ * above), iteration of the GLib main loop is not possible from within
+ * that callback. If the programmer tries to do so explicitly, then they
+ * will get a warning from GLib "main loop already active in another thread".
+ */
+
+/******* State for run loop iteration *******/
+
+/* Count of number of times we've gotten an "Entry" notification for
+ * our run loop observer.
+ */
+static int current_loop_level = 0;
+
+/* Run loop level at which we acquired ownership of the GLib main
+ * loop. See note in run_loop_entry(). -1 means that we don’t have
+ * ownership
+ */
+static int acquired_loop_level = -1;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * whether we we need to call select_thread_collect_poll()
+ */
+static gboolean run_loop_polling_async = FALSE;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * max_prioritiy to pass to g_main_loop_check()
+ */
+static gint run_loop_max_priority;
+
+/* Timer that we've added to wake up the run loop when a GLib timeout
+ */
+static CFRunLoopTimerRef run_loop_timer = NULL;
+
+/* These are the file descriptors that are we are polling out of
+ * the run loop. (We keep the array around and reuse it to avoid
+ * constant allocations.)
+ */
+#define RUN_LOOP_POLLFDS_INITIAL_SIZE 16
+static GPollFD *run_loop_pollfds;
+static guint run_loop_pollfds_size; /* Allocated size of the array */
+static guint run_loop_n_pollfds;    /* Number of file descriptors in the array */
+
+/******* Other global variables *******/
+
+/* Since we count on replacing the GLib main loop poll function as our
+ * method of integrating Cocoa event handling into the GLib main loop
+ * we need to make sure that the poll function is always called even
+ * when there are no file descriptors that need to be polled. To do
+ * this, we add a dummy GPollFD to our event source with a file
+ * descriptor of “-1”. Then any time that GLib is polling the event
+ * source, it will call our poll function.
+ */
+static GPollFD event_poll_fd;
+
+/* Current NSEvents that we've gotten from Cocoa but haven't yet converted
+ * to GdkEvents. We wait until our dispatch() function to do the conversion
+ * since the conversion can conceivably cause signals to be emmitted
+ * or other things that shouldn’t happen inside a poll function.
+ */
+static GQueue *current_events;
+
+/* The default poll function for GLib; we replace this with our own
+ * Cocoa-aware version and then call the old version to do actual
+ * file descriptor polling. There’s no actual need to chain to the
+ * old one; we could reimplement the same functionality from scratch,
+ * but since the default implementation does the right thing, why
+ * bother.
+ */
+static GPollFunc old_poll_func;
+
+/* Reference to the run loop of the main thread. (There is a unique
+ * CFRunLoop per thread.)
+ */
+static CFRunLoopRef main_thread_run_loop;
+
+/* Normally the Cocoa main loop maintains an NSAutoReleasePool and frees
+ * it on every iteration. Since we are replacing the main loop we have
+ * to provide this functionality ourself. We free and replace the
+ * auto-release pool in our sources prepare() function.
+ */
+static NSAutoreleasePool *autorelease_pool;
+
+/* Flag when we've called nextEventMatchingMask ourself; this triggers
+ * a run loop iteration, so we need to detect that and avoid triggering
+ * our "run the GLib main looop while the run loop is active machinery.
+ */
+static gint getting_events = 0;
+
+/************************************************************
+ *********              Select Thread               *********
+ ************************************************************/
+
+/* The states in our state machine, see comments in select_thread_func()
+ * for descriptiions of each state
+ */
+typedef enum {
+  BEFORE_START,
+  WAITING,
+  POLLING_QUEUED,
+  POLLING_RESTART,
+  POLLING_DESCRIPTORS,
+} SelectThreadState;
+
+#ifdef G_ENABLE_DEBUG
+static const char *const state_names[]  = {
+  "BEFORE_START",
+  "WAITING",
+  "POLLING_QUEUED",
+  "POLLING_RESTART",
+  "POLLING_DESCRIPTORS"
+};
+#endif
+
+static SelectThreadState select_thread_state = BEFORE_START;
+
+static pthread_t select_thread;
+static pthread_mutex_t select_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t select_thread_cond = PTHREAD_COND_INITIALIZER;
+
+#define SELECT_THREAD_LOCK() pthread_mutex_lock (&select_thread_mutex)
+#define SELECT_THREAD_UNLOCK() pthread_mutex_unlock (&select_thread_mutex)
+#define SELECT_THREAD_SIGNAL() pthread_cond_signal (&select_thread_cond)
+#define SELECT_THREAD_WAIT() pthread_cond_wait (&select_thread_cond, &select_thread_mutex)
+
+/* These are the file descriptors that the select thread is currently
+ * polling.
+ */
+static GPollFD *current_pollfds;
+static guint current_n_pollfds;
+
+/* These are the file descriptors that the select thread should pick
+ * up and start polling when it has a chance.
+ */
+static GPollFD *next_pollfds;
+static guint next_n_pollfds;
+
+/* Pipe used to wake up the select thread */
+static gint select_thread_wakeup_pipe[2];
+
+/* Run loop source used to wake up the main thread */
+static CFRunLoopSourceRef select_main_thread_source;
+
+static void
+select_thread_set_state (SelectThreadState new_state)
+{
+  gboolean old_state;
+
+  if (select_thread_state == new_state)
+    return;
+
+  GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Select thread state: %s => %s", 
state_names[select_thread_state], state_names[new_state]));
+
+  old_state = select_thread_state;
+  select_thread_state = new_state;
+  if (old_state == WAITING && new_state != WAITING)
+    SELECT_THREAD_SIGNAL ();
+}
+
+static void
+signal_main_thread (void)
+{
+  GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Waking up main thread"));
+
+  /* If we are in nextEventMatchingMask, then we need to make sure an
+   * event gets queued, otherwise it's enough to simply wake up the
+   * main thread run loop
+   */
+  if (!run_loop_polling_async)
+    CFRunLoopSourceSignal (select_main_thread_source);
+
+  /* Don't check for CFRunLoopIsWaiting() here because it causes a
+   * race condition (the loop could go into waiting state right after
+   * we checked).
+   */
+  CFRunLoopWakeUp (main_thread_run_loop);
+}
+
+static void *
+select_thread_func (void *arg)
+{
+  char c;
+
+  SELECT_THREAD_LOCK ();
+
+  while (TRUE)
+    {
+      switch (select_thread_state)
+        {
+        case BEFORE_START:
+          /* The select thread has not been started yet
+           */
+          g_assert_not_reached ();
+
+        case WAITING:
+          /* Waiting for a set of file descriptors to be submitted by the main thread
+           *
+           *  => POLLING_QUEUED: main thread thread submits a set of file descriptors
+           */
+          SELECT_THREAD_WAIT ();
+          break;
+
+        case POLLING_QUEUED:
+          /* Waiting for a set of file descriptors to be submitted by the main thread
+           *
+           *  => POLLING_DESCRIPTORS: select thread picks up the file descriptors to begin polling
+           */
+          g_free (current_pollfds);
+
+          current_pollfds = next_pollfds;
+          current_n_pollfds = next_n_pollfds;
+
+          next_pollfds = NULL;
+          next_n_pollfds = 0;
+
+          select_thread_set_state (POLLING_DESCRIPTORS);
+          break;
+
+        case POLLING_RESTART:
+          /* Select thread is currently polling a set of file descriptors, main thread has
+           * began a new iteration with the same set of file descriptors. We don't want to
+           * wake the select thread up and wait for it to restart immediately, but to avoid
+           * a race (described below in select_thread_start_polling()) we need to recheck after
+           * polling completes.
+           *
+           * => POLLING_DESCRIPTORS: select completes, main thread rechecks by polling again
+           * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+           */
+          select_thread_set_state (POLLING_DESCRIPTORS);
+          break;
+
+        case POLLING_DESCRIPTORS:
+          /* In the process of polling the file descriptors
+           *
+           *  => WAITING: polling completes when a file descriptor becomes active
+           *  => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+           *  => POLLING_RESTART: main thread begins a new iteration with the same set file descriptors
+           */
+          SELECT_THREAD_UNLOCK ();
+          old_poll_func (current_pollfds, current_n_pollfds, -1);
+          SELECT_THREAD_LOCK ();
+
+          read (select_thread_wakeup_pipe[0], &c, 1);
+
+          if (select_thread_state == POLLING_DESCRIPTORS)
+            {
+              signal_main_thread ();
+              select_thread_set_state (WAITING);
+            }
+          break;
+        }
+    }
+}
+
+static void
+got_fd_activity (void *info)
+{
+  NSEvent *event;
+
+  /* Post a message so we'll break out of the message loop */
+  event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+                             location: NSZeroPoint
+                        modifierFlags: 0
+                            timestamp: 0
+                         windowNumber: 0
+                              context: nil
+                              subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+                                data1: 0
+                                data2: 0];
+
+  [NSApp postEvent:event atStart:YES];
+}
+
+static void
+select_thread_start (void)
+{
+  g_return_if_fail (select_thread_state == BEFORE_START);
+
+  pipe (select_thread_wakeup_pipe);
+  fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
+
+  CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
got_fd_activity };
+  select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
+
+  CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopCommonModes);
+
+  select_thread_state = WAITING;
+
+  while (TRUE)
+    {
+      if (pthread_create (&select_thread, NULL, select_thread_func, NULL) == 0)
+        break;
+
+      g_warning ("Failed to create select thread, sleeping and trying again");
+      sleep (1);
+    }
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+dump_poll_result (GPollFD *ufds,
+                  guint    nfds)
+{
+  GString *s;
+  gint i;
+
+  s = g_string_new ("");
+  for (i = 0; i < nfds; i++)
+    {
+      if (ufds[i].fd >= 0 && ufds[i].revents)
+        {
+          g_string_append_printf (s, " %d:", ufds[i].fd);
+          if (ufds[i].revents & G_IO_IN)
+            g_string_append (s, " in");
+          if (ufds[i].revents & G_IO_OUT)
+            g_string_append (s, " out");
+          if (ufds[i].revents & G_IO_PRI)
+            g_string_append (s, " pri");
+          g_string_append (s, "\n");
+        }
+    }
+  g_message ("%s", s->str);
+  g_string_free (s, TRUE);
+}
+#endif
+
+static gboolean
+pollfds_equal (GPollFD *old_pollfds,
+               guint    old_n_pollfds,
+               GPollFD *new_pollfds,
+               guint    new_n_pollfds)
+{
+  gint i;
+
+  if (old_n_pollfds != new_n_pollfds)
+    return FALSE;
+
+  for (i = 0; i < old_n_pollfds; i++)
+    {
+      if (old_pollfds[i].fd != new_pollfds[i].fd ||
+          old_pollfds[i].events != new_pollfds[i].events)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+/* Begins a polling operation with the specified GPollFD array; the
+ * timeout is used only to tell if the polling operation is blocking
+ * or non-blocking.
+ *
+ * Returns:
+ *  -1: No file descriptors ready, began asynchronous poll
+ *   0: No file descriptors ready, asynchronous poll not needed
+ * > 0: Number of file descriptors ready
+ */
+static gint
+select_thread_start_poll (GPollFD *ufds,
+                          guint    nfds,
+                          gint     timeout)
+{
+  gint n_ready;
+  gboolean have_new_pollfds = FALSE;
+  gint poll_fd_index = -1;
+  gint i;
+
+  for (i = 0; i < nfds; i++)
+    if (ufds[i].fd == -1)
+      {
+        poll_fd_index = i;
+        break;
+      }
+
+  if (nfds == 0 ||
+      (nfds == 1 && poll_fd_index >= 0))
+    {
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Nothing to poll"));
+      return 0;
+    }
+
+  /* If we went immediately to an async poll, then we might decide to
+   * dispatch idle functions when higher priority file descriptor sources
+   * are ready to be dispatched. So we always need to first check
+   * check synchronously with a timeout of zero, and only when no
+   * sources are immediately ready, go to the asynchronous poll.
+   *
+   * Of course, if the timeout passed in is 0, then the synchronous
+   * check is sufficient and we never need to do the asynchronous poll.
+   */
+  n_ready = old_poll_func (ufds, nfds, 0);
+  if (n_ready > 0 || timeout == 0)
+    {
+#ifdef G_ENABLE_DEBUG
+      if ((_gdk_debug_flags & GDK_DEBUG_EVENTLOOP) && n_ready > 0)
+        {
+          g_message ("EventLoop: Found ready file descriptors before waiting");
+          dump_poll_result (ufds, nfds);
+        }
+#endif
+
+      return n_ready;
+    }
+
+  SELECT_THREAD_LOCK ();
+
+  if (select_thread_state == BEFORE_START)
+    {
+      select_thread_start ();
+    }
+
+  if (select_thread_state == POLLING_QUEUED)
+    {
+      /* If the select thread hasn't picked up the set of file descriptors yet
+       * then we can simply replace an old stale set with a new set.
+       */
+      if (!pollfds_equal (ufds, nfds, next_pollfds, next_n_pollfds - 1))
+        {
+          g_free (next_pollfds);
+          next_pollfds = NULL;
+          next_n_pollfds = 0;
+
+          have_new_pollfds = TRUE;
+        }
+    }
+  else if (select_thread_state == POLLING_RESTART || select_thread_state == POLLING_DESCRIPTORS)
+    {
+      /* If we are already in the process of polling the right set of file descriptors,
+       * there's no need for us to immediately force the select thread to stop polling
+       * and then restart again. And avoiding doing so increases the efficiency considerably
+       * in the common case where we have a set of basically inactive file descriptors that
+       * stay unchanged present as we process many events.
+       *
+       * However, we have to be careful that we don't hit the following race condition
+       *  Select Thread              Main Thread
+       *  -----------------          ---------------
+       *  Polling Completes
+       *                             Reads data or otherwise changes file descriptor state
+       *                             Checks if polling is current
+       *                             Does nothing (*)
+       *                             Releases lock
+       *  Acquires lock
+       *  Marks polling as complete
+       *  Wakes main thread
+       *                             Receives old stale file descriptor state
+       *
+       * To avoid this, when the new set of poll descriptors is the same as the current
+       * one, we transition to the POLLING_RESTART stage at the point marked (*). When
+       * the select thread wakes up from the poll because a file descriptor is active, if
+       * the state is POLLING_RESTART it immediately begins polling same the file descriptor
+       * set again. This normally will just return the same set of active file descriptors
+       * as the first time, but in sequence described above will properly update the
+       * file descriptor state.
+       *
+       * Special case: this RESTART logic is not needed if the only FD is the internal GLib
+       * "wakeup pipe" that is presented when threads are initialized.
+       *
+       * P.S.: The harm in the above sequence is mostly that sources can be signalled
+       *   as ready when they are no longer ready. This may prompt a blocking read
+       *   from a file descriptor that hangs.
+       */
+      if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
+        have_new_pollfds = TRUE;
+      else
+        {
+          if (!((nfds == 1 && poll_fd_index < 0 && g_thread_supported ()) ||
+                (nfds == 2 && poll_fd_index >= 0 && g_thread_supported ())))
+            select_thread_set_state (POLLING_RESTART);
+        }
+    }
+  else
+    have_new_pollfds = TRUE;
+
+  if (have_new_pollfds)
+    {
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Submitting a new set of file descriptor to the select 
thread"));
+
+      g_assert (next_pollfds == NULL);
+
+      next_n_pollfds = nfds + 1;
+      next_pollfds = g_new (GPollFD, nfds + 1);
+      memcpy (next_pollfds, ufds, nfds * sizeof (GPollFD));
+
+      next_pollfds[nfds].fd = select_thread_wakeup_pipe[0];
+      next_pollfds[nfds].events = G_IO_IN;
+
+      if (select_thread_state != POLLING_QUEUED && select_thread_state != WAITING)
+        {
+          if (select_thread_wakeup_pipe[1])
+            {
+              char c = 'A';
+              write (select_thread_wakeup_pipe[1], &c, 1);
+            }
+        }
+
+      select_thread_set_state (POLLING_QUEUED);
+    }
+
+  SELECT_THREAD_UNLOCK ();
+
+  return -1;
+}
+
+/* End an asynchronous polling operation started with
+ * select_thread_collect_poll(). This must be called if and only if
+ * select_thread_start_poll() return -1. The GPollFD array passed
+ * in must be identical to the one passed to select_thread_start_poll().
+ *
+ * The results of the poll are written into the GPollFD array passed in.
+ *
+ * Returns: number of file descriptors ready
+ */
+static int
+select_thread_collect_poll (GPollFD *ufds, guint nfds)
+{
+  gint i;
+  gint n_ready = 0;
+
+  SELECT_THREAD_LOCK ();
+
+  if (select_thread_state == WAITING) /* The poll completed */
+    {
+      for (i = 0; i < nfds; i++)
+        {
+          if (ufds[i].fd == -1)
+            continue;
+
+          g_assert (ufds[i].fd == current_pollfds[i].fd);
+          g_assert (ufds[i].events == current_pollfds[i].events);
+
+          if (current_pollfds[i].revents)
+            {
+              ufds[i].revents = current_pollfds[i].revents;
+              n_ready++;
+            }
+        }
+
+#ifdef G_ENABLE_DEBUG
+      if (_gdk_debug_flags & GDK_DEBUG_EVENTLOOP)
+        {
+          g_message ("EventLoop: Found ready file descriptors after waiting");
+          dump_poll_result (ufds, nfds);
+        }
+#endif
+    }
+
+  SELECT_THREAD_UNLOCK ();
+
+  return n_ready;
+}
+
+/************************************************************
+ *********             Main Loop Source             *********
+ ************************************************************/
+
+typedef struct _GdkMacosEventSource
+{
+  GSource     source;
+  GdkDisplay *display;
+} GdkMacosEventSource;
+
+gboolean
+_gdk_macos_event_source_check_pending (void)
+{
+  return current_events && current_events->head;
+}
+
+NSEvent *
+_gdk_macos_event_source_get_pending (void)
+{
+  NSEvent *event = NULL;
+
+  if (current_events)
+    event = g_queue_pop_tail (current_events);
+
+  return event;
+}
+
+static gboolean
+gdk_macos_event_source_prepare (GSource *source,
+                                gint    *timeout)
+{
+  GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+  gboolean retval;
+
+  /* The prepare stage is the stage before the main loop starts polling
+   * and dispatching events. The autorelease poll is drained here for
+   * the preceding main loop iteration or, in case of the first iteration,
+   * for the operations carried out between event loop initialization and
+   * this first iteration.
+   *
+   * The autorelease poll must only be drained when the following conditions
+   * apply:
+   *  - We are at the base CFRunLoop level (indicated by current_loop_level),
+   *  - We are at the base g_main_loop level (indicated by
+   *    g_main_depth())
+   *  - We are at the base poll_func level (indicated by getting events).
+   *
+   * Messing with the autorelease pool at any level of nesting can cause access
+   * to deallocated memory because autorelease_pool is static and releasing a
+   * pool will cause all pools allocated inside of it to be released as well.
+   */
+  if (current_loop_level == 0 && g_main_depth() == 0 && getting_events == 0)
+    {
+      if (autorelease_pool)
+        [autorelease_pool drain];
+
+      autorelease_pool = [[NSAutoreleasePool alloc] init];
+    }
+
+  *timeout = -1;
+
+  if (event_source->display->event_pause_count > 0)
+    retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+  else
+    retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+              _gdk_macos_event_source_check_pending ());
+
+  return retval;
+}
+
+static gboolean
+gdk_macos_event_source_check (GSource *source)
+{
+  GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+  gboolean retval;
+
+  if (event_source->display->event_pause_count > 0)
+    retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+  else
+    retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+              _gdk_macos_event_source_check_pending ());
+
+  return retval;
+}
+
+static gboolean
+gdk_macos_event_source_dispatch (GSource     *source,
+                                 GSourceFunc  callback,
+                                 gpointer     user_data)
+{
+  GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+  GdkEvent *event;
+
+  _gdk_macos_display_queue_events (GDK_MACOS_DISPLAY (event_source->display));
+
+  event = _gdk_event_unqueue (event_source->display);
+
+  if (event)
+    {
+      _gdk_event_emit (event);
+
+      gdk_event_unref (event);
+    }
+
+  return TRUE;
+}
+
+static void
+gdk_macos_event_source_finalize (GSource *source)
+{
+  GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+
+  g_clear_object (&event_source->display);
+}
+
+static GSourceFuncs event_funcs = {
+  gdk_macos_event_source_prepare,
+  gdk_macos_event_source_check,
+  gdk_macos_event_source_dispatch,
+  gdk_macos_event_source_finalize,
+};
+
+/************************************************************
+ *********             Our Poll Function            *********
+ ************************************************************/
+
+static gint
+poll_func (GPollFD *ufds,
+           guint    nfds,
+           gint     timeout_)
+{
+  NSEvent *event;
+  NSDate *limit_date;
+  gint n_ready;
+
+  static GPollFD *last_ufds;
+
+  last_ufds = ufds;
+
+  n_ready = select_thread_start_poll (ufds, nfds, timeout_);
+  if (n_ready > 0)
+    timeout_ = 0;
+
+  if (timeout_ == -1)
+    limit_date = [NSDate distantFuture];
+  else if (timeout_ == 0)
+    limit_date = [NSDate distantPast];
+  else
+    limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+
+  getting_events++;
+  event = [NSApp nextEventMatchingMask: NSEventMaskAny
+                             untilDate: limit_date
+                                inMode: NSDefaultRunLoopMode
+                               dequeue: YES];
+  getting_events--;
+
+  /* We check if last_ufds did not change since the time this function was
+   * called. It is possible that a recursive main loop (and thus recursive
+   * invocation of this poll function) is triggered while in
+   * nextEventMatchingMask:. If during that time new fds are added,
+   * the cached fds array might be replaced in g_main_context_iterate().
+   * So, we should avoid accessing the old fd array (still pointed at by
+   * ufds) here in that case, since it might have been freed. We avoid this
+   * by not calling the collect stage.
+   */
+  if (last_ufds == ufds && n_ready < 0)
+    n_ready = select_thread_collect_poll (ufds, nfds);
+
+  if (event &&
+      [event type] == NSEventTypeApplicationDefined &&
+      [event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP)
+    {
+      /* Just used to wake us up; if an event and a FD arrived at the same
+       * time; could have come from a previous iteration in some cases,
+       * but the spurious wake up is harmless if a little inefficient.
+       */
+      event = NULL;
+    }
+
+  if (event)
+    {
+      if (!current_events)
+        current_events = g_queue_new ();
+      g_queue_push_head (current_events, [event retain]);
+    }
+
+  return n_ready;
+}
+
+/************************************************************
+ *********  Running the main loop out of CFRunLoop  *********
+ ************************************************************/
+
+/* Wrapper around g_main_context_query() that handles reallocating
+ * run_loop_pollfds up to the proper size
+ */
+static gint
+query_main_context (GMainContext *context,
+                    int           max_priority,
+                    int          *timeout)
+{
+  gint nfds;
+
+  if (!run_loop_pollfds)
+    {
+      run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
+      run_loop_pollfds = g_new (GPollFD, run_loop_pollfds_size);
+    }
+
+  while ((nfds = g_main_context_query (context, max_priority, timeout,
+                                       run_loop_pollfds,
+                                       run_loop_pollfds_size)) > run_loop_pollfds_size)
+    {
+      g_free (run_loop_pollfds);
+      run_loop_pollfds_size = nfds;
+      run_loop_pollfds = g_new (GPollFD, nfds);
+    }
+
+  return nfds;
+}
+
+static void
+run_loop_entry (void)
+{
+  if (acquired_loop_level == -1)
+    {
+      if (g_main_context_acquire (NULL))
+        {
+          GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Beginning tracking run loop activity"));
+          acquired_loop_level = current_loop_level;
+        }
+      else
+        {
+          /* If we fail to acquire the main context, that means someone is iterating
+           * the main context in a different thread; we simply wait until this loop
+           * exits and then try again at next entry. In general, iterating the loop
+           * from a different thread is rare: it is only possible when GDK threading
+           * is initialized and is not frequently used even then. So, we hope that
+           * having GLib main loop iteration blocked in the combination of that and
+           * a native modal operation is a minimal problem. We could imagine using a
+           * thread that does g_main_context_wait() and then wakes us back up, but
+           * the gain doesn't seem worth the complexity.
+           */
+          GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Can't acquire main loop; skipping tracking run loop 
activity"));
+        }
+    }
+}
+
+static void
+run_loop_before_timers (void)
+{
+}
+
+static void
+run_loop_before_sources (void)
+{
+  GMainContext *context = g_main_context_default ();
+  gint max_priority;
+  gint nfds;
+
+  /* Before we let the CFRunLoop process sources, we want to check if there
+   * are any pending GLib main loop sources more urgent than
+   * G_PRIORITY_DEFAULT that need to be dispatched. (We consider all activity
+   * from the CFRunLoop to have a priority of G_PRIORITY_DEFAULT.) If no
+   * sources are processed by the CFRunLoop, then processing will continue
+   * on to the BeforeWaiting stage where we check for lower priority sources.
+   */
+
+  g_main_context_prepare (context, &max_priority);
+  max_priority = MIN (max_priority, G_PRIORITY_DEFAULT);
+
+  /* We ignore the timeout that query_main_context () returns since we'll
+   * always query again before waiting.
+   */
+  nfds = query_main_context (context, max_priority, NULL);
+
+  if (nfds)
+    old_poll_func (run_loop_pollfds, nfds, 0);
+
+  if (g_main_context_check (context, max_priority, run_loop_pollfds, nfds))
+    {
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching high priority sources"));
+      g_main_context_dispatch (context);
+    }
+}
+
+static void
+dummy_timer_callback (CFRunLoopTimerRef  timer,
+                      void              *info)
+{
+  /* Nothing; won't normally even be called */
+}
+
+static void
+run_loop_before_waiting (void)
+{
+  GMainContext *context = g_main_context_default ();
+  gint timeout;
+  gint n_ready;
+
+  /* At this point, the CFRunLoop is ready to wait. We start a GMain loop
+   * iteration by calling the check() and query() stages. We start a
+   * poll, and if it doesn't complete immediately we let the run loop
+   * go ahead and sleep. Before doing that, if there was a timeout from
+   * GLib, we set up a CFRunLoopTimer to wake us up.
+   */
+
+  g_main_context_prepare (context, &run_loop_max_priority);
+
+  run_loop_n_pollfds = query_main_context (context, run_loop_max_priority, &timeout);
+
+  n_ready = select_thread_start_poll (run_loop_pollfds, run_loop_n_pollfds, timeout);
+
+  if (n_ready > 0 || timeout == 0)
+    {
+      /* We have stuff to do, no sleeping allowed! */
+      CFRunLoopWakeUp (main_thread_run_loop);
+    }
+  else if (timeout > 0)
+    {
+      /* We need to get the run loop to break out of its wait when our timeout
+       * expires. We do this by adding a dummy timer that we'll remove immediately
+       * after the wait wakes up.
+       */
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Adding timer to wake us up in %d milliseconds", timeout));
+
+      run_loop_timer = CFRunLoopTimerCreate (NULL, /* allocator */
+                                             CFAbsoluteTimeGetCurrent () + timeout / 1000.,
+                                             0, /* interval (0=does not repeat) */
+                                             0, /* flags */
+                                             0, /* order (priority) */
+                                             dummy_timer_callback,
+                                             NULL);
+
+      CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+    }
+
+  run_loop_polling_async = n_ready < 0;
+}
+
+static void
+run_loop_after_waiting (void)
+{
+  GMainContext *context = g_main_context_default ();
+
+  /* After sleeping, we finish of the GMain loop iteratin started in before_waiting()
+   * by doing the check() and dispatch() stages.
+   */
+
+  if (run_loop_timer)
+    {
+      CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+      CFRelease (run_loop_timer);
+      run_loop_timer = NULL;
+    }
+
+  if (run_loop_polling_async)
+    {
+      select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds);
+      run_loop_polling_async = FALSE;
+    }
+
+  if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds))
+    {
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching after waiting"));
+      g_main_context_dispatch (context);
+    }
+}
+
+static void
+run_loop_exit (void)
+{
+  /* + 1 because we decrement current_loop_level separately in observer_callback() */
+  if ((current_loop_level + 1) == acquired_loop_level)
+    {
+      g_main_context_release (NULL);
+      acquired_loop_level = -1;
+      GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Ended tracking run loop activity"));
+    }
+}
+
+static void
+run_loop_observer_callback (CFRunLoopObserverRef observer,
+                            CFRunLoopActivity    activity,
+                            void                *info)
+{
+  switch (activity)
+    {
+    case kCFRunLoopEntry:
+      current_loop_level++;
+      break;
+    case kCFRunLoopExit:
+      g_return_if_fail (current_loop_level > 0);
+      current_loop_level--;
+      break;
+    case kCFRunLoopBeforeTimers:
+    case kCFRunLoopBeforeSources:
+    case kCFRunLoopBeforeWaiting:
+    case kCFRunLoopAfterWaiting:
+    case kCFRunLoopAllActivities:
+    default:
+      break;
+    }
+
+  if (getting_events > 0) /* Activity we triggered */
+    return;
+
+  switch (activity)
+    {
+    case kCFRunLoopEntry:
+      run_loop_entry ();
+      break;
+    case kCFRunLoopBeforeTimers:
+      run_loop_before_timers ();
+      break;
+    case kCFRunLoopBeforeSources:
+      run_loop_before_sources ();
+      break;
+    case kCFRunLoopBeforeWaiting:
+      run_loop_before_waiting ();
+      break;
+    case kCFRunLoopAfterWaiting:
+      run_loop_after_waiting ();
+      break;
+    case kCFRunLoopExit:
+      run_loop_exit ();
+      break;
+    case kCFRunLoopAllActivities:
+      /* TODO: Do most of the above? */
+    default:
+      break;
+    }
+}
+
+/************************************************************/
+
+GSource *
+_gdk_macos_event_source_new (GdkMacosDisplay *display)
+{
+  CFRunLoopObserverRef observer;
+  GdkMacosEventSource *event_source;
+  GSource *source;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  /* Hook into the GLib main loop */
+
+  event_poll_fd.events = G_IO_IN;
+  event_poll_fd.fd = -1;
+
+  source = g_source_new (&event_funcs, sizeof (GdkMacosEventSource));
+  g_source_set_name (source, "GDK Quartz event source");
+  g_source_add_poll (source, &event_poll_fd);
+  g_source_set_priority (source, GDK_PRIORITY_EVENTS);
+  g_source_set_can_recurse (source, TRUE);
+
+  old_poll_func = g_main_context_get_poll_func (NULL);
+  g_main_context_set_poll_func (NULL, poll_func);
+
+  event_source = (GdkMacosEventSource *)source;
+  event_source->display = g_object_ref (GDK_DISPLAY (display));
+
+  /* Hook into the the CFRunLoop for the main thread */
+
+  main_thread_run_loop = CFRunLoopGetCurrent ();
+
+  observer = CFRunLoopObserverCreate (NULL, /* default allocator */
+                                      kCFRunLoopAllActivities,
+                                      true, /* repeats: not one-shot */
+                                      0, /* order (priority) */
+                                      run_loop_observer_callback,
+                                      NULL);
+
+  CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
+
+  /* Initialize our autorelease pool */
+  autorelease_pool = [[NSAutoreleasePool alloc] init];
+
+  return source;
+}
diff --git a/gdk/macos/gdkmacosglcontext-private.h b/gdk/macos/gdkmacosglcontext-private.h
new file mode 100644
index 0000000000..e976939eb1
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext-private.h
@@ -0,0 +1,60 @@
+/* gdkmacosglcontext-private.h
+ *
+ * Copyright (C) 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_MACOS_GL_CONTEXT_PRIVATE_H__
+#define __GDK_MACOS_GL_CONTEXT_PRIVATE_H__
+
+#include "gdkglcontextprivate.h"
+#include "gdkdisplayprivate.h"
+#include "gdksurface.h"
+#include "gdkinternals.h"
+
+#include "gdkmacosglcontext.h"
+#include "gdkmacossurface.h"
+
+#import <OpenGL/OpenGL.h>
+#import <OpenGL/gl.h>
+#import <AppKit/AppKit.h>
+
+G_BEGIN_DECLS
+
+struct _GdkMacosGLContext
+{
+  GdkGLContext parent_instance;
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  NSOpenGLContext *gl_context;
+  G_GNUC_END_IGNORE_DEPRECATIONS
+
+  gboolean is_attached;
+};
+
+struct _GdkMacosGLContextClass
+{
+  GdkGLContextClass parent_class;
+};
+
+GdkGLContext *_gdk_macos_gl_context_new          (GdkMacosSurface    *surface,
+                                                  gboolean            attached,
+                                                  GdkGLContext       *share,
+                                                  GError            **error);
+gboolean      _gdk_macos_gl_context_make_current (GdkMacosGLContext  *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_GL_CONTEXT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosglcontext.c b/gdk/macos/gdkmacosglcontext.c
new file mode 100644
index 0000000000..0d1e03e1d2
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext.c
@@ -0,0 +1,153 @@
+/*
+ * 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.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 "gdkmacosglcontext-private.h"
+#include "gdkmacossurface-private.h"
+
+#include "gdkinternals.h"
+#include "gdkintl.h"
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+G_DEFINE_TYPE (GdkMacosGLContext, gdk_macos_gl_context, GDK_TYPE_GL_CONTEXT)
+
+static void
+gdk_macos_gl_context_end_frame (GdkDrawContext *context,
+                                cairo_region_t *painted)
+{
+  GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (context);
+
+  g_assert (GDK_IS_MACOS_GL_CONTEXT (self));
+
+  [self->gl_context flushBuffer];
+}
+
+static void
+gdk_macos_gl_context_dispose (GObject *gobject)
+{
+  GdkMacosGLContext *context_macos = GDK_MACOS_GL_CONTEXT (gobject);
+
+  if (context_macos->gl_context != NULL)
+    {
+      [context_macos->gl_context clearDrawable];
+      [context_macos->gl_context release];
+      context_macos->gl_context = NULL;
+    }
+
+  G_OBJECT_CLASS (gdk_macos_gl_context_parent_class)->dispose (gobject);
+}
+
+static void
+gdk_macos_gl_context_class_init (GdkMacosGLContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+  object_class->dispose = gdk_macos_gl_context_dispose;
+
+  draw_context_class->end_frame = gdk_macos_gl_context_end_frame;
+}
+
+static void
+gdk_macos_gl_context_init (GdkMacosGLContext *self)
+{
+}
+
+GdkGLContext *
+_gdk_macos_gl_context_new (GdkMacosSurface  *surface,
+                           gboolean          attached,
+                           GdkGLContext     *share,
+                           GError          **error)
+{
+  static const NSOpenGLPixelFormatAttribute attrs[] = {
+    NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+    NSOpenGLPFADoubleBuffer,
+    NSOpenGLPFAColorSize, 24,
+    NSOpenGLPFAAlphaSize, 8,
+    0
+  };
+
+  NSOpenGLPixelFormat *format;
+  GdkMacosGLContext *context = NULL;
+  NSOpenGLContext *ctx;
+  GdkDisplay *display;
+  NSView *nsview;
+  GLint sync_to_framerate = 1;
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (surface), NULL);
+  g_return_val_if_fail (!share || GDK_IS_MACOS_GL_CONTEXT (share), NULL);
+
+  display = gdk_surface_get_display (GDK_SURFACE (surface));
+
+  if (!(format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]))
+    {
+      g_set_error_literal (error,
+                           GDK_GL_ERROR,
+                           GDK_GL_ERROR_NOT_AVAILABLE,
+                           _("Unable to create a GL pixel format"));
+      goto failure;
+    }
+
+  ctx = [[NSOpenGLContext alloc] initWithFormat:format
+                                 shareContext:share ? GDK_MACOS_GL_CONTEXT (share)->gl_context : nil];
+  if (ctx == NULL)
+    {
+      g_set_error_literal (error,
+                           GDK_GL_ERROR,
+                           GDK_GL_ERROR_NOT_AVAILABLE,
+                           _("Unable to create a GL context"));
+      goto failure;
+    }
+
+  nsview = _gdk_macos_surface_get_view (surface);
+  [nsview setWantsBestResolutionOpenGLSurface:YES];
+  [ctx setValues:&sync_to_framerate forParameter:NSOpenGLCPSwapInterval];
+  [ctx setView:nsview];
+
+  GDK_NOTE (OPENGL,
+            g_print ("Created NSOpenGLContext[%p]\n", ctx));
+
+  context = g_object_new (GDK_TYPE_MACOS_GL_CONTEXT,
+                          "surface", surface,
+                          "shared-context", share,
+                          NULL);
+
+  context->gl_context = ctx;
+  context->is_attached = attached;
+
+failure:
+  if (format != NULL)
+    [format release];
+
+  return GDK_GL_CONTEXT (context);
+}
+
+gboolean
+_gdk_macos_gl_context_make_current (GdkMacosGLContext *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_GL_CONTEXT (self), FALSE);
+
+  [self->gl_context makeCurrentContext];
+
+  return TRUE;
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/gdk/macos/gdkmacosglcontext.h b/gdk/macos/gdkmacosglcontext.h
new file mode 100644
index 0000000000..e4add89502
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext.h
@@ -0,0 +1,43 @@
+/*
+ * 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.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_GL_CONTEXT_H__
+#define __GDK_MACOS_GL_CONTEXT_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_GL_CONTEXT    (gdk_macos_gl_context_get_type ())
+#define GDK_MACOS_GL_CONTEXT(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_MACOS_GL_CONTEXT, 
GdkMacosGLContext))
+#define GDK_IS_MACOS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_MACOS_GL_CONTEXT))
+
+typedef struct _GdkMacosGLContext      GdkMacosGLContext;
+typedef struct _GdkMacosGLContextClass GdkMacosGLContextClass;
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_gl_context_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_GL_CONTEXT_H__ */
diff --git a/gdk/macos/gdkmacoskeymap-private.h b/gdk/macos/gdkmacoskeymap-private.h
new file mode 100644
index 0000000000..34ff21b225
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap-private.h
@@ -0,0 +1,36 @@
+/*
+ * 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.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_KEYMAP_PRIVATE_H__
+#define __GDK_MACOS_KEYMAP_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacoskeymap.h"
+
+G_BEGIN_DECLS
+
+GdkMacosKeymap *_gdk_macos_keymap_new            (GdkMacosDisplay *display);
+GdkEventType    _gdk_macos_keymap_get_event_type (NSEvent         *event);
+gboolean        _gdk_macos_keymap_is_modifier    (guint            keycode);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_KEYMAP_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoskeymap.c b/gdk/macos/gdkmacoskeymap.c
new file mode 100644
index 0000000000..c638c75179
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap.c
@@ -0,0 +1,698 @@
+/*
+ * Copyright © 2000-2020 Red Hat, Inc.
+ * Copyright © 2005 Imendio AB
+ *
+ * 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
+ */
+/* Some parts of this code come from quartzKeyboard.c,
+ * from the Apple X11 Server.
+ *
+ * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
+ *
+ *  Permission is hereby granted, free of charge, to any person
+ *  obtaining a copy of this software and associated documentation files
+ *  (the "Software"), to deal in the Software without restriction,
+ *  including without limitation the rights to use, copy, modify, merge,
+ *  publish, distribute, sublicense, and/or sell copies of the Software,
+ *  and to permit persons to whom the Software is furnished to do so,
+ *  subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be
+ *  included in all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *  NONINFRINGEMENT.  IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
+ *  HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ *  DEALINGS IN THE SOFTWARE.
+ *
+ *  Except as contained in this notice, the name(s) of the above
+ *  copyright holders shall not be used in advertising or otherwise to
+ *  promote the sale, use or other dealings in this Software without
+ *  prior written authorization.
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+#include <Carbon/Carbon.h>
+#include <gdk/gdk.h>
+
+#include "gdkkeysprivate.h"
+#include "gdkkeysyms.h"
+#include "gdkmacoskeymap-private.h"
+
+struct _GdkMacosKeymap
+{
+  GdkKeymap parent_instance;
+};
+
+struct _GdkMacosKeymapClass
+{
+  GdkKeymapClass parent_instance;
+};
+
+G_DEFINE_TYPE (GdkMacosKeymap, gdk_macos_keymap, GDK_TYPE_KEYMAP)
+
+/* This is a table of all keyvals. Each keycode gets KEYVALS_PER_KEYCODE entries.
+ * TThere is 1 keyval per modifier (Nothing, Shift, Alt, Shift+Alt);
+ */
+static guint *keyval_array = NULL;
+
+#define NUM_KEYCODES 128
+#define KEYVALS_PER_KEYCODE 4
+#define GET_KEYVAL(keycode, group, level) \
+  (keyval_array[(keycode * KEYVALS_PER_KEYCODE + group * 2 + level)])
+
+const static struct {
+  guint keycode;
+  guint keyval;
+  unsigned int modmask; /* So we can tell when a mod key is pressed/released */
+} modifier_keys[] = {
+  {  54, GDK_KEY_Meta_R,    NSEventModifierFlagCommand },
+  {  55, GDK_KEY_Meta_L,    NSEventModifierFlagCommand },
+  {  56, GDK_KEY_Shift_L,   NSEventModifierFlagShift },
+  {  57, GDK_KEY_Caps_Lock, NSEventModifierFlagCapsLock },
+  {  58, GDK_KEY_Alt_L,     NSEventModifierFlagOption },
+  {  59, GDK_KEY_Control_L, NSEventModifierFlagControl },
+  {  60, GDK_KEY_Shift_R,   NSEventModifierFlagShift },
+  {  61, GDK_KEY_Alt_R,     NSEventModifierFlagOption },
+  {  62, GDK_KEY_Control_R, NSEventModifierFlagControl }
+};
+
+const static struct {
+  guint keycode;
+  guint keyval;
+} function_keys[] = {
+  { 122, GDK_KEY_F1 },
+  { 120, GDK_KEY_F2 },
+  {  99, GDK_KEY_F3 },
+  { 118, GDK_KEY_F4 },
+  {  96, GDK_KEY_F5 },
+  {  97, GDK_KEY_F6 },
+  {  98, GDK_KEY_F7 },
+  { 100, GDK_KEY_F8 },
+  { 101, GDK_KEY_F9 },
+  { 109, GDK_KEY_F10 },
+  { 103, GDK_KEY_F11 },
+  { 111, GDK_KEY_F12 },
+  { 105, GDK_KEY_F13 },
+  { 107, GDK_KEY_F14 },
+  { 113, GDK_KEY_F15 },
+  { 106, GDK_KEY_F16 }
+};
+
+const static struct {
+  guint keycode;
+  guint normal_keyval, keypad_keyval;
+} known_numeric_keys[] = {
+  { 65, GDK_KEY_period, GDK_KEY_KP_Decimal },
+  { 67, GDK_KEY_asterisk, GDK_KEY_KP_Multiply },
+  { 69, GDK_KEY_plus, GDK_KEY_KP_Add },
+  { 75, GDK_KEY_slash, GDK_KEY_KP_Divide },
+  { 76, GDK_KEY_Return, GDK_KEY_KP_Enter },
+  { 78, GDK_KEY_minus, GDK_KEY_KP_Subtract },
+  { 81, GDK_KEY_equal, GDK_KEY_KP_Equal },
+  { 82, GDK_KEY_0, GDK_KEY_KP_0 },
+  { 83, GDK_KEY_1, GDK_KEY_KP_1 },
+  { 84, GDK_KEY_2, GDK_KEY_KP_2 },
+  { 85, GDK_KEY_3, GDK_KEY_KP_3 },
+  { 86, GDK_KEY_4, GDK_KEY_KP_4 },
+  { 87, GDK_KEY_5, GDK_KEY_KP_5 },
+  { 88, GDK_KEY_6, GDK_KEY_KP_6 },
+  { 89, GDK_KEY_7, GDK_KEY_KP_7 },
+  { 91, GDK_KEY_8, GDK_KEY_KP_8 },
+  { 92, GDK_KEY_9, GDK_KEY_KP_9 }
+};
+
+/* These values aren't covered by gdk_unicode_to_keyval */
+const static struct {
+  gunichar ucs_value;
+  guint keyval;
+} special_ucs_table [] = {
+  { 0x0001, GDK_KEY_Home },
+  { 0x0003, GDK_KEY_Return },
+  { 0x0004, GDK_KEY_End },
+  { 0x0008, GDK_KEY_BackSpace },
+  { 0x0009, GDK_KEY_Tab },
+  { 0x000b, GDK_KEY_Page_Up },
+  { 0x000c, GDK_KEY_Page_Down },
+  { 0x000d, GDK_KEY_Return },
+  { 0x001b, GDK_KEY_Escape },
+  { 0x001c, GDK_KEY_Left },
+  { 0x001d, GDK_KEY_Right },
+  { 0x001e, GDK_KEY_Up },
+  { 0x001f, GDK_KEY_Down },
+  { 0x007f, GDK_KEY_Delete },
+  { 0xf027, GDK_KEY_dead_acute },
+  { 0xf060, GDK_KEY_dead_grave },
+  { 0xf300, GDK_KEY_dead_grave },
+  { 0xf0b4, GDK_KEY_dead_acute },
+  { 0xf301, GDK_KEY_dead_acute },
+  { 0xf385, GDK_KEY_dead_acute },
+  { 0xf05e, GDK_KEY_dead_circumflex },
+  { 0xf2c6, GDK_KEY_dead_circumflex },
+  { 0xf302, GDK_KEY_dead_circumflex },
+  { 0xf07e, GDK_KEY_dead_tilde },
+  { 0xf2dc, GDK_KEY_dead_tilde },
+  { 0xf303, GDK_KEY_dead_tilde },
+  { 0xf342, GDK_KEY_dead_perispomeni },
+  { 0xf0af, GDK_KEY_dead_macron },
+  { 0xf304, GDK_KEY_dead_macron },
+  { 0xf2d8, GDK_KEY_dead_breve },
+  { 0xf306, GDK_KEY_dead_breve },
+  { 0xf2d9, GDK_KEY_dead_abovedot },
+  { 0xf307, GDK_KEY_dead_abovedot },
+  { 0xf0a8, GDK_KEY_dead_diaeresis },
+  { 0xf308, GDK_KEY_dead_diaeresis },
+  { 0xf2da, GDK_KEY_dead_abovering },
+  { 0xf30A, GDK_KEY_dead_abovering },
+  { 0xf022, GDK_KEY_dead_doubleacute },
+  { 0xf2dd, GDK_KEY_dead_doubleacute },
+  { 0xf30B, GDK_KEY_dead_doubleacute },
+  { 0xf2c7, GDK_KEY_dead_caron },
+  { 0xf30C, GDK_KEY_dead_caron },
+  { 0xf0be, GDK_KEY_dead_cedilla },
+  { 0xf327, GDK_KEY_dead_cedilla },
+  { 0xf2db, GDK_KEY_dead_ogonek },
+  { 0xf328, GDK_KEY_dead_ogonek },
+  { 0xfe5d, GDK_KEY_dead_iota },
+  { 0xf323, GDK_KEY_dead_belowdot },
+  { 0xf309, GDK_KEY_dead_hook },
+  { 0xf31B, GDK_KEY_dead_horn },
+  { 0xf02d, GDK_KEY_dead_stroke },
+  { 0xf335, GDK_KEY_dead_stroke },
+  { 0xf336, GDK_KEY_dead_stroke },
+  { 0xf313, GDK_KEY_dead_abovecomma },
+  /*  { 0xf313, GDK_KEY_dead_psili }, */
+  { 0xf314, GDK_KEY_dead_abovereversedcomma },
+  /*  { 0xf314, GDK_KEY_dead_dasia }, */
+  { 0xf30F, GDK_KEY_dead_doublegrave },
+  { 0xf325, GDK_KEY_dead_belowring },
+  { 0xf2cd, GDK_KEY_dead_belowmacron },
+  { 0xf331, GDK_KEY_dead_belowmacron },
+  { 0xf32D, GDK_KEY_dead_belowcircumflex },
+  { 0xf330, GDK_KEY_dead_belowtilde },
+  { 0xf32E, GDK_KEY_dead_belowbreve },
+  { 0xf324, GDK_KEY_dead_belowdiaeresis },
+  { 0xf311, GDK_KEY_dead_invertedbreve },
+  { 0xf02c, GDK_KEY_dead_belowcomma },
+  { 0xf326, GDK_KEY_dead_belowcomma }
+};
+
+static void
+gdk_macos_keymap_update (GdkMacosKeymap *self)
+{
+  const void *chr_data = NULL;
+  guint *p;
+  int i;
+
+  TISInputSourceRef new_layout = TISCopyCurrentKeyboardLayoutInputSource ();
+  CFDataRef layout_data_ref;
+
+  g_free (keyval_array);
+  keyval_array = g_new0 (guint, NUM_KEYCODES * KEYVALS_PER_KEYCODE);
+
+  layout_data_ref = (CFDataRef)TISGetInputSourceProperty (new_layout, kTISPropertyUnicodeKeyLayoutData);
+
+  if (layout_data_ref)
+    chr_data = CFDataGetBytePtr (layout_data_ref);
+
+  if (chr_data == NULL)
+    {
+      g_error ("cannot get keyboard layout data");
+      return;
+    }
+
+  for (i = 0; i < NUM_KEYCODES; i++)
+    {
+      int j;
+      UInt32 modifiers[] = {0, shiftKey, optionKey, shiftKey | optionKey};
+      UniChar chars[4];
+      UniCharCount nChars;
+
+      p = keyval_array + i * KEYVALS_PER_KEYCODE;
+
+      for (j = 0; j < KEYVALS_PER_KEYCODE; j++)
+        {
+          UInt32 state = 0;
+          OSStatus err;
+          UInt16 key_code;
+          UniChar uc;
+
+          key_code = modifiers[j] | i;
+          err = UCKeyTranslate (chr_data, i, kUCKeyActionDisplay,
+                                (modifiers[j] >> 8) & 0xFF,
+                                LMGetKbdType(),
+                                0,
+                                &state, 4, &nChars, chars);
+
+          /* FIXME: Theoretically, we can get multiple UTF-16
+           * values; we should convert them to proper unicode and
+           * figure out whether there are really keyboard layouts
+           * that give us more than one character for one
+           * keypress.
+           */
+          if (err == noErr && nChars == 1)
+            {
+              int k;
+              gboolean found = FALSE;
+
+              /* A few <Shift><Option>keys return two characters,
+               * the first of which is U+00a0, which isn't
+               * interesting; so we return the second. More
+               * sophisticated handling is the job of a
+               * GtkIMContext.
+               *
+               * If state isn't zero, it means that it's a dead
+               * key of some sort. Some of those are enumerated in
+               * the special_ucs_table with the high nibble set to
+               * f to push it into the private use range. Here we
+               * do the same.
+               */
+              if (state != 0)
+                chars[nChars - 1] |= 0xf000;
+              uc = chars[nChars - 1];
+
+              for (k = 0; k < G_N_ELEMENTS (special_ucs_table); k++)
+                {
+                  if (special_ucs_table[k].ucs_value == uc)
+                    {
+                      p[j] = special_ucs_table[k].keyval;
+                      found = TRUE;
+                      break;
+                    }
+                }
+
+              /* Special-case shift-tab since GTK+ expects
+               * GDK_KEY_ISO_Left_Tab for that.
+               */
+              if (found && p[j] == GDK_KEY_Tab && modifiers[j] == shiftKey)
+                p[j] = GDK_KEY_ISO_Left_Tab;
+
+              if (!found)
+                p[j] = gdk_unicode_to_keyval (uc);
+            }
+        }
+
+      if (p[3] == p[2])
+        p[3] = 0;
+      if (p[2] == p[1])
+        p[2] = 0;
+      if (p[1] == p[0])
+        p[1] = 0;
+      if (p[0] == p[2] &&
+          p[1] == p[3])
+        p[2] = p[3] = 0;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+    {
+      p = keyval_array + modifier_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+      if (p[0] == 0 && p[1] == 0 &&
+          p[2] == 0 && p[3] == 0)
+        p[0] = modifier_keys[i].keyval;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (function_keys); i++)
+    {
+      p = keyval_array + function_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+      p[0] = function_keys[i].keyval;
+      p[1] = p[2] = p[3] = 0;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (known_numeric_keys); i++)
+    {
+      p = keyval_array + known_numeric_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+      if (p[0] == known_numeric_keys[i].normal_keyval)
+        p[0] = known_numeric_keys[i].keypad_keyval;
+    }
+
+  g_signal_emit_by_name (self, "keys-changed");
+}
+
+static PangoDirection
+gdk_macos_keymap_get_direction (GdkKeymap *keymap)
+{
+  return PANGO_DIRECTION_NEUTRAL;
+}
+
+static gboolean
+gdk_macos_keymap_have_bidi_layouts (GdkKeymap *keymap)
+{
+  return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_caps_lock_state (GdkKeymap *keymap)
+{
+  return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_num_lock_state (GdkKeymap *keymap)
+{
+  return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_scroll_lock_state (GdkKeymap *keymap)
+{
+  return FALSE;
+}
+
+static guint
+gdk_macos_keymap_lookup_key (GdkKeymap          *keymap,
+                             const GdkKeymapKey *key)
+{
+  GdkMacosKeymap *self = (GdkMacosKeymap *)keymap;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (self));
+  g_assert (key != NULL);
+
+  return GET_KEYVAL (key->keycode, key->group, key->level);
+}
+
+static guint
+translate_keysym (guint            hardware_keycode,
+                  gint             group,
+                  GdkModifierType  state,
+                  gint            *effective_group,
+                  gint            *effective_level)
+{
+  gint level;
+  guint tmp_keyval;
+
+  level = (state & GDK_SHIFT_MASK) ? 1 : 0;
+
+  if (!(GET_KEYVAL (hardware_keycode, group, 0) || GET_KEYVAL (hardware_keycode, group, 1)) &&
+      (GET_KEYVAL (hardware_keycode, 0, 0) || GET_KEYVAL (hardware_keycode, 0, 1)))
+    group = 0;
+
+  if (!GET_KEYVAL (hardware_keycode, group, level) &&
+      GET_KEYVAL (hardware_keycode, group, 0))
+    level = 0;
+
+  tmp_keyval = GET_KEYVAL (hardware_keycode, group, level);
+
+  if (state & GDK_LOCK_MASK)
+    {
+      guint upper = gdk_keyval_to_upper (tmp_keyval);
+      if (upper != tmp_keyval)
+        tmp_keyval = upper;
+    }
+
+  if (effective_group)
+    *effective_group = group;
+  if (effective_level)
+    *effective_level = level;
+
+  return tmp_keyval;
+}
+
+static gboolean
+gdk_macos_keymap_get_entries_for_keyval (GdkKeymap *keymap,
+                                         guint      keyval,
+                                         GArray    *keys)
+{
+  gboolean ret = FALSE;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+  g_assert (keys != NULL);
+
+  for (guint i = 0; i < NUM_KEYCODES * KEYVALS_PER_KEYCODE; i++)
+    {
+      GdkKeymapKey key;
+
+      if (keyval_array[i] != keyval)
+        continue;
+
+      key.keycode = i / KEYVALS_PER_KEYCODE;
+      key.group = (i % KEYVALS_PER_KEYCODE) >= 2;
+      key.level = i % 2;
+
+      g_array_append_val (keys, key);
+
+      ret = TRUE;
+    }
+
+  return ret;
+}
+
+static gboolean
+gdk_macos_keymap_get_entries_for_keycode (GdkKeymap     *keymap,
+                                          guint          hardware_keycode,
+                                          GdkKeymapKey **keys,
+                                          guint        **keyvals,
+                                          gint          *n_entries)
+{
+  GArray *keys_array;
+  GArray *keyvals_array;
+  guint *p;
+  guint i;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+  g_assert (keyvals != NULL);
+  g_assert (n_entries != NULL);
+
+  *keyvals = NULL;
+  *n_entries = 0;
+
+  if (hardware_keycode > NUM_KEYCODES)
+    return FALSE;
+
+  if (keys)
+    keys_array = g_array_new (FALSE, FALSE, sizeof (GdkKeymapKey));
+  else
+    keys_array = NULL;
+
+  if (keyvals)
+    keyvals_array = g_array_new (FALSE, FALSE, sizeof (guint));
+  else
+    keyvals_array = NULL;
+
+  p = keyval_array + hardware_keycode * KEYVALS_PER_KEYCODE;
+
+  for (i = 0; i < KEYVALS_PER_KEYCODE; i++)
+    {
+      if (!p[i])
+        continue;
+
+      (*n_entries)++;
+
+      if (keyvals_array)
+        g_array_append_val (keyvals_array, p[i]);
+
+      if (keys_array)
+        {
+          GdkKeymapKey key;
+
+          key.keycode = hardware_keycode;
+          key.group = i >= 2;
+          key.level = i % 2;
+
+          g_array_append_val (keys_array, key);
+        }
+    }
+
+  if (keys)
+    *keys = (GdkKeymapKey *)(gpointer)g_array_free (keys_array, FALSE);
+
+  if (keyvals)
+    *keyvals = (guint *)(gpointer)g_array_free (keyvals_array, FALSE);
+
+  return *n_entries > 0;
+}
+
+static gboolean
+gdk_macos_keymap_translate_keyboard_state (GdkKeymap       *keymap,
+                                           guint            hardware_keycode,
+                                           GdkModifierType  state,
+                                           gint             group,
+                                           guint           *keyval,
+                                           gint            *effective_group,
+                                           gint            *level,
+                                           GdkModifierType *consumed_modifiers)
+{
+  guint tmp_keyval;
+  GdkModifierType bit;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+
+  if (keyval)
+    *keyval = 0;
+  if (effective_group)
+    *effective_group = 0;
+  if (level)
+    *level = 0;
+  if (consumed_modifiers)
+    *consumed_modifiers = 0;
+
+  if (hardware_keycode < 0 || hardware_keycode >= NUM_KEYCODES)
+    return FALSE;
+
+  tmp_keyval = translate_keysym (hardware_keycode, group, state, level, effective_group);
+
+  /* Check if modifiers modify the keyval */
+  if (consumed_modifiers)
+    {
+      guint tmp_modifiers = (state & GDK_MODIFIER_MASK);
+
+      for (bit = 1; bit <= tmp_modifiers; bit <<= 1)
+        {
+          if ((bit & tmp_modifiers) &&
+              translate_keysym (hardware_keycode, group, state & ~bit,
+                                NULL, NULL) == tmp_keyval)
+            tmp_modifiers &= ~bit;
+        }
+
+      *consumed_modifiers = tmp_modifiers;
+    }
+
+  if (keyval)
+    *keyval = tmp_keyval;
+
+  return TRUE;
+}
+
+static void
+input_sources_changed_notification (CFNotificationCenterRef  center,
+                                    void                    *observer,
+                                    CFStringRef              name,
+                                    const void              *object,
+                                    CFDictionaryRef          userInfo)
+{
+  GdkMacosKeymap *self = observer;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (self));
+
+  gdk_macos_keymap_update (self);
+}
+
+static void
+gdk_macos_keymap_finalize (GObject *object)
+{
+  CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+                                      object,
+                                      CFSTR ("AppleSelectedInputSourcesChangedNotification"),
+                                      NULL);
+
+  G_OBJECT_CLASS (gdk_macos_keymap_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_keymap_class_init (GdkMacosKeymapClass *klass)
+{
+  GdkKeymapClass *keymap_class = GDK_KEYMAP_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gdk_macos_keymap_finalize;
+
+  keymap_class->get_caps_lock_state = gdk_macos_keymap_get_caps_lock_state;
+  keymap_class->get_direction = gdk_macos_keymap_get_direction;
+  keymap_class->get_entries_for_keycode = gdk_macos_keymap_get_entries_for_keycode;
+  keymap_class->get_entries_for_keyval = gdk_macos_keymap_get_entries_for_keyval;
+  keymap_class->get_num_lock_state = gdk_macos_keymap_get_num_lock_state;
+  keymap_class->get_scroll_lock_state = gdk_macos_keymap_get_scroll_lock_state;
+  keymap_class->have_bidi_layouts = gdk_macos_keymap_have_bidi_layouts;
+  keymap_class->lookup_key = gdk_macos_keymap_lookup_key;
+  keymap_class->translate_keyboard_state = gdk_macos_keymap_translate_keyboard_state;
+}
+
+static void
+gdk_macos_keymap_init (GdkMacosKeymap *self)
+{
+  CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+                                   self,
+                                   input_sources_changed_notification,
+                                   CFSTR ("AppleSelectedInputSourcesChangedNotification"),
+                                   NULL,
+                                   CFNotificationSuspensionBehaviorDeliverImmediately);
+  gdk_macos_keymap_update (self);
+}
+
+GdkMacosKeymap *
+_gdk_macos_keymap_new (GdkMacosDisplay *display)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  return g_object_new (GDK_TYPE_MACOS_KEYMAP,
+                       "display", display,
+                       NULL);
+}
+
+/* What sort of key event is this? Returns one of
+ * GDK_KEY_PRESS, GDK_KEY_RELEASE, GDK_NOTHING (should be ignored)
+ */
+GdkEventType
+_gdk_macos_keymap_get_event_type (NSEvent *event)
+{
+  unsigned short keycode;
+  unsigned int flags;
+
+  switch ((int)[event type])
+    {
+    case NSEventTypeKeyDown:
+      return GDK_KEY_PRESS;
+    case NSEventTypeKeyUp:
+      return GDK_KEY_RELEASE;
+    case NSEventTypeFlagsChanged:
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  /* For flags-changed events, we have to find the special key that caused the
+   * event, and see if it's in the modifier mask. */
+  keycode = [event keyCode];
+  flags = [event modifierFlags];
+
+  for (guint i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+    {
+      if (modifier_keys[i].keycode == keycode)
+        {
+          if (flags & modifier_keys[i].modmask)
+            return GDK_KEY_PRESS;
+          else
+            return GDK_KEY_RELEASE;
+        }
+    }
+
+  /* Some keypresses (eg: Expose' activations) seem to trigger flags-changed
+   * events for no good reason. Ignore them! */
+  return 0;
+}
+
+gboolean
+_gdk_macos_keymap_is_modifier (guint keycode)
+{
+  for (guint i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+    {
+      if (modifier_keys[i].modmask == 0)
+        break;
+
+      if (modifier_keys[i].keycode == keycode)
+        return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/gdk/macos/gdkmacoskeymap.h b/gdk/macos/gdkmacoskeymap.h
new file mode 100644
index 0000000000..6a8a7e288f
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap.h
@@ -0,0 +1,43 @@
+/*
+ * 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.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_KEYMAP_H__
+#define __GDK_MACOS_KEYMAP_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosKeymap      GdkMacosKeymap;
+typedef struct _GdkMacosKeymapClass GdkMacosKeymapClass;
+
+#define GDK_TYPE_MACOS_KEYMAP       (gdk_macos_keymap_get_type())
+#define GDK_MACOS_KEYMAP(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_KEYMAP, 
GdkMacosKeymap))
+#define GDK_IS_MACOS_KEYMAP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_KEYMAP))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_keymap_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_KEYMAP_H__ */
diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h
new file mode 100644
index 0000000000..3c6f058bd0
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor-private.h
@@ -0,0 +1,39 @@
+/*
+ * 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.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_MONITOR_PRIVATE_H__
+#define __GDK_MACOS_MONITOR_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacosmonitor.h"
+
+#include "gdkmonitorprivate.h"
+
+G_BEGIN_DECLS
+
+GdkMacosMonitor   *_gdk_macos_monitor_new           (GdkMacosDisplay   *display,
+                                                     CGDirectDisplayID  screen_id);
+CGDirectDisplayID  _gdk_macos_monitor_get_screen_id (GdkMacosMonitor   *self);
+gboolean           _gdk_macos_monitor_reconfigure   (GdkMacosMonitor   *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_MONITOR_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c
new file mode 100644
index 0000000000..ade7f0bf42
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.c
@@ -0,0 +1,288 @@
+/*
+ * 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.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 <gdk/gdk.h>
+#include <math.h>
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosMonitor
+{
+  GdkMonitor        parent_instance;
+  CGDirectDisplayID screen_id;
+  NSRect            workarea;
+  guint             has_opengl : 1;
+};
+
+struct _GdkMacosMonitorClass
+{
+  GdkMonitorClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosMonitor, gdk_macos_monitor, GDK_TYPE_MONITOR)
+
+static void
+gdk_macos_monitor_get_workarea (GdkMonitor   *monitor,
+                                GdkRectangle *geometry)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosMonitor *self = (GdkMacosMonitor *)monitor;
+  int x,  y;
+
+  g_assert (GDK_IS_MACOS_MONITOR (self));
+  g_assert (geometry != NULL);
+
+  x = self->workarea.origin.x;
+  y = self->workarea.origin.y + self->workarea.size.height;
+
+  _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (monitor->display),
+                                          x, y,
+                                          &x, &y);
+
+  geometry->x = x;
+  geometry->y = y;
+  geometry->width = self->workarea.size.width;
+  geometry->height = self->workarea.size.height;
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_monitor_class_init (GdkMacosMonitorClass *klass)
+{
+  GdkMonitorClass *monitor_class = GDK_MONITOR_CLASS (klass);
+
+  monitor_class->get_workarea = gdk_macos_monitor_get_workarea;
+}
+
+static void
+gdk_macos_monitor_init (GdkMacosMonitor *self)
+{
+}
+
+static GdkSubpixelLayout
+GetSubpixelLayout (CGDirectDisplayID screen_id)
+{
+#if 0
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkSubpixelLayout subpixel_layout = GDK_SUBPIXEL_LAYOUT_UNKNOWN;
+  io_service_t iosvc;
+  NSDictionary *dict;
+  guint layout;
+  gboolean rotation;
+
+  rotation = CGDisplayRotation (screen_id);
+  iosvc = CGDisplayIOServicePort (screen_id);
+  dict = (NSDictionary *)IODisplayCreateInfoDictionary (iosvc, kIODisplayOnlyPreferredName);
+  layout = [[dict objectForKey:@kDisplaySubPixelLayout] unsignedIntValue];
+
+  switch (layout)
+    {
+    case kDisplaySubPixelLayoutRGB:
+      if (rotation == 0.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_RGB;
+      else if (rotation == 90.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_RGB;
+      else if (rotation == 180.0 || rotation == -180.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_BGR;
+      else if (rotation == -90.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_BGR;
+      break;
+
+    case kDisplaySubPixelLayoutBGR:
+      if (rotation == 0.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_BGR;
+      else if (rotation == 90.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_BGR;
+      else if (rotation == 180.0 || rotation == -180.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_RGB;
+      else if (rotation == -90.0 || rotation == 270.0)
+        subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_RGB;
+      break;
+
+    default:
+      break;
+    }
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return subpixel_layout;
+#endif
+
+  return GDK_SUBPIXEL_LAYOUT_UNKNOWN;
+}
+
+static char *
+GetLocalizedName (NSScreen *screen)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSString *str;
+  char *name;
+
+  g_assert (screen);
+
+  str = [screen localizedName];
+  name = g_strdup ([str UTF8String]);
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return g_steal_pointer (&name);
+}
+
+static gchar *
+GetConnectorName (CGDirectDisplayID screen_id)
+{
+  guint unit = CGDisplayUnitNumber (screen_id);
+  return g_strdup_printf ("unit-%u", unit);
+}
+
+static NSScreen *
+find_screen (CGDirectDisplayID screen_id)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSScreen *screen = NULL;
+
+  for (id obj in [NSScreen screens])
+    {
+      if (screen_id == [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue])
+        {
+          screen = (NSScreen *)obj;
+          break;
+        }
+    }
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return screen;
+}
+
+gboolean
+_gdk_macos_monitor_reconfigure (GdkMacosMonitor *self)
+{
+  GdkSubpixelLayout subpixel_layout;
+  CGDisplayModeRef mode;
+  GdkMacosDisplay *display;
+  NSScreen *screen;
+  GdkRectangle geom;
+  gboolean has_opengl;
+  CGSize size;
+  CGRect bounds;
+  size_t width;
+  size_t pixel_width;
+  gchar *connector;
+  gchar *name;
+  int refresh_rate;
+  int scale_factor = 1;
+  int width_mm;
+  int height_mm;
+
+  g_return_val_if_fail (GDK_IS_MACOS_MONITOR (self), FALSE);
+
+  display = GDK_MACOS_DISPLAY (GDK_MONITOR (self)->display);
+
+  if (!(screen = find_screen (self->screen_id)) ||
+      !(mode = CGDisplayCopyDisplayMode (self->screen_id)))
+    return FALSE;
+
+  size = CGDisplayScreenSize (self->screen_id);
+  bounds = [screen frame];
+  width = CGDisplayModeGetWidth (mode);
+  pixel_width = CGDisplayModeGetPixelWidth (mode);
+  has_opengl = CGDisplayUsesOpenGLAcceleration (self->screen_id);
+  subpixel_layout = GetSubpixelLayout (self->screen_id);
+  name = GetLocalizedName (screen);
+  connector = GetConnectorName (self->screen_id);
+
+  if (width != 0 && pixel_width != 0)
+    scale_factor = MAX (1, pixel_width / width);
+
+  width_mm = size.width;
+  height_mm = size.height;
+
+  geom.x = bounds.origin.x - display->min_x;
+  geom.y = display->height - bounds.origin.y - bounds.size.height + display->min_y;
+  geom.width = bounds.size.width;
+  geom.height = bounds.size.height;
+
+  /* We will often get 0 back from CGDisplayModeGetRefreshRate().  We
+   * can fallback by getting it from CoreVideo based on a CVDisplayLink
+   * setting (which is also used by the frame clock).
+   */
+  if (!(refresh_rate = CGDisplayModeGetRefreshRate (mode)))
+    refresh_rate = _gdk_macos_display_get_nominal_refresh_rate (display);
+
+  gdk_monitor_set_connector (GDK_MONITOR (self), connector);
+  gdk_monitor_set_model (GDK_MONITOR (self), name);
+  gdk_monitor_set_geometry (GDK_MONITOR (self), &geom);
+  gdk_monitor_set_physical_size (GDK_MONITOR (self), width_mm, height_mm);
+  gdk_monitor_set_scale_factor (GDK_MONITOR (self), scale_factor);
+  gdk_monitor_set_refresh_rate (GDK_MONITOR (self), refresh_rate);
+  gdk_monitor_set_subpixel_layout (GDK_MONITOR (self), GDK_SUBPIXEL_LAYOUT_UNKNOWN);
+
+  self->workarea = [screen visibleFrame];
+
+  /* We might be able to use this at some point to change which GSK renderer
+   * we use for surfaces on this monitor.  For example, it might be better
+   * to use cairo if we cannot use OpenGL (or it would be software) anyway.
+   * Presumably that is more common in cases where macOS is running under
+   * an emulator such as QEMU.
+   */
+  self->has_opengl = !!has_opengl;
+
+  CGDisplayModeRelease (mode);
+  g_free (name);
+  g_free (connector);
+
+  return TRUE;
+}
+
+GdkMacosMonitor *
+_gdk_macos_monitor_new (GdkMacosDisplay   *display,
+                        CGDirectDisplayID  screen_id)
+{
+  GdkMacosMonitor *self;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  self = g_object_new (GDK_TYPE_MACOS_MONITOR,
+                       "display", display,
+                       NULL);
+
+  self->screen_id = screen_id;
+
+  _gdk_macos_monitor_reconfigure (self);
+
+  return g_steal_pointer (&self);
+}
+
+CGDirectDisplayID
+_gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_MONITOR (self), 0);
+
+  return self->screen_id;
+}
diff --git a/gdk/macos/gdkmacosmonitor.h b/gdk/macos/gdkmacosmonitor.h
new file mode 100644
index 0000000000..64b91f887e
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.h
@@ -0,0 +1,43 @@
+/*
+ * 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.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_MONITOR_H__
+#define __GDK_MACOS_MONITOR_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosMonitor      GdkMacosMonitor;
+typedef struct _GdkMacosMonitorClass GdkMacosMonitorClass;
+
+#define GDK_TYPE_MACOS_MONITOR       (gdk_macos_monitor_get_type())
+#define GDK_MACOS_MONITOR(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_MONITOR, 
GdkMacosMonitor))
+#define GDK_IS_MACOS_MONITOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_MONITOR))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_monitor_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_MONITOR_H__ */
diff --git a/gdk/macos/gdkmacospopupsurface-private.h b/gdk/macos/gdkmacospopupsurface-private.h
new file mode 100644
index 0000000000..8c63d4f6a0
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface-private.h
@@ -0,0 +1,48 @@
+/*
+ * 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.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_POPUP_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_POPUP_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosPopupSurface      GdkMacosPopupSurface;
+typedef struct _GdkMacosPopupSurfaceClass GdkMacosPopupSurfaceClass;
+
+#define GDK_TYPE_MACOS_POPUP_SURFACE       (_gdk_macos_popup_surface_get_type())
+#define GDK_MACOS_POPUP_SURFACE(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GDK_TYPE_MACOS_POPUP_SURFACE, GdkMacosPopupSurface))
+#define GDK_IS_MACOS_POPUP_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GDK_TYPE_MACOS_POPUP_SURFACE))
+
+GType            _gdk_macos_popup_surface_get_type           (void);
+GdkMacosSurface *_gdk_macos_popup_surface_new                (GdkMacosDisplay      *display,
+                                                              GdkSurface           *parent,
+                                                              GdkFrameClock        *frame_clock,
+                                                              int                   x,
+                                                              int                   y,
+                                                              int                   width,
+                                                              int                   height);
+void             _gdk_macos_popup_surface_attach_to_parent   (GdkMacosPopupSurface *self);
+void             _gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self);
+void             _gdk_macos_popup_surface_reposition         (GdkMacosPopupSurface *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_POPUP_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c
new file mode 100644
index 0000000000..520a717dfc
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface.c
@@ -0,0 +1,378 @@
+/*
+ * 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.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"
+
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+#include "gdkpopupprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosPopupSurface
+{
+  GdkMacosSurface parent_instance;
+  GdkPopupLayout *layout;
+};
+
+struct _GdkMacosPopupSurfaceClass
+{
+  GdkMacosSurfaceClass parent_class;
+};
+
+static void
+gdk_macos_popup_surface_layout (GdkMacosPopupSurface *self,
+                                int                   width,
+                                int                   height,
+                                GdkPopupLayout       *layout)
+{
+  GdkRectangle final_rect;
+  int x, y;
+
+  g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
+  g_assert (layout != NULL);
+  g_assert (GDK_SURFACE (self)->parent);
+
+  if (layout != self->layout)
+    {
+      g_clear_pointer (&self->layout, gdk_popup_layout_unref);
+      self->layout = gdk_popup_layout_ref (layout);
+    }
+
+  gdk_surface_layout_popup_helper (GDK_SURFACE (self),
+                                   width,
+                                   height,
+                                   layout,
+                                   &final_rect);
+
+  gdk_surface_get_origin (GDK_SURFACE (self)->parent, &x, &y);
+
+  x += final_rect.x;
+  y += final_rect.y;
+
+  if (final_rect.width != GDK_SURFACE (self)->width ||
+      final_rect.height != GDK_SURFACE (self)->height)
+    _gdk_macos_surface_move_resize (GDK_MACOS_SURFACE (self),
+                                    x,
+                                    y,
+                                    final_rect.width,
+                                    final_rect.height);
+  else if (x != GDK_MACOS_SURFACE (self)->root_x ||
+           y != GDK_MACOS_SURFACE (self)->root_y)
+    _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y);
+  else
+    return;
+
+  gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+}
+
+static void
+show_popup (GdkMacosPopupSurface *self)
+{
+  _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
+}
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  show_popup (GDK_MACOS_POPUP_SURFACE (surface));
+}
+
+static gboolean
+gdk_macos_popup_surface_present (GdkPopup       *popup,
+                                 int             width,
+                                 int             height,
+                                 GdkPopupLayout *layout)
+{
+  GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)popup;
+
+  g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+  gdk_macos_popup_surface_layout (self, width, height, layout);
+
+  GDK_MACOS_SURFACE (self)->did_initial_present = TRUE;
+
+  if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)))
+    return TRUE;
+
+  if (GDK_SURFACE (self)->autohide)
+    {
+      GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (popup));
+      GdkSeat *seat = gdk_display_get_default_seat (display);
+
+      gdk_seat_grab (seat,
+                     GDK_SURFACE (self),
+                     GDK_SEAT_CAPABILITY_ALL,
+                     TRUE,
+                     NULL, NULL,
+                     show_grabbing_popup,
+                     NULL);
+    }
+  else
+    {
+      show_popup (GDK_MACOS_POPUP_SURFACE (self));
+    }
+
+  return GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
+}
+
+static GdkGravity
+gdk_macos_popup_surface_get_surface_anchor (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->popup.surface_anchor;
+}
+
+static GdkGravity
+gdk_macos_popup_surface_get_rect_anchor (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->popup.rect_anchor;
+}
+
+static int
+gdk_macos_popup_surface_get_position_x (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->x;
+}
+
+static int
+gdk_macos_popup_surface_get_position_y (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->y;
+}
+
+static void
+popup_interface_init (GdkPopupInterface *iface)
+{
+  iface->present = gdk_macos_popup_surface_present;
+  iface->get_surface_anchor = gdk_macos_popup_surface_get_surface_anchor;
+  iface->get_rect_anchor = gdk_macos_popup_surface_get_rect_anchor;
+  iface->get_position_x = gdk_macos_popup_surface_get_position_x;
+  iface->get_position_y = gdk_macos_popup_surface_get_position_y;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosPopupSurface, _gdk_macos_popup_surface, GDK_TYPE_MACOS_SURFACE,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP, popup_interface_init))
+
+enum {
+  PROP_0,
+  LAST_PROP,
+};
+
+static void
+_gdk_macos_popup_surface_finalize (GObject *object)
+{
+  GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)object;
+
+  g_clear_object (&GDK_SURFACE (self)->parent);
+  g_clear_pointer (&self->layout, gdk_popup_layout_unref);
+
+  G_OBJECT_CLASS (_gdk_macos_popup_surface_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_popup_surface_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  GdkSurface *surface = GDK_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      g_value_set_object (value, surface->parent);
+      break;
+
+    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+      g_value_set_boolean (value, surface->autohide);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+_gdk_macos_popup_surface_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  GdkSurface *surface = GDK_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      surface->parent = g_value_dup_object (value);
+      if (surface->parent)
+        surface->parent->children = g_list_prepend (surface->parent->children, surface);
+      break;
+
+    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+      surface->autohide = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+_gdk_macos_popup_surface_class_init (GdkMacosPopupSurfaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = _gdk_macos_popup_surface_finalize;
+  object_class->get_property = _gdk_macos_popup_surface_get_property;
+  object_class->set_property = _gdk_macos_popup_surface_set_property;
+
+  gdk_popup_install_properties (object_class, 1);
+}
+
+static void
+_gdk_macos_popup_surface_init (GdkMacosPopupSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_popup_surface_new (GdkMacosDisplay *display,
+                              GdkSurface      *parent,
+                              GdkFrameClock   *frame_clock,
+                              int              x,
+                              int              y,
+                              int              width,
+                              int              height)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosWindow *window;
+  GdkMacosSurface *self;
+  NSScreen *screen;
+  NSUInteger style_mask;
+  NSRect content_rect;
+  NSRect screen_rect;
+  int nx;
+  int ny;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+  g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+  g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
+
+  style_mask = NSWindowStyleMaskBorderless;
+
+  _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+  screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+  screen_rect = [screen frame];
+  nx -= screen_rect.origin.x;
+  ny -= screen_rect.origin.y;
+  content_rect = NSMakeRect (nx, ny - height, width, height);
+
+  window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+                                             styleMask:style_mask
+                                               backing:NSBackingStoreBuffered
+                                                 defer:NO
+                                                screen:screen];
+
+  [window setOpaque:NO];
+  [window setBackgroundColor:[NSColor clearColor]];
+  [window setDecorated:NO];
+
+#if 0
+  /* NOTE: We could set these to be popup level, but then
+   * [NSApp orderedWindows] would not give us the windows
+   * back with the stacking order applied.
+   */
+  [window setLevel:NSPopUpMenuWindowLevel];
+#endif
+
+  self = g_object_new (GDK_TYPE_MACOS_POPUP_SURFACE,
+                       "display", display,
+                       "frame-clock", frame_clock,
+                       "native", window,
+                       "parent", parent,
+                       NULL);
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_popup_surface_attach_to_parent (GdkMacosPopupSurface *self)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
+    {
+      NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
+      NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+      [parent addChildWindow:window ordered:NSWindowAbove];
+
+      _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
+    }
+}
+
+void
+_gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
+    {
+      NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
+      NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+      [parent removeChildWindow:window];
+
+      _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
+    }
+}
+
+void
+_gdk_macos_popup_surface_reposition (GdkMacosPopupSurface *self)
+{
+  g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+  if (self->layout == NULL ||
+      !gdk_surface_get_mapped (GDK_SURFACE (self)) ||
+      GDK_SURFACE (self)->parent == NULL)
+    return;
+
+  gdk_macos_popup_surface_layout (self,
+                                  GDK_SURFACE (self)->width,
+                                  GDK_SURFACE (self)->height,
+                                  self->layout);
+}
diff --git a/gdk/macos/gdkmacosseat-private.h b/gdk/macos/gdkmacosseat-private.h
new file mode 100644
index 0000000000..57e5605018
--- /dev/null
+++ b/gdk/macos/gdkmacosseat-private.h
@@ -0,0 +1,35 @@
+/*
+ * 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.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_SEAT_PRIVATE_H__
+#define __GDK_MACOS_SEAT_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+
+#include "gdkseatprivate.h"
+
+G_BEGIN_DECLS
+
+GdkSeat *_gdk_macos_seat_new (GdkMacosDisplay *display);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SEAT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosseat.c b/gdk/macos/gdkmacosseat.c
new file mode 100644
index 0000000000..cb3c3747e1
--- /dev/null
+++ b/gdk/macos/gdkmacosseat.c
@@ -0,0 +1,65 @@
+/*
+ * 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.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 <gdk/gdk.h>
+
+#include "gdkdeviceprivate.h"
+#include "gdkseatdefaultprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosseat-private.h"
+
+GdkSeat *
+_gdk_macos_seat_new (GdkMacosDisplay *display)
+{
+  GdkDevice *core_keyboard;
+  GdkDevice *core_pointer;
+  GdkSeat *seat;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  core_pointer = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                               "name", "Core Pointer",
+                               "type", GDK_DEVICE_TYPE_LOGICAL,
+                               "source", GDK_SOURCE_MOUSE,
+                               "has-cursor", TRUE,
+                               "display", display,
+                               NULL);
+  core_keyboard = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                                "name", "Core Keyboard",
+                                "type", GDK_DEVICE_TYPE_LOGICAL,
+                                "source", GDK_SOURCE_KEYBOARD,
+                                "has-cursor", FALSE,
+                                "display", display,
+                                NULL);
+
+  _gdk_device_set_associated_device (GDK_DEVICE (core_pointer),
+                                     GDK_DEVICE (core_keyboard));
+  _gdk_device_set_associated_device (GDK_DEVICE (core_keyboard),
+                                     GDK_DEVICE (core_pointer));
+
+  seat = gdk_seat_default_new_for_logical_pair (core_pointer, core_keyboard);
+
+  g_object_unref (core_pointer);
+  g_object_unref (core_keyboard);
+
+  return g_steal_pointer (&seat);
+}
diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h
new file mode 100644
index 0000000000..f5a52db3cb
--- /dev/null
+++ b/gdk/macos/gdkmacossurface-private.h
@@ -0,0 +1,131 @@
+/*
+ * 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.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_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_SURFACE_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <cairo.h>
+
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+
+#import "GdkMacosWindow.h"
+
+G_BEGIN_DECLS
+
+#define GDK_MACOS_SURFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_MACOS_SURFACE, 
GdkMacosSurfaceClass))
+#define GDK_IS_MACOS_SURFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_MACOS_SURFACE))
+#define GDK_MACOS_SURFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_MACOS_SURFACE, 
GdkMacosSurfaceClass))
+
+struct _GdkMacosSurface
+{
+  GdkSurface parent_instance;
+
+  GList main;
+  GList sorted;
+  GList frame;
+
+  GdkMacosWindow *window;
+  GPtrArray *monitors;
+  cairo_region_t *input_region;
+  char *title;
+
+  int root_x;
+  int root_y;
+
+  int shadow_top;
+  int shadow_right;
+  int shadow_bottom;
+  int shadow_left;
+
+  gint64 pending_frame_counter;
+
+  guint did_initial_present : 1;
+};
+
+struct _GdkMacosSurfaceClass
+{
+  GdkSurfaceClass parent_class;
+};
+
+GdkMacosSurface   *_gdk_macos_surface_new                     (GdkMacosDisplay    *display,
+                                                               GdkSurfaceType      surface_type,
+                                                               GdkSurface         *parent,
+                                                               int                 x,
+                                                               int                 y,
+                                                               int                 width,
+                                                               int                 height);
+NSWindow          *_gdk_macos_surface_get_native              (GdkMacosSurface    *self);
+CGDirectDisplayID  _gdk_macos_surface_get_screen_id           (GdkMacosSurface    *self);
+const char        *_gdk_macos_surface_get_title               (GdkMacosSurface    *self);
+void               _gdk_macos_surface_set_title               (GdkMacosSurface    *self,
+                                                               const gchar        *title);
+void               _gdk_macos_surface_get_shadow              (GdkMacosSurface    *self,
+                                                               gint               *top,
+                                                               gint               *right,
+                                                               gint               *bottom,
+                                                               gint               *left);
+NSView            *_gdk_macos_surface_get_view                (GdkMacosSurface    *self);
+gboolean           _gdk_macos_surface_get_modal_hint          (GdkMacosSurface    *self);
+void               _gdk_macos_surface_set_modal_hint          (GdkMacosSurface    *self,
+                                                               gboolean            modal_hint);
+void               _gdk_macos_surface_set_geometry_hints      (GdkMacosSurface    *self,
+                                                               const GdkGeometry  *geometry,
+                                                               GdkSurfaceHints     geom_mask);
+void               _gdk_macos_surface_resize                  (GdkMacosSurface    *self,
+                                                               int                 width,
+                                                               int                 height);
+void               _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface    *self);
+void               _gdk_macos_surface_update_position         (GdkMacosSurface    *self);
+void               _gdk_macos_surface_show                    (GdkMacosSurface    *self);
+void               _gdk_macos_surface_thaw                    (GdkMacosSurface    *self,
+                                                               gint64              
predicted_presentation_time,
+                                                               gint64              refresh_interval);
+CGContextRef       _gdk_macos_surface_acquire_context         (GdkMacosSurface    *self,
+                                                               gboolean            clear_scale,
+                                                               gboolean            antialias);
+void               _gdk_macos_surface_release_context         (GdkMacosSurface    *self,
+                                                               CGContextRef        cg_context);
+void               _gdk_macos_surface_synthesize_null_key     (GdkMacosSurface    *self);
+void               _gdk_macos_surface_move                    (GdkMacosSurface    *self,
+                                                               int                 x,
+                                                               int                 y);
+void               _gdk_macos_surface_move_resize             (GdkMacosSurface    *self,
+                                                               int                 x,
+                                                               int                 y,
+                                                               int                 width,
+                                                               int                 height);
+gboolean           _gdk_macos_surface_is_tracking             (GdkMacosSurface    *self,
+                                                               NSTrackingArea     *area);
+void               _gdk_macos_surface_monitor_changed         (GdkMacosSurface    *self);
+GdkMonitor        *_gdk_macos_surface_get_best_monitor        (GdkMacosSurface    *self);
+void               _gdk_macos_surface_reposition_children     (GdkMacosSurface    *self);
+void               _gdk_macos_surface_set_opacity             (GdkMacosSurface    *self,
+                                                               double              opacity);
+void               _gdk_macos_surface_get_root_coords         (GdkMacosSurface    *self,
+                                                               int                *x,
+                                                               int                *y);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c
new file mode 100644
index 0000000000..83fad33ffd
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.c
@@ -0,0 +1,1071 @@
+/*
+ * 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.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 <AppKit/AppKit.h>
+#include <float.h>
+#include <gdk/gdk.h>
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkdeviceprivate.h"
+#include "gdkdisplay.h"
+#include "gdkframeclockidleprivate.h"
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdrag-private.h"
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacosglcontext-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+G_DEFINE_ABSTRACT_TYPE (GdkMacosSurface, gdk_macos_surface, GDK_TYPE_SURFACE)
+
+enum {
+  PROP_0,
+  PROP_NATIVE,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+window_is_fullscreen (GdkMacosSurface *self)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0;
+}
+
+void
+_gdk_macos_surface_reposition_children (GdkMacosSurface *self)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  if (!gdk_surface_get_mapped (GDK_SURFACE (self)))
+    return;
+
+  for (const GList *iter = GDK_SURFACE (self)->children;
+       iter != NULL;
+       iter = iter->next)
+    {
+      GdkMacosSurface *child = iter->data;
+
+      g_assert (GDK_IS_MACOS_SURFACE (child));
+
+      if (GDK_IS_MACOS_POPUP_SURFACE (child))
+        _gdk_macos_popup_surface_reposition (GDK_MACOS_POPUP_SURFACE (child));
+    }
+
+  if (GDK_IS_POPUP (self) || self->did_initial_present)
+    g_signal_emit_by_name (self, "popup-layout-changed");
+}
+
+static void
+gdk_macos_surface_set_input_region (GdkSurface     *surface,
+                                    cairo_region_t *region)
+{
+}
+
+static void
+gdk_macos_surface_set_opaque_region (GdkSurface     *surface,
+                                     cairo_region_t *region)
+{
+  NSView *nsview;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface))) &&
+      GDK_IS_MACOS_CAIRO_VIEW (nsview))
+    [(GdkMacosCairoView *)nsview setOpaqueRegion:region];
+}
+
+static void
+gdk_macos_surface_hide (GdkSurface *surface)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+  GdkSeat *seat;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  seat = gdk_display_get_default_seat (surface->display);
+  gdk_seat_ungrab (seat);
+
+  [self->window hide];
+
+  _gdk_surface_clear_update_area (surface);
+}
+
+static gint
+gdk_macos_surface_get_scale_factor (GdkSurface *surface)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  return [self->window backingScaleFactor];
+}
+
+static void
+gdk_macos_surface_set_shadow_width (GdkSurface *surface,
+                                    int         left,
+                                    int         right,
+                                    int         top,
+                                    int         bottom)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  self->shadow_top = top;
+  self->shadow_right = right;
+  self->shadow_bottom = bottom;
+  self->shadow_left = left;
+
+  if (top || right || bottom || left)
+    [self->window setHasShadow:NO];
+}
+
+static void
+gdk_macos_surface_begin_frame (GdkMacosSurface *self)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+}
+
+static void
+gdk_macos_surface_end_frame (GdkMacosSurface *self)
+{
+  GdkFrameTimings *timings;
+  GdkFrameClock *frame_clock;
+  GdkDisplay *display;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  display = gdk_surface_get_display (GDK_SURFACE (self));
+  frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self));
+
+  if ((timings = gdk_frame_clock_get_current_timings (frame_clock)))
+    self->pending_frame_counter = timings->frame_counter;
+
+  _gdk_macos_display_add_frame_callback (GDK_MACOS_DISPLAY (display), self);
+
+  gdk_surface_freeze_updates (GDK_SURFACE (self));
+}
+
+static void
+gdk_macos_surface_before_paint (GdkMacosSurface *self,
+                                GdkFrameClock   *frame_clock)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+  g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+
+  if (surface->update_freeze_count > 0)
+    return;
+
+  gdk_macos_surface_begin_frame (self);
+}
+
+static void
+gdk_macos_surface_after_paint (GdkMacosSurface *self,
+                               GdkFrameClock   *frame_clock)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+  g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+
+  if (surface->update_freeze_count > 0)
+    return;
+
+  gdk_macos_surface_end_frame (self);
+}
+
+static void
+gdk_macos_surface_get_root_coords (GdkSurface *surface,
+                                   int         x,
+                                   int         y,
+                                   int        *root_x,
+                                   int        *root_y)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (root_x)
+    *root_x = self->root_x + x;
+
+  if (root_y)
+    *root_y = self->root_y + y;
+}
+
+static gboolean
+gdk_macos_surface_get_device_state (GdkSurface      *surface,
+                                    GdkDevice       *device,
+                                    gdouble         *x,
+                                    gdouble         *y,
+                                    GdkModifierType *mask)
+{
+  GdkDisplay *display;
+  NSWindow *nswindow;
+  NSPoint point;
+  int x_tmp;
+  int y_tmp;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (x != NULL);
+  g_assert (y != NULL);
+  g_assert (mask != NULL);
+
+  display = gdk_surface_get_display (surface);
+  nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+  point = [nswindow mouseLocationOutsideOfEventStream];
+
+  _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+                                          point.x, point.y,
+                                          &x_tmp, &y_tmp);
+
+  *x = x_tmp;
+  *y = x_tmp;
+
+  return TRUE;
+}
+
+static void
+gdk_macos_surface_get_geometry (GdkSurface *surface,
+                                int        *x,
+                                int        *y,
+                                int        *width,
+                                int        *height)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  if (x != NULL)
+    *x = surface->x;
+
+  if (y != NULL)
+    *y = surface->y;
+
+  if (width != NULL)
+    *width = surface->width;
+
+  if (height != NULL)
+    *height = surface->height;
+}
+
+static GdkDrag *
+gdk_macos_surface_drag_begin (GdkSurface         *surface,
+                              GdkDevice          *device,
+                              GdkContentProvider *content,
+                              GdkDragAction       actions,
+                              double              dx,
+                              double              dy)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+  GdkMacosSurface *drag_surface;
+  GdkMacosDrag *drag;
+  GdkCursor *cursor;
+  GdkSeat *seat;
+  double px;
+  double py;
+  int sx;
+  int sy;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self) ||
+            GDK_IS_MACOS_POPUP_SURFACE (self));
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (GDK_IS_CONTENT_PROVIDER (content));
+
+  seat = gdk_device_get_seat (device);
+  _gdk_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,
+                                         -99, -99, 1, 1);
+  drag = g_object_new (GDK_TYPE_MACOS_DRAG,
+                       "drag-surface", drag_surface,
+                       "surface", surface,
+                       "device", device,
+                       "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);
+
+  if (!_gdk_macos_drag_begin (drag))
+    {
+      g_object_unref (drag);
+      return NULL;
+    }
+
+  /* Hold a reference until drop_done is called */
+  g_object_ref (drag);
+
+  return GDK_DRAG (g_steal_pointer (&drag));
+}
+
+static GdkGLContext *
+gdk_macos_surface_create_gl_context (GdkSurface    *surface,
+                                     gboolean       attached,
+                                     GdkGLContext  *share,
+                                     GError       **error)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+  g_assert (!share || GDK_IS_GL_CONTEXT (share));
+
+  return _gdk_macos_gl_context_new (self, attached, share, error);
+}
+
+static void
+gdk_macos_surface_destroy (GdkSurface *surface,
+                           gboolean    foreign_destroy)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+  GdkMacosWindow *window = g_steal_pointer (&self->window);
+
+  g_clear_pointer (&self->title, g_free);
+
+  if (window != NULL)
+    [window close];
+
+  _gdk_macos_display_surface_removed (GDK_MACOS_DISPLAY (surface->display), self);
+
+  g_clear_pointer (&self->monitors, g_ptr_array_unref);
+
+  g_assert (self->sorted.prev == NULL);
+  g_assert (self->sorted.next == NULL);
+  g_assert (self->frame.prev == NULL);
+  g_assert (self->frame.next == NULL);
+  g_assert (self->main.prev == NULL);
+  g_assert (self->main.next == NULL);
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_surface_constructed (GObject *object)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)object;
+  GdkFrameClock *frame_clock;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  G_OBJECT_CLASS (gdk_macos_surface_parent_class)->constructed (object);
+
+  if (self->window != NULL)
+    {
+      NSRect bounds = [[self->window contentView] bounds];
+
+      GDK_SURFACE (self)->width = bounds.size.width;
+      GDK_SURFACE (self)->height = bounds.size.height;
+      _gdk_macos_surface_update_position (self);
+    }
+
+  if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
+    {
+      g_signal_connect_object (frame_clock,
+                               "before-paint",
+                               G_CALLBACK (gdk_macos_surface_before_paint),
+                               self,
+                               G_CONNECT_SWAPPED);
+      g_signal_connect_object (frame_clock,
+                               "after-paint",
+                               G_CALLBACK (gdk_macos_surface_after_paint),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+}
+
+static void
+gdk_macos_surface_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NATIVE:
+      g_value_set_pointer (value, self->window);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gdk_macos_surface_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NATIVE:
+      self->window = g_value_get_pointer (value);
+      [self->window setGdkSurface:self];
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gdk_macos_surface_class_init (GdkMacosSurfaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass);
+
+  object_class->constructed = gdk_macos_surface_constructed;
+  object_class->get_property = gdk_macos_surface_get_property;
+  object_class->set_property = gdk_macos_surface_set_property;
+
+  surface_class->create_gl_context = gdk_macos_surface_create_gl_context;
+  surface_class->destroy = gdk_macos_surface_destroy;
+  surface_class->drag_begin = gdk_macos_surface_drag_begin;
+  surface_class->get_device_state = gdk_macos_surface_get_device_state;
+  surface_class->get_geometry = gdk_macos_surface_get_geometry;
+  surface_class->get_root_coords = gdk_macos_surface_get_root_coords;
+  surface_class->get_scale_factor = gdk_macos_surface_get_scale_factor;
+  surface_class->hide = gdk_macos_surface_hide;
+  surface_class->set_input_region = gdk_macos_surface_set_input_region;
+  surface_class->set_opaque_region = gdk_macos_surface_set_opaque_region;
+  surface_class->set_shadow_width = gdk_macos_surface_set_shadow_width;
+
+  properties [PROP_NATIVE] =
+    g_param_spec_pointer ("native",
+                          "Native",
+                          "The native NSWindow",
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gdk_macos_surface_init (GdkMacosSurface *self)
+{
+  self->frame.data = self;
+  self->main.data = self;
+  self->sorted.data = self;
+  self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+GdkMacosSurface *
+_gdk_macos_surface_new (GdkMacosDisplay   *display,
+                        GdkSurfaceType     surface_type,
+                        GdkSurface        *parent,
+                        int                x,
+                        int                y,
+                        int                width,
+                        int                height)
+{
+  GdkFrameClock *frame_clock;
+  GdkMacosSurface *ret;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+  if (parent != NULL)
+    frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent));
+  else
+    frame_clock = _gdk_frame_clock_idle_new ();
+
+  switch (surface_type)
+    {
+    case GDK_SURFACE_TOPLEVEL:
+      ret = _gdk_macos_toplevel_surface_new (display, parent, frame_clock, x, y, width, height);
+      break;
+
+    case GDK_SURFACE_POPUP:
+      ret = _gdk_macos_popup_surface_new (display, parent, frame_clock, x, y, width, height);
+      break;
+
+    case GDK_SURFACE_TEMP:
+      ret = _gdk_macos_drag_surface_new (display, frame_clock, x, y, width, height);
+      break;
+
+    default:
+      g_warn_if_reached ();
+      ret = NULL;
+    }
+
+  if (ret != NULL)
+    _gdk_macos_surface_monitor_changed (ret);
+
+  g_object_unref (frame_clock);
+
+  return g_steal_pointer (&ret);
+}
+
+void
+_gdk_macos_surface_get_shadow (GdkMacosSurface *self,
+                               gint            *top,
+                               gint            *right,
+                               gint            *bottom,
+                               gint            *left)
+{
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (top)
+    *top = self->shadow_top;
+
+  if (left)
+    *left = self->shadow_left;
+
+  if (bottom)
+    *bottom = self->shadow_bottom;
+
+  if (right)
+    *right = self->shadow_right;
+}
+
+const char *
+_gdk_macos_surface_get_title (GdkMacosSurface *self)
+{
+
+  return self->title;
+}
+
+void
+_gdk_macos_surface_set_title (GdkMacosSurface *self,
+                              const gchar     *title)
+{
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (title == NULL)
+    title = "";
+
+  if (g_strcmp0 (self->title, title) != 0)
+    {
+      g_free (self->title);
+      self->title = g_strdup (title);
+
+      GDK_BEGIN_MACOS_ALLOC_POOL;
+      [self->window setTitle:[NSString stringWithUTF8String:title]];
+      GDK_END_MACOS_ALLOC_POOL;
+    }
+}
+
+CGDirectDisplayID
+_gdk_macos_surface_get_screen_id (GdkMacosSurface *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), (CGDirectDisplayID)-1);
+
+  if (self->window != NULL)
+    {
+      NSScreen *screen = [self->window screen];
+      return [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+    }
+
+  return (CGDirectDisplayID)-1;
+}
+
+NSWindow *
+_gdk_macos_surface_get_native (GdkMacosSurface *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+  return (NSWindow *)self->window;
+}
+
+void
+_gdk_macos_surface_set_geometry_hints (GdkMacosSurface   *self,
+                                       const GdkGeometry *geometry,
+                                       GdkSurfaceHints    geom_mask)
+{
+  NSSize max_size;
+  NSSize min_size;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+  g_return_if_fail (geometry != NULL);
+  g_return_if_fail (self->window != NULL);
+
+  if (geom_mask & GDK_HINT_POS) { /* TODO */ }
+  if (geom_mask & GDK_HINT_USER_POS) { /* TODO */ }
+  if (geom_mask & GDK_HINT_USER_SIZE) { /* TODO */ }
+
+  if (geom_mask & GDK_HINT_MAX_SIZE)
+    max_size = NSMakeSize (geometry->max_width, geometry->max_height);
+  else
+    max_size = NSMakeSize (FLT_MAX, FLT_MAX);
+  [self->window setContentMaxSize:max_size];
+
+  if (geom_mask & GDK_HINT_MIN_SIZE)
+    min_size = NSMakeSize (geometry->min_width, geometry->min_height);
+  else
+    min_size = NSMakeSize (1, 1);
+  [self->window setContentMinSize:min_size];
+
+  if (geom_mask & GDK_HINT_BASE_SIZE) { /* TODO */ }
+
+  if (geom_mask & GDK_HINT_RESIZE_INC)
+    {
+      NSSize size = NSMakeSize (geometry->width_inc, geometry->height_inc);
+      [self->window setContentResizeIncrements:size];
+    }
+
+  if (geom_mask & GDK_HINT_ASPECT)
+    {
+      NSSize size;
+
+      if (geometry->min_aspect != geometry->max_aspect)
+        g_warning ("Only equal minimum and maximum aspect ratios are supported on Mac OS. Using minimum 
aspect ratio...");
+
+      size.width = geometry->min_aspect;
+      size.height = 1.0;
+
+      [self->window setContentAspectRatio:size];
+    }
+
+  if (geom_mask & GDK_HINT_WIN_GRAVITY) { /* TODO */ }
+}
+
+void
+_gdk_macos_surface_resize (GdkMacosSurface *self,
+                           int              width,
+                           int              height)
+{
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  _gdk_macos_surface_move_resize (self, -1, -1, width, height);
+}
+
+void
+_gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self)
+{
+  GdkSurfaceState state;
+  gboolean is_fullscreen;
+  gboolean was_fullscreen;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  state = GDK_SURFACE (self)->state;
+  is_fullscreen = window_is_fullscreen (self);
+  was_fullscreen = (state & GDK_SURFACE_STATE_FULLSCREEN) != 0;
+
+  if (is_fullscreen != was_fullscreen)
+    {
+      if (is_fullscreen)
+        gdk_synthesize_surface_state (GDK_SURFACE (self), 0, GDK_SURFACE_STATE_FULLSCREEN);
+      else
+        gdk_synthesize_surface_state (GDK_SURFACE (self), GDK_SURFACE_STATE_FULLSCREEN, 0);
+    }
+}
+
+void
+_gdk_macos_surface_update_position (GdkMacosSurface *self)
+{
+  GdkSurface *surface = GDK_SURFACE (self);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  NSRect frame_rect = [self->window frame];
+  NSRect content_rect = [self->window contentRectForFrameRect:frame_rect];
+
+  _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+                                          content_rect.origin.x,
+                                          content_rect.origin.y + content_rect.size.height,
+                                          &self->root_x, &self->root_y);
+
+  if (surface->parent != NULL)
+    {
+      surface->x = self->root_x - GDK_MACOS_SURFACE (surface->parent)->root_x;
+      surface->y = self->root_y - GDK_MACOS_SURFACE (surface->parent)->root_y;
+    }
+  else
+    {
+      surface->x = self->root_x;
+      surface->y = self->root_y;
+    }
+}
+
+void
+_gdk_macos_surface_thaw (GdkMacosSurface *self,
+                         gint64           presentation_time,
+                         gint64           refresh_interval)
+{
+  GdkFrameTimings *timings;
+  GdkFrameClock *frame_clock;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  gdk_surface_thaw_updates (GDK_SURFACE (self));
+
+  frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self));
+
+  if (self->pending_frame_counter)
+    {
+      timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter);
+
+      if (timings != NULL)
+        timings->presentation_time = presentation_time - refresh_interval;
+
+      self->pending_frame_counter = 0;
+    }
+
+  timings = gdk_frame_clock_get_current_timings (frame_clock);
+
+  if (timings != NULL)
+    {
+      timings->refresh_interval = refresh_interval;
+      timings->predicted_presentation_time = presentation_time;
+    }
+}
+
+void
+_gdk_macos_surface_show (GdkMacosSurface *self)
+{
+  gboolean was_mapped;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
+
+  if (!was_mapped)
+    gdk_synthesize_surface_state (GDK_SURFACE (self), GDK_SURFACE_STATE_WITHDRAWN, 0);
+
+  _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display));
+
+  [self->window showAndMakeKey:YES];
+
+  if (!was_mapped)
+    {
+      if (gdk_surface_get_mapped (GDK_SURFACE (self)))
+        {
+          _gdk_macos_surface_update_position (self);
+          gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+        }
+    }
+
+  [[self->window contentView] setNeedsDisplay:YES];
+}
+
+CGContextRef
+_gdk_macos_surface_acquire_context (GdkMacosSurface *self,
+                                    gboolean         clear_scale,
+                                    gboolean         antialias)
+{
+  CGContextRef cg_context;
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return NULL;
+
+  if (!(cg_context = [[NSGraphicsContext currentContext] CGContext]))
+    return NULL;
+
+  CGContextSaveGState (cg_context);
+
+  if (!antialias)
+    CGContextSetAllowsAntialiasing (cg_context, antialias);
+
+  if (clear_scale)
+    {
+      CGSize scale;
+
+      scale = CGSizeMake (1.0, 1.0);
+      scale = CGContextConvertSizeToDeviceSpace (cg_context, scale);
+
+      CGContextScaleCTM (cg_context, 1.0 / scale.width, 1.0 / scale.height);
+    }
+
+  return cg_context;
+}
+
+void
+_gdk_macos_surface_release_context (GdkMacosSurface *self,
+                                    CGContextRef     cg_context)
+{
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  CGContextRestoreGState (cg_context);
+  CGContextSetAllowsAntialiasing (cg_context, TRUE);
+}
+
+void
+_gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self)
+{
+  GdkTranslatedKey translated = {0};
+  GdkTranslatedKey no_lock = {0};
+  GdkDisplay *display;
+  GdkEvent *event;
+  GdkSeat *seat;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  translated.keyval = GDK_KEY_VoidSymbol;
+  no_lock.keyval = GDK_KEY_VoidSymbol;
+
+  display = gdk_surface_get_display (GDK_SURFACE (self));
+  seat = gdk_display_get_default_seat (display);
+  event = gdk_key_event_new (GDK_KEY_PRESS,
+                             GDK_SURFACE (self),
+                             gdk_seat_get_keyboard (seat),
+                             NULL,
+                             GDK_CURRENT_TIME,
+                             0,
+                             0,
+                             FALSE,
+                             &translated,
+                             &no_lock);
+  _gdk_event_queue_append (display, event);
+}
+
+void
+_gdk_macos_surface_move (GdkMacosSurface *self,
+                         int              x,
+                         int              y)
+{
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  _gdk_macos_surface_move_resize (self, x, y, -1, -1);
+}
+
+void
+_gdk_macos_surface_move_resize (GdkMacosSurface *self,
+                                int              x,
+                                int              y,
+                                int              width,
+                                int              height)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+  GdkDisplay *display;
+  NSRect content_rect;
+  NSRect frame_rect;
+  gboolean size_changed;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if ((x == -1 || (x == self->root_x)) &&
+      (y == -1 || (y == self->root_y)) &&
+      (width == -1 || (width == surface->width)) &&
+      (height == -1 || (height == surface->height)))
+    return;
+
+  display = gdk_surface_get_display (surface);
+
+  if (width == -1)
+    width = surface->width;
+
+  if (height == -1)
+    height = surface->height;
+
+  if (x == -1)
+    x = self->root_x;
+
+  if (y == -1)
+    y = self->root_y;
+
+  size_changed = height != surface->height || width != surface->width;
+
+  if (GDK_IS_MACOS_SURFACE (surface->parent))
+    {
+      surface->x = x - GDK_MACOS_SURFACE (surface->parent)->root_x;
+      surface->y = y - GDK_MACOS_SURFACE (surface->parent)->root_y;
+    }
+  else
+    {
+      surface->x = x;
+      surface->y = y;
+    }
+
+  _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
+                                        x, y + height, &x, &y);
+
+  content_rect = NSMakeRect (x, y, width, height);
+  frame_rect = [self->window frameRectForContentRect:content_rect];
+  [self->window setFrame:frame_rect display:YES];
+
+  if (size_changed)
+    gdk_surface_invalidate_rect (surface, NULL);
+}
+
+gboolean
+_gdk_macos_surface_is_tracking (GdkMacosSurface *self,
+                                NSTrackingArea  *area)
+{
+  GdkMacosBaseView *view;
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE);
+
+  if (self->window == NULL)
+    return FALSE;
+
+  view = (GdkMacosBaseView *)[self->window contentView];
+  if (view == NULL)
+    return FALSE;
+
+  return [view trackingArea] == area;
+}
+
+void
+_gdk_macos_surface_monitor_changed (GdkMacosSurface *self)
+{
+  GListModel *monitors;
+  GdkRectangle rect;
+  GdkRectangle intersect;
+  GdkDisplay *display;
+  GdkMonitor *monitor;
+  guint n_monitors;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  rect.x = self->root_x;
+  rect.y = self->root_y;
+  rect.width = GDK_SURFACE (self)->width;
+  rect.height = GDK_SURFACE (self)->height;
+
+  for (guint i = self->monitors->len; i > 0; i--)
+    {
+      monitor = g_ptr_array_index (self->monitors, i-1);
+
+      if (!gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
+        {
+          g_object_ref (monitor);
+          g_ptr_array_remove_index (self->monitors, i-1);
+          gdk_surface_leave_monitor (GDK_SURFACE (self), monitor);
+          g_object_unref (monitor);
+        }
+    }
+
+  display = gdk_surface_get_display (GDK_SURFACE (self));
+  monitors = gdk_display_get_monitors (display);
+  n_monitors = g_list_model_get_n_items (monitors);
+
+  for (guint i = 0; i < n_monitors; i++)
+    {
+      monitor = g_list_model_get_item (monitors, i);
+
+      if (!g_ptr_array_find (self->monitors, monitor, NULL))
+        {
+          gdk_surface_enter_monitor (GDK_SURFACE (self), monitor);
+          g_ptr_array_add (self->monitors, g_object_ref (monitor));
+        }
+
+      g_object_unref (monitor);
+    }
+}
+
+GdkMonitor *
+_gdk_macos_surface_get_best_monitor (GdkMacosSurface *self)
+{
+  GdkMonitor *best = NULL;
+  GdkRectangle rect;
+  int best_area = 0;
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+  rect.x = self->root_x;
+  rect.y = self->root_y;
+  rect.width = GDK_SURFACE (self)->width;
+  rect.height = GDK_SURFACE (self)->height;
+
+  for (guint i = 0; i < self->monitors->len; i++)
+    {
+      GdkMonitor *monitor = g_ptr_array_index (self->monitors, i);
+      GdkRectangle intersect;
+
+      if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
+        {
+          int area = intersect.width * intersect.height;
+
+          if (area > best_area)
+            {
+              best = monitor;
+              best_area = area;
+            }
+        }
+    }
+
+  return best;
+}
+
+NSView *
+_gdk_macos_surface_get_view (GdkMacosSurface *self)
+{
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+  if (self->window == NULL)
+    return NULL;
+
+  return [self->window contentView];
+}
+
+void
+_gdk_macos_surface_set_opacity (GdkMacosSurface *self,
+                                double           opacity)
+{
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (self->window != NULL)
+    [self->window setAlphaValue:opacity];
+}
+
+void
+_gdk_macos_surface_get_root_coords (GdkMacosSurface *self,
+                                    int             *x,
+                                    int             *y)
+{
+  GdkSurface *surface;
+  int out_x = 0;
+  int out_y = 0;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  for (surface = GDK_SURFACE (self); surface; surface = surface->parent)
+    {
+      out_x += surface->x;
+      out_y += surface->y;
+    }
+
+  if (x)
+    *x = out_x;
+
+  if (y)
+    *y = out_y;
+}
diff --git a/gdk/macos/gdkmacossurface.h b/gdk/macos/gdkmacossurface.h
new file mode 100644
index 0000000000..470287202e
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.h
@@ -0,0 +1,43 @@
+/*
+ * 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.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_SURFACE_H__
+#define __GDK_MACOS_SURFACE_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosSurface GdkMacosSurface;
+typedef struct _GdkMacosSurfaceClass GdkMacosSurfaceClass;
+
+#define GDK_TYPE_MACOS_SURFACE       (gdk_macos_surface_get_type())
+#define GDK_MACOS_SURFACE(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_SURFACE, 
GdkMacosSurface))
+#define GDK_IS_MACOS_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_SURFACE))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_surface_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SURFACE_H__ */
diff --git a/gdk/macos/gdkmacostoplevelsurface-private.h b/gdk/macos/gdkmacostoplevelsurface-private.h
new file mode 100644
index 0000000000..d7866601e5
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface-private.h
@@ -0,0 +1,47 @@
+/*
+ * 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.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_TOPLEVEL_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_TOPLEVEL_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosToplevelSurface      GdkMacosToplevelSurface;
+typedef struct _GdkMacosToplevelSurfaceClass GdkMacosToplevelSurfaceClass;
+
+#define GDK_TYPE_MACOS_TOPLEVEL_SURFACE       (_gdk_macos_toplevel_surface_get_type())
+#define GDK_MACOS_TOPLEVEL_SURFACE(object)    (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GDK_TYPE_MACOS_TOPLEVEL_SURFACE, GdkMacosToplevelSurface))
+#define GDK_IS_MACOS_TOPLEVEL_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GDK_TYPE_MACOS_TOPLEVEL_SURFACE))
+
+GType            _gdk_macos_toplevel_surface_get_type           (void);
+GdkMacosSurface *_gdk_macos_toplevel_surface_new                (GdkMacosDisplay *display,
+                                                                 GdkSurface      *parent,
+                                                                 GdkFrameClock   *frame_clock,
+                                                                 int              x,
+                                                                 int              y,
+                                                                 int              width,
+                                                                 int              height);
+void             _gdk_macos_toplevel_surface_attach_to_parent   (GdkMacosToplevelSurface *self);
+void             _gdk_macos_toplevel_surface_detach_from_parent (GdkMacosToplevelSurface *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_TOPLEVEL_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c
new file mode 100644
index 0000000000..8407636774
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface.c
@@ -0,0 +1,581 @@
+/*
+ * 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.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"
+
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+#include "gdktoplevelprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosToplevelSurface
+{
+  GdkMacosSurface parent_instance;
+  guint           decorated : 1;
+};
+
+struct _GdkMacosToplevelSurfaceClass
+{
+  GdkMacosSurfaceClass parent_instance;
+};
+
+static void
+_gdk_macos_toplevel_surface_fullscreen (GdkMacosToplevelSurface *self)
+{
+  NSWindow *window;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+  if (([window styleMask] & NSWindowStyleMaskFullScreen) == 0)
+    [window toggleFullScreen:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_unfullscreen (GdkMacosToplevelSurface *self)
+{
+  NSWindow *window;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+  if (([window styleMask] & NSWindowStyleMaskFullScreen) != 0)
+    [window toggleFullScreen:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_maximize (GdkMacosToplevelSurface *self)
+{
+  NSWindow *window;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+  if (![window isZoomed])
+    [window zoom:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_unmaximize (GdkMacosToplevelSurface *self)
+{
+  NSWindow *window;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+  if ([window isZoomed])
+    [window zoom:window];
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_present (GdkToplevel       *toplevel,
+                                     int                width,
+                                     int                height,
+                                     GdkToplevelLayout *layout)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+  NSWindow *nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  GdkGeometry geometry;
+  GdkSurfaceHints mask;
+  NSWindowStyleMask style_mask;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+  g_assert (GDK_IS_MACOS_WINDOW (nswindow));
+
+  style_mask = [nswindow styleMask];
+
+  if (gdk_toplevel_layout_get_resizable (layout))
+    {
+      geometry.min_width = gdk_toplevel_layout_get_min_width (layout);
+      geometry.min_height = gdk_toplevel_layout_get_min_height (layout);
+      mask = GDK_HINT_MIN_SIZE;
+
+      /* Only set 'Resizable' mask to get native resize zones if the window is
+       * titled, otherwise we do this internally for CSD and do not need
+       * NSWindow to do it for us. Additionally, it can mess things up when
+       * doing a window resize since it can cause mouseDown to get passed
+       * through to the next window.
+       */
+      if ((style_mask & NSWindowStyleMaskTitled) != 0)
+        style_mask |= NSWindowStyleMaskResizable;
+      else
+        style_mask &= ~NSWindowStyleMaskResizable;
+    }
+  else
+    {
+      geometry.max_width = geometry.min_width = width;
+      geometry.max_height = geometry.min_height = height;
+      mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
+
+      style_mask &= ~NSWindowStyleMaskResizable;
+    }
+
+  if (style_mask != [nswindow styleMask])
+    [nswindow setStyleMask:style_mask];
+
+  _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask);
+  gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height);
+  _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height);
+
+  /* Maximized state */
+  if (gdk_toplevel_layout_get_maximized (layout))
+    _gdk_macos_toplevel_surface_maximize (self);
+  else
+    _gdk_macos_toplevel_surface_unmaximize (self);
+
+  /* Fullscreen state */
+  if (gdk_toplevel_layout_get_fullscreen (layout))
+    _gdk_macos_toplevel_surface_fullscreen (self);
+  else
+    _gdk_macos_toplevel_surface_unfullscreen (self);
+
+  if (GDK_SURFACE (self)->transient_for != NULL)
+    {
+    }
+  else
+    {
+      if (!self->decorated &&
+          !GDK_MACOS_SURFACE (self)->did_initial_present &&
+          GDK_SURFACE (self)->x == 0 &&
+          GDK_SURFACE (self)->y == 0 &&
+          (GDK_MACOS_SURFACE (self)->shadow_left ||
+           GDK_MACOS_SURFACE (self)->shadow_top))
+        {
+          GdkMonitor *monitor = _gdk_macos_surface_get_best_monitor (GDK_MACOS_SURFACE (self));
+          int x = GDK_SURFACE (self)->x;
+          int y = GDK_SURFACE (self)->y;
+
+          if (monitor != NULL)
+            {
+              GdkRectangle visible;
+
+              gdk_monitor_get_workarea (monitor, &visible);
+
+              if (x < visible.x)
+                x = visible.x;
+
+              if (y < visible.y)
+                y = visible.y;
+            }
+
+          x -= GDK_MACOS_SURFACE (self)->shadow_left;
+          y -= GDK_MACOS_SURFACE (self)->shadow_top;
+
+          _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y);
+        }
+    }
+
+  _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
+
+  GDK_MACOS_SURFACE (self)->did_initial_present = TRUE;
+
+  return TRUE;
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_minimize (GdkToplevel *toplevel)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+  NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  [window miniaturize:window];
+  return TRUE;
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_lower (GdkToplevel *toplevel)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+  NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  [window orderBack:window];
+  return TRUE;
+}
+
+static void
+_gdk_macos_toplevel_surface_focus (GdkToplevel *toplevel,
+                                   guint32      timestamp)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+  NSWindow *nswindow;
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  [nswindow makeKeyAndOrderFront:nswindow];
+}
+
+static void
+_gdk_macos_toplevel_surface_begin_resize (GdkToplevel    *toplevel,
+                                          GdkSurfaceEdge  edge,
+                                          GdkDevice      *device,
+                                          int             button,
+                                          double          root_x,
+                                          double          root_y,
+                                          guint32         timestamp)
+{
+  NSWindow *nswindow;
+
+  g_assert (GDK_IS_MACOS_SURFACE (toplevel));
+
+  if (GDK_SURFACE_DESTROYED (toplevel))
+    return;
+
+  /* Release passive grab */
+  if (button != 0)
+    gdk_seat_ungrab (gdk_device_get_seat (device));
+
+  if ((nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (toplevel))))
+    [(GdkMacosWindow *)nswindow beginManualResize:edge];
+}
+
+static void
+_gdk_macos_toplevel_surface_begin_move (GdkToplevel *toplevel,
+                                        GdkDevice   *device,
+                                        int          button,
+                                        double       root_x,
+                                        double       root_y,
+                                        guint32      timestamp)
+{
+  NSWindow *nswindow;
+
+  g_assert (GDK_IS_MACOS_SURFACE (toplevel));
+
+  if (GDK_SURFACE_DESTROYED (toplevel))
+    return;
+
+  /* Release passive grab */
+  if (button != 0)
+    gdk_seat_ungrab (gdk_device_get_seat (device));
+
+  if ((nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (toplevel))))
+    [(GdkMacosWindow *)nswindow beginManualMove];
+}
+
+
+static void
+toplevel_iface_init (GdkToplevelInterface *iface)
+{
+  iface->present = _gdk_macos_toplevel_surface_present;
+  iface->minimize = _gdk_macos_toplevel_surface_minimize;
+  iface->lower = _gdk_macos_toplevel_surface_lower;
+  iface->focus = _gdk_macos_toplevel_surface_focus;
+  iface->begin_resize = _gdk_macos_toplevel_surface_begin_resize;
+  iface->begin_move = _gdk_macos_toplevel_surface_begin_move;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosToplevelSurface, _gdk_macos_toplevel_surface, GDK_TYPE_MACOS_SURFACE,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_TOPLEVEL, toplevel_iface_init))
+
+enum {
+  PROP_0,
+  LAST_PROP
+};
+
+static void
+_gdk_macos_toplevel_surface_set_transient_for (GdkMacosToplevelSurface *self,
+                                               GdkMacosSurface         *parent)
+{
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+  g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+  _gdk_macos_toplevel_surface_detach_from_parent (self);
+  g_clear_object (&GDK_SURFACE (self)->transient_for);
+
+  if (g_set_object (&GDK_SURFACE (self)->transient_for, GDK_SURFACE (parent)))
+    _gdk_macos_toplevel_surface_attach_to_parent (self);
+}
+
+static void
+_gdk_macos_toplevel_surface_set_decorated (GdkMacosToplevelSurface *self,
+                                           gboolean                 decorated)
+{
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  decorated = !!decorated;
+
+  if (decorated != self->decorated)
+    {
+      NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+      self->decorated = decorated;
+      [(GdkMacosWindow *)window setDecorated:(BOOL)decorated];
+    }
+}
+
+static void
+_gdk_macos_toplevel_surface_hide (GdkSurface *surface)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+
+  _gdk_macos_toplevel_surface_detach_from_parent (self);
+
+  GDK_SURFACE_CLASS (_gdk_macos_toplevel_surface_parent_class)->hide (surface);
+}
+
+static void
+_gdk_macos_toplevel_surface_destroy (GdkSurface *surface,
+                                     gboolean    foreign_destroy)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+
+  g_clear_object (&GDK_SURFACE (self)->transient_for);
+
+  GDK_SURFACE_CLASS (_gdk_macos_toplevel_surface_parent_class)->destroy (surface, foreign_destroy);
+}
+
+static void
+_gdk_macos_toplevel_surface_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GdkSurface *surface = GDK_SURFACE (object);
+  GdkMacosSurface *base = GDK_MACOS_SURFACE (surface);
+  GdkMacosToplevelSurface *toplevel = GDK_MACOS_TOPLEVEL_SURFACE (base);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STATE:
+      g_value_set_flags (value, surface->state);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+      g_value_set_string (value, _gdk_macos_surface_get_title (base));
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+      g_value_set_string (value, "");
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+      g_value_set_object (value, GDK_SURFACE (toplevel)->transient_for);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+      g_value_set_boolean (value, GDK_SURFACE (toplevel)->modal_hint);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+      g_value_set_pointer (value, NULL);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
+      g_value_set_boolean (value, toplevel->decorated);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+      g_value_set_enum (value, surface->fullscreen_mode);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+      g_value_set_boolean (value, surface->shortcuts_inhibited);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_gdk_macos_toplevel_surface_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GdkSurface *surface = GDK_SURFACE (object);
+  GdkMacosSurface *base = GDK_MACOS_SURFACE (surface);
+  GdkMacosToplevelSurface *toplevel = GDK_MACOS_TOPLEVEL_SURFACE (base);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+      _gdk_macos_surface_set_title (base, g_value_get_string (value));
+      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+      _gdk_macos_toplevel_surface_set_transient_for (toplevel, g_value_get_object (value));
+      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+      GDK_SURFACE (surface)->modal_hint = g_value_get_boolean (value);
+      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
+      _gdk_macos_toplevel_surface_set_decorated (toplevel, g_value_get_boolean (value));
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+      surface->fullscreen_mode = g_value_get_enum (value);
+      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_gdk_macos_toplevel_surface_class_init (GdkMacosToplevelSurfaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass);
+
+  object_class->get_property = _gdk_macos_toplevel_surface_get_property;
+  object_class->set_property = _gdk_macos_toplevel_surface_set_property;
+
+  surface_class->destroy = _gdk_macos_toplevel_surface_destroy;
+  surface_class->hide = _gdk_macos_toplevel_surface_hide;
+
+  gdk_toplevel_install_properties (object_class, LAST_PROP);
+}
+
+static void
+_gdk_macos_toplevel_surface_init (GdkMacosToplevelSurface *self)
+{
+  self->decorated = TRUE;
+}
+
+GdkMacosSurface *
+_gdk_macos_toplevel_surface_new (GdkMacosDisplay *display,
+                                 GdkSurface      *parent,
+                                 GdkFrameClock   *frame_clock,
+                                 int              x,
+                                 int              y,
+                                 int              width,
+                                 int              height)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  GdkMacosWindow *window;
+  GdkMacosSurface *self;
+  NSScreen *screen;
+  NSUInteger style_mask;
+  NSRect content_rect;
+  NSRect screen_rect;
+  int nx;
+  int ny;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+  g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+  g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
+
+  style_mask = (NSWindowStyleMaskTitled |
+                NSWindowStyleMaskClosable |
+                NSWindowStyleMaskMiniaturizable |
+                NSWindowStyleMaskResizable);
+
+  _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+  screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+  screen_rect = [screen visibleFrame];
+  nx -= screen_rect.origin.x;
+  ny -= screen_rect.origin.y;
+  content_rect = NSMakeRect (nx, ny - height, width, height);
+
+  window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+                                             styleMask:style_mask
+                                               backing:NSBackingStoreBuffered
+                                                 defer:NO
+                                                screen:screen];
+
+  self = g_object_new (GDK_TYPE_MACOS_TOPLEVEL_SURFACE,
+                       "display", display,
+                       "frame-clock", frame_clock,
+                       "native", window,
+                       NULL);
+
+  GDK_END_MACOS_ALLOC_POOL;
+
+  return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_toplevel_surface_attach_to_parent (GdkMacosToplevelSurface *self)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_return_if_fail (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  if (surface->transient_for != NULL &&
+      !GDK_SURFACE_DESTROYED (surface->transient_for))
+    {
+      NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->transient_for));
+      NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+      [parent addChildWindow:window ordered:NSWindowAbove];
+
+      if (GDK_SURFACE (self)->modal_hint)
+        [window setLevel:NSModalPanelWindowLevel];
+
+      _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
+    }
+}
+
+void
+_gdk_macos_toplevel_surface_detach_from_parent (GdkMacosToplevelSurface *self)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+
+  g_return_if_fail (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  if (surface->transient_for != NULL &&
+      !GDK_SURFACE_DESTROYED (surface->transient_for))
+    {
+      NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->transient_for));
+      NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+      [parent removeChildWindow:window];
+      [window setLevel:NSNormalWindowLevel];
+
+      _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
+    }
+}
diff --git a/gdk/macos/gdkmacosutils-private.h b/gdk/macos/gdkmacosutils-private.h
new file mode 100644
index 0000000000..678982ba97
--- /dev/null
+++ b/gdk/macos/gdkmacosutils-private.h
@@ -0,0 +1,36 @@
+/*
+ * 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.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_UTILS_PRIVATE_H__
+#define __GDK_MACOS_UTILS_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <gdk/gdk.h>
+
+#define GDK_BEGIN_MACOS_ALLOC_POOL NSAutoreleasePool *_pool = [[NSAutoreleasePool alloc] init]
+#define GDK_END_MACOS_ALLOC_POOL   [_pool release]
+
+static inline gboolean
+queue_contains (GQueue *queue,
+                GList  *link_)
+{
+  return queue->head == link_ || link_->prev || link_->next;
+}
+
+#endif /* __GDK_MACOS_UTILS_PRIVATE_H__ */
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
new file mode 100644
index 0000000000..ef9f21ea85
--- /dev/null
+++ b/gdk/macos/meson.build
@@ -0,0 +1,62 @@
+gdk_macos_sources = files([
+  'edgesnapping.c',
+
+  'gdkdisplaylinksource.c',
+  'gdkmacoscairocontext.c',
+  'gdkmacosclipboard.c',
+  'gdkmacoscursor.c',
+  'gdkmacosdevice.c',
+  'gdkmacosdisplay.c',
+  'gdkmacosdisplay-settings.c',
+  'gdkmacosdisplay-translate.c',
+  'gdkmacosdrag.c',
+  'gdkmacosdragsurface.c',
+  'gdkmacosglcontext.c',
+  'gdkmacoseventsource.c',
+  'gdkmacoskeymap.c',
+  'gdkmacosmonitor.c',
+  'gdkmacospopupsurface.c',
+  'gdkmacosseat.c',
+  'gdkmacossurface.c',
+  'gdkmacostoplevelsurface.c',
+
+  'GdkMacosBaseView.c',
+  'GdkMacosCairoView.c',
+  'GdkMacosCairoSubview.c',
+  'GdkMacosGLLayer.c',
+  'GdkMacosWindow.c',
+])
+
+gdk_macos_public_headers = files([
+  'gdkmacosdevice.h',
+  'gdkmacosdisplay.h',
+  'gdkmacosglcontext.h',
+  'gdkmacoskeymap.h',
+  'gdkmacosmonitor.h',
+  'gdkmacossurface.h',
+])
+
+install_headers(gdk_macos_public_headers, 'gdkmacos.h', subdir: 'gtk-4.0/gdk/macos/')
+
+gdk_macos_frameworks = [
+  'AppKit',
+  'Carbon',
+  'CoreVideo',
+  'CoreServices',
+  'OpenGL',
+  'QuartzCore',
+]
+
+gdk_macos_deps = [
+  dependency('appleframeworks', modules: gdk_macos_frameworks)
+]
+
+libgdk_c_args += ['-xobjective-c']
+
+libgdk_macos = static_library('gdk-macos',
+                              gdk_macos_sources, gdkconfig, gdkenum_h,
+                              include_directories: [ confinc, gdkinc, ],
+                              c_args: libgdk_c_args + common_cflags,
+                              link_args: common_ldflags,
+                              link_with: [],
+                              dependencies: gdk_deps + gdk_macos_deps)
diff --git a/gdk/meson.build b/gdk/meson.build
index ac14c3b6fe..eebc9b2a97 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -143,6 +143,7 @@ gdkconfig_cdata.set('GDK_WINDOWING_X11', x11_enabled)
 gdkconfig_cdata.set('GDK_WINDOWING_WAYLAND', wayland_enabled)
 gdkconfig_cdata.set('GDK_WINDOWING_WIN32', win32_enabled)
 gdkconfig_cdata.set('GDK_WINDOWING_BROADWAY', broadway_enabled)
+gdkconfig_cdata.set('GDK_WINDOWING_MACOS', macos_enabled)
 gdkconfig_cdata.set('GDK_RENDERING_VULKAN', have_vulkan)
 
 gdkconfig = configure_file(
@@ -216,9 +217,14 @@ if wayland_enabled or broadway_enabled
   endif
 endif
 
+libgdk_c_args = [
+  '-DGTK_COMPILATION',
+  '-DG_LOG_DOMAIN="Gdk"',
+]
+
 gdk_backends = []
 gdk_backends_gen_headers = []  # non-public generated headers
-foreach backend : ['broadway', 'quartz', 'wayland', 'win32', 'x11']
+foreach backend : ['broadway', 'quartz', 'wayland', 'win32', 'x11', 'macos']
   if get_variable('@0@_enabled'.format(backend))
     subdir(backend)
     gdk_deps += get_variable('gdk_@0@_deps'.format(backend))
@@ -235,16 +241,12 @@ if gdk_backends.length() == 0
   error('No backends enabled')
 endif
 
-# FIXME: might have to add '-xobjective-c' to c_args for quartz backend?
 libgdk = static_library('gdk',
   sources: [gdk_sources, gdk_backends_gen_headers, gdkconfig],
   dependencies: gdk_deps + [libgtk_css_dep],
   link_with: [libgtk_css, ],
   include_directories: [confinc, gdkx11_inc, wlinc],
-  c_args: [
-    '-DGTK_COMPILATION',
-    '-DG_LOG_DOMAIN="Gdk"',
-  ] + common_cflags,
+  c_args: libgdk_c_args + common_cflags,
   link_whole: gdk_backends,
   link_args: common_ldflags)
 
diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c
index 7563c5bb6f..5f4b1deeb8 100644
--- a/gsk/gskrenderer.c
+++ b/gsk/gskrenderer.c
@@ -60,6 +60,9 @@
 #ifdef GDK_RENDERING_VULKAN
 #include "vulkan/gskvulkanrenderer.h"
 #endif
+#ifdef GDK_WINDOWING_MACOS
+#include <gdk/macos/gdkmacos.h>
+#endif
 
 typedef struct
 {
@@ -555,6 +558,10 @@ get_renderer_for_backend (GdkSurface *surface)
   if (GDK_IS_BROADWAY_SURFACE (surface))
     return GSK_TYPE_BROADWAY_RENDERER;
 #endif
+#ifdef GDK_WINDOWING_MACOS
+  if (GDK_IS_MACOS_SURFACE (surface))
+    return GSK_TYPE_GL_RENDERER;
+#endif
 
   return G_TYPE_INVALID;
 }
diff --git a/gtk/meson.build b/gtk/meson.build
index bb08e506a8..9a209b468f 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -1053,6 +1053,12 @@ if cc.has_header('langinfo.h')
   endforeach
 endif
 
+# Maintain compatibility with autotools
+gtk_ldflags = []
+if os_darwin
+  gtk_ldflags += [ '-compatibility_version 1', '-current_version 1.0', ]
+endif
+
 # Library
 libgtk = library('gtk-4',
                  soversion: gtk_soversion,
@@ -1062,7 +1068,7 @@ libgtk = library('gtk-4',
                  include_directories: [confinc, gdkinc, gskinc, gtkinc],
                  dependencies: gtk_deps + [libgtk_css_dep, libgdk_dep, libgsk_dep],
                  link_with: [libgtk_css, libgdk, libgsk, ],
-                 link_args: common_ldflags,
+                 link_args: common_ldflags + gtk_ldflags,
                  install: true)
 
 gtk_dep_sources = [gtkversion, gtktypebuiltins_h]
diff --git a/meson.build b/meson.build
index 1b3358fe70..181faa6118 100644
--- a/meson.build
+++ b/meson.build
@@ -93,6 +93,7 @@ gtk_api_version = '4.0'
 x11_enabled      = get_option('x11-backend')
 wayland_enabled  = get_option('wayland-backend')
 broadway_enabled = get_option('broadway-backend')
+macos_enabled    = get_option('macos-backend')
 quartz_enabled   = get_option('quartz-backend')
 win32_enabled    = get_option('win32-backend')
 
@@ -118,6 +119,7 @@ if os_darwin
   wayland_enabled = false
 else
   quartz_enabled = false
+  macos_enabled = false
 endif
 
 if os_win32
@@ -235,6 +237,8 @@ if cc.get_id() == 'msvc'
 elif cc.get_id() == 'gcc' or cc.get_id() == 'clang'
   test_cflags = [
     '-fno-strict-aliasing',
+    '-Wno-c++11-extensions',
+    '-Wno-missing-include-dirs',
     '-Wno-typedef-redefinition',
     '-Wcast-align',
     '-Wduplicated-branches',
@@ -314,11 +318,6 @@ if os_unix and not os_darwin
   endforeach
 endif
 
-# Maintain compatibility with autotools
-if os_darwin
-  common_ldflags += [ '-compatibility_version 1', '-current_version 1.0', ]
-endif
-
 confinc = include_directories('.')
 gdkinc = include_directories('gdk')
 gskinc = include_directories('gsk')
@@ -367,8 +366,10 @@ pangocairo_dep = dependency('pangocairo', version: pango_req,
 pixbuf_dep     = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req,
                             fallback : ['gdk-pixbuf', 'gdkpixbuf_dep'])
 epoxy_dep      = dependency('epoxy', version: epoxy_req,
-                            fallback: ['libepoxy', 'libepoxy_dep'])
-atk_dep        = dependency('atk', version: atk_req)
+                            fallback: ['libepoxy', 'libepoxy_dep'],
+                            static: false)
+atk_dep        = dependency('atk', version: atk_req,
+                            fallback : ['atk', 'libatk_dep'])
 harfbuzz_dep   = dependency('harfbuzz', version: '>= 0.9', required: false)
 xkbdep         = dependency('xkbcommon', version: xkbcommon_req, required: wayland_enabled)
 graphene_dep   = dependency('graphene-gobject-1.0', version: graphene_req,
@@ -793,7 +794,7 @@ pkgs = [ 'gtk4.pc' ]
 
 pkg_targets = ''
 display_backends = []
-foreach backend: [ 'broadway', 'quartz', 'wayland', 'win32', 'x11', ]
+foreach backend: [ 'broadway', 'quartz', 'macos', 'wayland', 'win32', 'x11', ]
   if get_variable('@0@_enabled'.format(backend))
     pkgs += ['gtk4-@0@.pc'.format(backend)]
     pkg_targets += ' ' + backend
diff --git a/meson_options.txt b/meson_options.txt
index c14136f073..f73f966d60 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -9,6 +9,8 @@ option('win32-backend', type: 'boolean', value: true,
   description : 'Enable the Windows gdk backend (only when building on Windows)')
 option('quartz-backend', type: 'boolean', value: true,
   description : 'Enable the macOS gdk backend (only when building on macOS)')
+option('macos-backend', type: 'boolean', value: true,
+  description : 'Enable the macOS gdk backend (only when building on macOS)')
 
 # Media backends
 option('media', type: 'string', value: 'gstreamer',
diff --git a/subprojects/atk.wrap b/subprojects/atk.wrap
new file mode 100644
index 0000000000..f6765c5bfa
--- /dev/null
+++ b/subprojects/atk.wrap
@@ -0,0 +1,5 @@
+[wrap-git]
+directory=atk
+url=https://gitlab.gnome.org/GNOME/atk.git
+push-url=ssh://git gitlab gnome org:GNOME/atk.git
+revision=master
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index 8f1828aa0c..45bbf967d5 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -4,7 +4,11 @@ testdatadir = join_paths(installed_test_datadir, 'gtk')
 gtk_tests_export_dynamic_ldflag = []
 
 if cc.get_id() != 'msvc'
-  gtk_tests_export_dynamic_ldflag = ['-Wl,--export-dynamic']
+  if os_darwin
+    gtk_tests_export_dynamic_ldflag = ['-Wl,-export_dynamic']
+  else
+    gtk_tests_export_dynamic_ldflag = ['-Wl,--export-dynamic']
+  endif
 endif
 
 # Available keys for each test:


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