[gtk/wip/chergert/quartz4u] macos: start on macos GDK backend



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

    macos: start on macos GDK backend

 gdk/gdkconfig.h.meson                       |    1 +
 gdk/gdkcontentdeserializer.c                |   12 +-
 gdk/gdkcontentserializer.c                  |   18 +-
 gdk/gdkdisplaymanager.c                     |    7 +
 gdk/macos/GdkMacosBaseView.c                |  702 +++++++++++++++++
 gdk/macos/GdkMacosBaseView.h                |   43 ++
 gdk/macos/GdkMacosCairoView.c               |  125 +++
 gdk/macos/GdkMacosCairoView.h               |   36 +
 gdk/macos/GdkMacosWindow.c                  |  626 +++++++++++++++
 gdk/macos/GdkMacosWindow.h                  |   68 ++
 gdk/macos/gdkdisplaylinksource.c            |  253 +++++++
 gdk/macos/gdkdisplaylinksource.h            |   48 ++
 gdk/macos/gdkmacos.h                        |   27 +
 gdk/macos/gdkmacoscairocontext-private.h    |   38 +
 gdk/macos/gdkmacoscairocontext.c            |  166 ++++
 gdk/macos/gdkmacoscursor-private.h          |   32 +
 gdk/macos/gdkmacoscursor.c                  |  135 ++++
 gdk/macos/gdkmacosdevice.c                  |  219 ++++++
 gdk/macos/gdkmacosdevice.h                  |   43 ++
 gdk/macos/gdkmacosdisplay-private.h         |  137 ++++
 gdk/macos/gdkmacosdisplay-settings.c        |  194 +++++
 gdk/macos/gdkmacosdisplay-translate.c       |  870 +++++++++++++++++++++
 gdk/macos/gdkmacosdisplay.c                 |  955 +++++++++++++++++++++++
 gdk/macos/gdkmacosdisplay.h                 |   47 ++
 gdk/macos/gdkmacosdragsurface-private.h     |   45 ++
 gdk/macos/gdkmacosdragsurface.c             |   80 ++
 gdk/macos/gdkmacoseventsource-private.h     |   40 +
 gdk/macos/gdkmacoseventsource.c             | 1094 +++++++++++++++++++++++++++
 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                 |  284 +++++++
 gdk/macos/gdkmacosmonitor.h                 |   43 ++
 gdk/macos/gdkmacospopupsurface-private.h    |   47 ++
 gdk/macos/gdkmacospopupsurface.c            |  347 +++++++++
 gdk/macos/gdkmacosseat-private.h            |   35 +
 gdk/macos/gdkmacosseat.c                    |   65 ++
 gdk/macos/gdkmacossurface-private.h         |  121 +++
 gdk/macos/gdkmacossurface.c                 |  887 ++++++++++++++++++++++
 gdk/macos/gdkmacossurface.h                 |   43 ++
 gdk/macos/gdkmacostoplevelsurface-private.h |   47 ++
 gdk/macos/gdkmacostoplevelsurface.c         |  473 ++++++++++++
 gdk/macos/gdkmacosutils-private.h           |   36 +
 gdk/macos/meson.build                       |   53 ++
 gdk/meson.build                             |   14 +-
 gtk/meson.build                             |    8 +-
 meson.build                                 |   17 +-
 meson_options.txt                           |    2 +
 subprojects/atk.wrap                        |    5 +
 50 files changed, 9374 insertions(+), 30 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 8a359b77c6..2452f110e1 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..825b022546
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -0,0 +1,702 @@
+/* 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;
+}
+
+-(BOOL)acceptsFirstMouse
+{
+  return YES;
+}
+
+-(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..03510db3b5
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -0,0 +1,43 @@
+/* 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"
+
+@interface GdkMacosBaseView : NSView
+{
+  NSTrackingArea *trackingArea;
+  BOOL needsInvalidateShadow;
+  NSRange markedRange;
+  NSRange selectedRange;
+}
+
+-(GdkMacosSurface *)gdkSurface;
+-(GdkMacosDisplay *)gdkDisplay;
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(NSTrackingArea *)trackingArea;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
new file mode 100644
index 0000000000..48d763e739
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -0,0 +1,125 @@
+/* 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"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoView
+
+-(void)dealloc
+{
+  g_clear_pointer (&self->surface, cairo_surface_destroy);
+  [super dealloc];
+}
+
+-(BOOL)isOpaque
+{
+  return NO;
+}
+
+-(BOOL)isFlipped
+{
+  return YES;
+}
+
+-(void)setCairoSurfaceWithRegion:(cairo_surface_t *)cairoSurface
+                     cairoRegion:(cairo_region_t *)cairoRegion
+{
+  guint n_rects = cairo_region_num_rectangles (cairoRegion);
+
+  if (self->surface == NULL)
+    {
+      NSRect rect = [self bounds];
+      rect.origin.x = rect.origin.y = 0;
+      [self setNeedsDisplayInRect:rect];
+    }
+  else
+    {
+      for (guint i = 0; i < n_rects; i++)
+        {
+          cairo_rectangle_int_t rect;
+
+          cairo_region_get_rectangle (cairoRegion, i, &rect);
+          [self setNeedsDisplayInRect:NSMakeRect (rect.x, rect.y, rect.width, rect.height)];
+        }
+    }
+
+  g_clear_pointer (&self->surface, cairo_surface_destroy);
+  self->surface = cairo_surface_reference (cairoSurface);
+}
+
+-(void)drawRect:(NSRect)rect
+{
+  GdkMacosSurface *gdkSurface;
+  CGContextRef cg_context;
+  cairo_surface_t *dest;
+  const NSRect *rects = NULL;
+  NSInteger n_rects = 0;
+  cairo_t *cr;
+  int scale_factor;
+
+  if (self->surface == NULL)
+    return;
+
+  gdkSurface = [self gdkSurface];
+  cg_context = _gdk_macos_surface_acquire_context (gdkSurface, TRUE, TRUE);
+  scale_factor = gdk_surface_get_scale_factor (GDK_SURFACE (gdkSurface));
+
+  dest = cairo_quartz_surface_create_for_cg_context (cg_context,
+                                                     self.bounds.size.width * scale_factor,
+                                                     self.bounds.size.height * scale_factor);
+  cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+
+  cr = cairo_create (dest);
+
+  cairo_set_source_surface (cr, self->surface, 0, 0);
+
+  [self getRectsBeingDrawn:&rects count:&n_rects];
+  for (NSInteger i = 0; i < n_rects; i++)
+    {
+      const NSRect *r = &rects[i];
+      cairo_rectangle (cr,
+                       r->origin.x,
+                       r->origin.y,
+                       r->size.width,
+                       r->size.height);
+    }
+  cairo_clip (cr);
+
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_paint (cr);
+
+  cairo_destroy (cr);
+  cairo_surface_flush (dest);
+  cairo_surface_destroy (dest);
+
+  _gdk_macos_surface_release_context (gdkSurface, cg_context);
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosCairoView.h
new file mode 100644
index 0000000000..c66e8b730a
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -0,0 +1,36 @@
+/* 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 isKindOfClass:[GdkMacosCairoView class]])
+
+@interface GdkMacosCairoView : GdkMacosBaseView
+{
+  cairo_surface_t *surface;
+}
+
+-(void)setCairoSurfaceWithRegion:(cairo_surface_t *)cairoSurface
+                     cairoRegion:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
new file mode 100644
index 0000000000..a809e19bcb
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.c
@@ -0,0 +1,626 @@
+/* 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 "gdksurfaceprivate.h"
+
+@implementation GdkMacosWindow
+
+-(void)windowWillClose:(NSNotification*)notification
+{
+  /* Clears the delegate when window is going to be closed; since EL
+   * Capitan it is possible that the methods of delegate would get called
+   * after the window has been closed.
+   */
+  [self setDelegate:nil];
+}
+
+-(BOOL)windowShouldClose:(id)sender
+{
+  GdkDisplay *display;
+  GdkEvent *event;
+
+  display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+  event = gdk_delete_event_new (GDK_SURFACE (gdk_surface));
+
+  _gdk_event_queue_append (display, event);
+
+  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_surface_update_viewable (GDK_SURFACE (gdk_surface));
+  _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_surface_update_viewable (GDK_SURFACE (gdk_surface));
+  _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;
+
+      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)windowDidMove:(NSNotification *)aNotification
+{
+  GdkSurface *surface = GDK_SURFACE (gdk_surface);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  gboolean maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+  GdkEvent *event;
+
+  /* In case the window is changed when maximized remove the maximized state */
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+    gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+  _gdk_macos_surface_update_position (gdk_surface);
+
+  event = gdk_configure_event_new (surface, surface->width, surface->height);
+  _gdk_event_queue_append (GDK_DISPLAY (display), event);
+
+  [self checkSendEnterNotify];
+}
+
+-(void)windowDidResize:(NSNotification *)aNotification
+{
+  NSRect content_rect;
+  GdkSurface *surface;
+  GdkDisplay *display;
+  GdkEvent *event;
+  gboolean maximized;
+
+  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]))
+    gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+  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);
+  _gdk_event_queue_append (display, event);
+
+  [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 makeKeyAndOrderFront:nil];
+  else
+    [self orderFront:nil];
+
+  inShowOrHide = NO;
+
+  [self checkSendEnterNotify];
+}
+
+-(void)hide
+{
+  inShowOrHide = YES;
+  [self orderOut:nil];
+  inShowOrHide = NO;
+
+  initialPositionKnown = NO;
+}
+
+-(BOOL)trackManualMove
+{
+  NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
+  NSRect windowFrame = [self frame];
+  NSPoint currentLocation;
+  NSPoint newOrigin;
+  int shadow_top = 0;
+
+  if (!inManualMove)
+    return NO;
+
+  currentLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+  newOrigin.x = currentLocation.x - initialMoveLocation.x;
+  newOrigin.y = currentLocation.y - initialMoveLocation.y;
+
+  _gdk_macos_surface_get_shadow (gdk_surface, &shadow_top, NULL, NULL, NULL);
+
+  /* Clamp vertical position to below the menu bar. */
+  if (newOrigin.y + windowFrame.size.height - shadow_top > screenFrame.origin.y + screenFrame.size.height)
+    newOrigin.y = screenFrame.origin.y + screenFrame.size.height - windowFrame.size.height + shadow_top;
+
+  [self setFrameOrigin:newOrigin];
+
+  return YES;
+}
+
+/* Used by gdkevents-quartz.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
+{
+  NSRect frame = [self frame];
+
+  if (inMove || inManualMove || inManualResize)
+    return;
+
+  inManualMove = YES;
+
+  initialMoveLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+  initialMoveLocation.x -= frame.origin.x;
+  initialMoveLocation.y -= frame.origin.y;
+}
+
+-(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;
+      gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MAXIMIZED, 0);
+    }
+  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
+{
+  inMaximizeTransition = NO;
+}
+
+-(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);
+}
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h
new file mode 100644
index 0000000000..f4a343021b
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.h
@@ -0,0 +1,68 @@
+/* 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"
+
+#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          initialMoveLocation;
+  NSPoint          initialResizeLocation;
+  NSRect           initialResizeFrame;
+  GdkSurfaceEdge   resizeEdge;
+
+  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/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c
new file mode 100644
index 0000000000..2961042724
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.c
@@ -0,0 +1,253 @@
+/* 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;
+
+  /*
+   * 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..7493b0c0d4
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.h
@@ -0,0 +1,48 @@
+/* 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;
+
+  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..f7eea95502
--- /dev/null
+++ b/gdk/macos/gdkmacos.h
@@ -0,0 +1,27 @@
+/*
+ * 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 "gdkmacosdisplay.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..21d05dc6fc
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -0,0 +1,166 @@
+/*
+ * 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>
+
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosCairoContext
+{
+  GdkCairoContext parent_instance;
+
+  cairo_surface_t *window_surface;
+  cairo_surface_t *paint_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;
+  GdkDisplay *display;
+  int scale;
+  int width;
+  int height;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  display = gdk_surface_get_display (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_image_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->paint_surface);
+}
+
+static void
+_gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
+                                      cairo_region_t *region)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+  GdkRectangle clip_box;
+  GdkSurface *surface;
+  double sx = 1.0;
+  double sy = 1.0;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+  g_assert (self->paint_surface == NULL);
+
+  surface = gdk_draw_context_get_surface (draw_context);
+
+  if (self->window_surface == NULL)
+    self->window_surface = create_cairo_surface_for_surface (surface);
+
+  cairo_region_get_extents (region, &clip_box);
+  self->paint_surface = gdk_surface_create_similar_surface (surface,
+                                                            cairo_surface_get_content (self->window_surface),
+                                                            MAX (clip_box.width, 1),
+                                                            MAX (clip_box.height, 1));
+  sx = sy = 1;
+  cairo_surface_get_device_scale (self->paint_surface, &sx, &sy);
+  cairo_surface_set_device_offset (self->paint_surface, -clip_box.x*sx, -clip_box.y*sy);
+}
+
+static void
+_gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context,
+                                    cairo_region_t *painted)
+{
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+  GdkSurface *surface;
+  cairo_t *cr;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+  g_assert (self->paint_surface != NULL);
+  g_assert (self->window_surface != NULL);
+
+  cr = cairo_create (self->window_surface);
+
+  cairo_set_source_surface (cr, self->paint_surface, 0, 0);
+  gdk_cairo_region (cr, painted);
+  cairo_clip (cr);
+
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_paint (cr);
+
+  cairo_destroy (cr);
+  cairo_surface_flush (self->window_surface);
+
+  surface = gdk_draw_context_get_surface (draw_context);
+  _gdk_macos_surface_damage_cairo (GDK_MACOS_SURFACE (surface),
+                                   self->window_surface,
+                                   painted);
+  g_clear_pointer (&self->paint_surface, cairo_surface_destroy);
+}
+
+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);
+  g_clear_pointer (&self->paint_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/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..884fcd9abf
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor.c
@@ -0,0 +1,135 @@
+/* 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 "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" },
+  { "n-resize", @"resizeUpCursor" },
+  { "e-resize", @"resizeRightCursor" },
+  { "s-resize", @"resizeDownCursor" },
+  { "w-resize", @"resizeLeftCursor" },
+  { "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" },
+  { NULL, NULL },
+};
+
+NSCursor *
+_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor)
+{
+  return NULL;
+}
diff --git a/gdk/macos/gdkmacosdevice.c b/gdk/macos/gdkmacosdevice.c
new file mode 100644
index 0000000000..9406bf9245
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.c
@@ -0,0 +1,219 @@
+/*
+ * 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,
+                                      gboolean         get_toplevel)
+{
+  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, NULL, 0);
+}
+
+static gboolean
+gdk_macos_device_get_history (GdkDevice      *device,
+                              GdkSurface     *surface,
+                              guint32         start,
+                              guint32         stop,
+                              GdkTimeCoord ***events,
+                              gint           *n_events)
+{
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  return FALSE;
+}
+
+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;
+  gint x_tmp;
+  gint y_tmp;
+
+  g_assert (GDK_IS_MACOS_DEVICE (device));
+  g_assert (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 (win_x)
+    *win_x = x_tmp;
+
+  if (win_y)
+    *win_y = y_tmp;
+
+  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_history = gdk_macos_device_get_history;
+  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), NULL, GDK_AXIS_X, 0, 0, 1);
+  _gdk_device_add_axis (GDK_DEVICE (self), NULL, 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..5d8bb9bc3c
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -0,0 +1,137 @@
+/*
+ * 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 array of GdkMacosMonitor. The first instance is always the primary
+   * monitor (which contains the 0,0 coordinate.
+   */
+  GPtrArray *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;
+
+  int             width;
+  int             height;
+  int             min_x;
+  int             min_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);
+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_stacking_changed               (GdkMacosDisplay *self);
+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);
+
+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..73bbc485b8
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -0,0 +1,870 @@
+/*
+ * Copyright © 2005 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 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);
+
+  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;
+
+      dy = fabs (dy);
+      dx = 0.0;
+    }
+  else if (dx != 0.0)
+    {
+      if (dx < 0.0)
+        direction = GDK_SCROLL_RIGHT;
+      else
+        direction = GDK_SCROLL_LEFT;
+
+      dx = fabs (dx);
+      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);
+}
+
+GdkEvent *
+_gdk_macos_display_translate (GdkMacosDisplay *self,
+                              NSEvent         *nsevent)
+{
+  GdkMacosSurface *surface;
+  GdkMacosWindow *window;
+  NSEventType event_type;
+  NSWindow *nswindow;
+  GdkEvent *ret = NULL;
+  NSPoint point;
+
+  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;
+    }
+
+  /* Make sure the event has a window */
+  if (!(nswindow = [nsevent window]))
+    {
+      int x_tmp;
+      int y_tmp;
+
+      if (!(nswindow = _gdk_macos_display_find_native_under_pointer (self, &x_tmp, &y_tmp)))
+        return NULL;
+
+      point.x = x_tmp;
+      point.y = y_tmp;
+    }
+  else
+    {
+      point = [[nswindow contentView] convertPoint:[nsevent locationInWindow] fromView:nil];
+    }
+
+  /* Ignore unless it is for a GdkMacosWindow */
+  if (!GDK_IS_MACOS_WINDOW (nswindow))
+    return NULL;
+
+  window = (GdkMacosWindow *)nswindow;
+
+  /* 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, point.x, point.y))
+    return NULL;
+
+  /* If the app is not active leave the event to AppKit so the window gets
+   * focused correctly and don't do click-through (so we behave like most
+   * native apps). If the app is active, we focus the window and then handle
+   * the event, also to match native apps.
+   */
+  if ((event_type == NSEventTypeRightMouseDown ||
+       event_type == NSEventTypeOtherMouseDown ||
+       event_type == NSEventTypeLeftMouseDown))
+    {
+      if (![NSApp isActive])
+        {
+          [NSApp activateIgnoringOtherApps:YES];
+          return NULL;
+        }
+      else if (![nswindow isKeyWindow])
+        {
+          [nswindow makeKeyWindow];
+        }
+    }
+
+  switch ((int)event_type)
+    {
+    case NSEventTypeLeftMouseDown:
+    case NSEventTypeRightMouseDown:
+    case NSEventTypeOtherMouseDown:
+    case NSEventTypeLeftMouseUp:
+    case NSEventTypeRightMouseUp:
+    case NSEventTypeOtherMouseUp:
+      ret = fill_button_event (self, surface, nsevent, point.x, point.y);
+      break;
+
+    case NSEventTypeLeftMouseDragged:
+    case NSEventTypeRightMouseDragged:
+    case NSEventTypeOtherMouseDragged:
+    case NSEventTypeMouseMoved:
+      ret = fill_motion_event (self, surface, nsevent, point.x, point.y);
+      break;
+
+    case NSEventTypeMagnify:
+    case NSEventTypeRotate:
+      ret = fill_pinch_event (self, surface, nsevent, point.x, point.y);
+      break;
+
+    case NSEventTypeMouseExited:
+      if (_gdk_macos_surface_is_tracking (surface, [nsevent trackingArea]))
+        {
+          [[NSCursor arrowCursor] set];
+          ret = synthesize_crossing_event (self, surface, nsevent, point.x, point.y);
+        }
+      break;
+
+    case NSEventTypeMouseEntered:
+      if (_gdk_macos_surface_is_tracking (surface, [nsevent trackingArea]))
+        ret = synthesize_crossing_event (self, surface, nsevent, point.x, point.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, point.x, point.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);
+}
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
new file mode 100644
index 0000000000..6cb6437659
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -0,0 +1,955 @@
+/*
+ * 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 "gdkmacoscairocontext-private.h"
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-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 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 int
+gdk_macos_display_get_n_monitors (GdkDisplay *display)
+{
+  return GDK_MACOS_DISPLAY (display)->monitors->len;
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor (GdkDisplay *display,
+                               int         index)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (index >= 0);
+
+  if (index < self->monitors->len)
+    return g_ptr_array_index (self->monitors, index);
+
+  return NULL;
+}
+
+static GdkMonitor *
+gdk_macos_display_get_primary_monitor (GdkDisplay *display)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  return g_ptr_array_index (self->monitors, 0);
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor_at_surface (GdkDisplay *display,
+                                          GdkSurface *surface)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+  CGDirectDisplayID screen_id;
+
+  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));
+
+  for (guint i = 0; i < self->monitors->len; i++)
+    {
+      GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, i);
+
+      if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+        return GDK_MONITOR (monitor);
+    }
+
+  return gdk_macos_display_get_primary_monitor (display);
+}
+
+static void
+gdk_macos_display_add_monitor (GdkMacosDisplay *self,
+                               GdkMacosMonitor *monitor)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (GDK_IS_MACOS_MONITOR (monitor));
+
+  g_ptr_array_add (self->monitors, g_object_ref (monitor));
+  gdk_display_monitor_added (GDK_DISPLAY (self), GDK_MONITOR (monitor));
+}
+
+static void
+gdk_macos_display_remove_monitor (GdkMacosDisplay *self,
+                                  GdkMacosMonitor *monitor)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+  g_assert (GDK_IS_MACOS_MONITOR (monitor));
+
+  g_object_ref (monitor);
+
+  if (g_ptr_array_remove (self->monitors, monitor))
+    gdk_display_monitor_removed (GDK_DISPLAY (self), GDK_MONITOR (monitor));
+
+  g_object_unref (monitor);
+}
+
+static GdkMacosMonitor *
+gdk_macos_display_find_monitor (GdkMacosDisplay   *self,
+                                CGDirectDisplayID  screen_id)
+{
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  for (guint i = 0; i < self->monitors->len; i++)
+    {
+      GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, 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;
+
+  int max_x = 0;
+  int max_y = 0;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  self->min_x = 0;
+  self->min_y = 0;
+
+  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);
+      max_x = MAX (max_x, geom.origin.x + geom.size.width);
+      max_y = MAX (max_y, geom.origin.y + geom.size.height);
+    }
+
+  self->width = max_x - self->min_x;
+  self->height = 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);
+}
+
+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;
+
+  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];
+
+      if ((monitor = gdk_macos_display_find_monitor (self, screen_id)))
+        {
+          _gdk_macos_monitor_reconfigure (monitor);
+        }
+      else
+        {
+          monitor = _gdk_macos_monitor_new (self, screen_id);
+          gdk_macos_display_add_monitor (self, monitor);
+          g_object_unref (monitor);
+        }
+
+      g_array_append_val (seen, screen_id);
+    }
+
+  for (guint i = self->monitors->len; i > 0; i--)
+    {
+      GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, 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)
+        gdk_macos_display_remove_monitor (self, monitor);
+    }
+
+  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 gboolean
+gdk_macos_display_supports_shapes (GdkDisplay *display)
+{
+  return FALSE;
+}
+
+static gboolean
+gdk_macos_display_supports_input_shapes (GdkDisplay *display)
+{
+  return FALSE;
+}
+
+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);
+
+  g_queue_push_tail_link (&self->sorted_surfaces, &surface->sorted);
+
+  if (GDK_IS_TOPLEVEL (surface))
+    g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+}
+
+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))
+    g_queue_unlink (&self->main_surfaces, &surface->main);
+
+  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);
+
+  _gdk_macos_display_stacking_changed (self);
+
+  /* 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;
+}
+
+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_stacking_changed (self);
+}
+
+void
+_gdk_macos_display_surface_resigned_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);
+
+  for (const GList *iter = self->main_surfaces.head; iter; iter = iter->next)
+    {
+      GdkMacosSurface *new_surface = iter->data;
+
+      g_assert (GDK_IS_MACOS_SURFACE (new_surface));
+      g_assert (new_surface != surface);
+
+      if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (new_surface)) &&
+          GDK_IS_TOPLEVEL (new_surface))
+        {
+          NSWindow *nswindow = _gdk_macos_surface_get_native (new_surface);
+          [nswindow makeKeyAndOrderFront:nswindow];
+          break;
+        }
+    }
+
+  g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+}
+
+void
+_gdk_macos_display_stacking_changed (GdkMacosDisplay *self)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSArray *ordered;
+  GQueue sorted = G_QUEUE_INIT;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  /* "orderedWindows" gives us the stacking order starting from front-to-back.
+   * We maintain this so that we can resolve X,Y coordinates in the topmost
+   * surfaces while processing GDK operations from devices, etc.
+   */
+
+  ordered = [NSApp orderedWindows];
+
+  for (id nswindow in ordered)
+    {
+      GdkMacosSurface *surface;
+      GList *link;
+
+      if (!GDK_IS_MACOS_WINDOW (nswindow))
+        continue;
+
+      surface = [(GdkMacosWindow *)nswindow gdkSurface];
+
+      link = &surface->sorted;
+      link->prev = NULL;
+      link->next = NULL;
+
+      g_queue_push_tail_link (&sorted, link);
+    }
+
+  self->sorted_surfaces = sorted;
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+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_finalize (GObject *object)
+{
+  GdkMacosDisplay *self = (GdkMacosDisplay *)object;
+
+  CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+                                      self,
+                                      CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+                                      NULL);
+
+  CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+                                      self,
+                                      CFSTR ("NSUserDefaultsDidChangeNotification"),
+                                      NULL);
+
+  g_clear_pointer (&self->frame_source, g_source_unref);
+  g_clear_pointer (&self->monitors, g_ptr_array_unref);
+  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_monitor = gdk_macos_display_get_monitor;
+  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_n_monitors = gdk_macos_display_get_n_monitors;
+  display_class->get_name = gdk_macos_display_get_name;
+  display_class->get_primary_monitor = gdk_macos_display_get_primary_monitor;
+  display_class->get_setting = gdk_macos_display_get_setting;
+  display_class->has_pending = gdk_macos_display_has_pending;
+  display_class->notify_startup_complete = gdk_macos_display_notify_startup_complete;
+  display_class->queue_events = gdk_macos_display_queue_events;
+  display_class->supports_input_shapes = gdk_macos_display_supports_input_shapes;
+  display_class->supports_shapes = gdk_macos_display_supports_shapes;
+  display_class->sync = gdk_macos_display_sync;
+}
+
+static void
+gdk_macos_display_init (GdkMacosDisplay *self)
+{
+  self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+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_reload_monitors (self);
+  gdk_macos_display_load_display_link (self);
+
+  CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+                                   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;
+}
+
+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)
+        {
+          grab->serial_end = 0;
+          grab->implicit_ungrab = TRUE;
+        }
+
+      _gdk_display_device_grab_update (GDK_DISPLAY (self), device, NULL, 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));
+}
+
+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);
+
+  for (const GList *iter = self->sorted_surfaces.head; iter; iter = iter->next)
+    {
+      GdkSurface *surface = iter->data;
+      NSWindow *nswindow;
+
+      g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+      nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+      if (![nswindow isVisible])
+        continue;
+
+      if (x_gdk >= surface->x &&
+          y_gdk >= surface->y &&
+          x_gdk <= (surface->x + surface->width) &&
+          y_gdk >= (surface->y + surface->height))
+        {
+          *surface_x = x_gdk - surface->x;
+          *surface_y = y_gdk - surface->y;
+
+          return GDK_MACOS_SURFACE (surface);
+        }
+    }
+
+  *surface_x = 0;
+  *surface_y = 0;
+
+  return NULL;
+}
+
+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;
+}
+
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/gdkmacosdragsurface-private.h b/gdk/macos/gdkmacosdragsurface-private.h
new file mode 100644
index 0000000000..94dec116fc
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface-private.h
@@ -0,0 +1,45 @@
+/*
+ * 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,
+                                                   GdkSurface      *parent,
+                                                   GdkFrameClock   *frame_clock,
+                                                   int              x,
+                                                   int              y,
+                                                   int              width,
+                                                   int              height);
+
+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..000eb8f602
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface.c
@@ -0,0 +1,80 @@
+/*
+ * 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"
+
+struct _GdkMacosDragSurface
+{
+  GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosDragSurfaceClass
+{
+  GdkMacosSurfaceClass parent_instance;
+};
+
+static void
+drag_surface_iface_init (GdkDragSurfaceInterface *iface)
+{
+}
+
+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_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (_gdk_macos_drag_surface_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_drag_surface_class_init (GdkMacosDragSurfaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = _gdk_macos_drag_surface_finalize;
+}
+
+static void
+_gdk_macos_drag_surface_init (GdkMacosDragSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+                             GdkSurface      *parent,
+                             GdkFrameClock   *frame_clock,
+                             int              x,
+                             int              y,
+                             int              width,
+                             int              height)
+{
+  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);
+
+  return g_object_new (GDK_TYPE_MACOS_DRAG_SURFACE,
+                       "display", display,
+                       "frame-clock", frame_clock,
+                       NULL);
+}
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..c9c416b136
--- /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/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..4bb3c195c2
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.c
@@ -0,0 +1,284 @@
+/*
+ * 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;
+  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;
+
+  g_assert (GDK_IS_MACOS_MONITOR (self));
+  g_assert (geometry != NULL);
+
+  *geometry = monitor->geometry;
+
+  for (id obj in [NSScreen screens])
+    {
+      CGDirectDisplayID screen_id;
+
+      screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+
+      if (screen_id == self->screen_id)
+        {
+          NSScreen *screen = (NSScreen *)obj;
+          NSRect visibleFrame = [screen visibleFrame];
+          int x = visibleFrame.origin.x;
+          int y = visibleFrame.origin.y + visibleFrame.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 = visibleFrame.size.width;
+          geometry->height = visibleFrame.size.height;
+
+          break;
+        }
+    }
+
+  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 (CGDirectDisplayID screen_id)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  char *name = NULL;
+
+  for (id obj in [NSScreen screens])
+    {
+      CGDirectDisplayID this_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] 
unsignedIntValue];
+
+      if (screen_id == this_id)
+        {
+          NSString *str = [(NSScreen *)obj localizedName];
+          name = g_strdup ([str UTF8String]);
+          break;
+        }
+    }
+
+  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);
+}
+
+gboolean
+_gdk_macos_monitor_reconfigure (GdkMacosMonitor *self)
+{
+  GdkSubpixelLayout subpixel_layout;
+  CGDisplayModeRef mode;
+  GdkMacosDisplay *display;
+  GdkRectangle geom;
+  gboolean has_opengl;
+  CGSize size;
+  CGRect bounds;
+  CGRect main_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);
+
+  if (!(mode = CGDisplayCopyDisplayMode (self->screen_id)))
+    return FALSE;
+
+  size = CGDisplayScreenSize (self->screen_id);
+  bounds = CGDisplayBounds (self->screen_id);
+  main_bounds = CGDisplayBounds (CGMainDisplayID ());
+  width = CGDisplayModeGetWidth (mode);
+  pixel_width = CGDisplayModeGetPixelWidth (mode);
+  refresh_rate = CGDisplayModeGetRefreshRate (mode);
+  has_opengl = CGDisplayUsesOpenGLAcceleration (self->screen_id);
+  subpixel_layout = GetSubpixelLayout (self->screen_id);
+  name = GetLocalizedName (self->screen_id);
+  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;
+
+  CGDisplayModeRelease (mode);
+
+  /* This requires that the display bounds have been
+   * updated before the monitor is reconfigured.
+   */
+  display = GDK_MACOS_DISPLAY (GDK_MONITOR (self)->display);
+  _gdk_macos_display_from_display_coords (display,
+                                          bounds.origin.x,
+                                          bounds.origin.y,
+                                          &geom.x, &geom.y);
+  geom.width = bounds.size.width;
+  geom.height = bounds.size.height;
+
+  gdk_monitor_set_connector (GDK_MONITOR (self), connector);
+  gdk_monitor_set_model (GDK_MONITOR (self), name);
+  gdk_monitor_set_position (GDK_MONITOR (self), geom.x, geom.y);
+  gdk_monitor_set_size (GDK_MONITOR (self), geom.width, geom.height);
+  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);
+
+  /* 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;
+
+  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..c04c137cf7
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface-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_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);
+
+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..9da37fb09a
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface.c
@@ -0,0 +1,347 @@
+/*
+ * 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;
+};
+
+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);
+
+  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
+    _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y);
+
+}
+
+static void
+show_popup (GdkMacosPopupSurface *self)
+{
+  NSWindow *nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+  [(GdkMacosWindow *)nswindow showAndMakeKey:NO];
+
+  _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);
+
+  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_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]];
+
+#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];
+    }
+}
+
+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];
+    }
+}
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..b396ec565d
--- /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_MASTER,
+                               "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_MASTER,
+                                "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_master_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..44b90152f2
--- /dev/null
+++ b/gdk/macos/gdkmacossurface-private.h
@@ -0,0 +1,121 @@
+/*
+ * 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 shadow_top;
+  int shadow_right;
+  int shadow_bottom;
+  int shadow_left;
+
+  gint64 pending_frame_counter;
+};
+
+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);
+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_damage_cairo            (GdkMacosSurface    *self,
+                                                               cairo_surface_t    *surface,
+                                                               cairo_region_t     *painted);
+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);
+
+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..214d2411c4
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.c
@@ -0,0 +1,887 @@
+/*
+ * 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 "gdkframeclockidleprivate.h"
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+G_DEFINE_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;
+}
+
+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)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  /* TODO: */
+}
+
+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);
+
+  _gdk_macos_display_stacking_changed (GDK_MACOS_DISPLAY (surface->display));
+}
+
+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_resize_drag (GdkSurface     *surface,
+                                     GdkSurfaceEdge  edge,
+                                     GdkDevice      *device,
+                                     int             button,
+                                     int             root_x,
+                                     int             root_y,
+                                     guint32         timestamp)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  [self->window beginManualResize:edge];
+}
+
+static void
+gdk_macos_surface_begin_move_drag (GdkSurface     *surface,
+                                   GdkDevice      *device,
+                                   int             button,
+                                   int             root_x,
+                                   int             root_y,
+                                   guint32         timestamp)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  [self->window beginManualMove];
+}
+
+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;
+  GdkDisplay *display;
+  NSRect content_rect;
+  int tmp_x = 0;
+  int tmp_y = 0;
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    {
+      if (root_x)
+        *root_x = 0;
+      if (root_y)
+        *root_y = 0;
+
+      return;
+    }
+
+  content_rect = [self->window contentRectForFrameRect:[self->window frame]];
+
+  display = gdk_surface_get_display (surface);
+  _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+                                          content_rect.origin.x,
+                                          content_rect.origin.y + content_rect.size.height,
+                                          &tmp_x, &tmp_y);
+
+  tmp_x += x;
+  tmp_y += y;
+
+  if (root_x)
+    *root_x = tmp_x;
+
+  if (root_y)
+    *root_y = tmp_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_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 ((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->begin_move_drag = gdk_macos_surface_begin_move_drag;
+  surface_class->begin_resize_drag = gdk_macos_surface_begin_resize_drag;
+  surface_class->destroy = gdk_macos_surface_destroy;
+  surface_class->get_device_state = gdk_macos_surface_get_device_state;
+  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, parent, frame_clock, x, y, width, height);
+      break;
+
+    default:
+      g_warn_if_reached ();
+      ret = NULL;
+    }
+
+  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_MAX_SIZE)
+    max_size = NSMakeSize (geometry->max_width, geometry->max_height);
+  else
+    max_size = NSMakeSize (FLT_MAX, FLT_MAX);
+
+  if (geom_mask & GDK_HINT_MIN_SIZE)
+    min_size = NSMakeSize (geometry->min_width, geometry->min_height);
+  else
+    min_size = NSMakeSize (0, 0);
+
+  [self->window setMaxSize:max_size];
+  [self->window setMinSize:min_size];
+}
+
+void
+_gdk_macos_surface_resize (GdkMacosSurface *self,
+                           int              width,
+                           int              height)
+{
+  GdkSurface *surface = (GdkSurface *)self;
+  GdkDisplay *display;
+  NSRect content_rect;
+  NSRect frame_rect;
+  int gx;
+  int gy;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+  display = gdk_surface_get_display (surface);
+  _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
+                                        surface->x,
+                                        surface->y + surface->height,
+                                        &gx,
+                                        &gy);
+  content_rect = NSMakeRect (gx, gy, width, height);
+  frame_rect = [self->window frameRectForContentRect:content_rect];
+  [self->window setFrame:frame_rect display:YES];
+}
+
+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)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  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,
+                                          &surface->x, &surface->y);
+
+  GDK_END_MACOS_ALLOC_POOL;
+}
+
+void
+_gdk_macos_surface_damage_cairo (GdkMacosSurface *self,
+                                 cairo_surface_t *surface,
+                                 cairo_region_t  *painted)
+{
+  NSView *view;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+  g_return_if_fail (surface != NULL);
+
+  view = [self->window contentView];
+
+  if (GDK_IS_MACOS_CAIRO_VIEW (view))
+    [(GdkMacosCairoView *)view setCairoSurfaceWithRegion:surface
+                                             cairoRegion:painted];
+}
+
+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)
+{
+  GdkMacosDisplay *display;
+  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_surface_update_viewable (GDK_SURFACE (self));
+
+  [self->window showAndMakeKey:GDK_IS_TOPLEVEL (self)];
+
+  if (!was_mapped)
+    {
+      if (gdk_surface_is_viewable (GDK_SURFACE (self)))
+        gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+    }
+
+  display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+  _gdk_macos_display_stacking_changed (display);
+}
+
+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;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if ((x == -1 || (x == surface->x)) &&
+      (y == -1 || (y == surface->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;
+
+  _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
+                                        x, y, &x, &y);
+
+  [self->window setFrame:NSMakeRect(x, y - height, width, height)
+                 display:YES];
+}
+
+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)
+{
+  GdkRectangle rect;
+  GdkRectangle intersect;
+  GdkDisplay *display;
+  GdkMonitor *monitor;
+  guint n_monitors;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  rect.x = GDK_SURFACE (self)->x;
+  rect.y = GDK_SURFACE (self)->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));
+  n_monitors = gdk_display_get_n_monitors (display);
+
+  for (guint i = 0; i < n_monitors; i++)
+    {
+      monitor = gdk_display_get_monitor (display, i);
+
+      if (g_ptr_array_find (self->monitors, monitor, NULL))
+        continue;
+
+      if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
+        {
+          g_ptr_array_add (self->monitors, g_object_ref (monitor));
+          gdk_surface_enter_monitor (GDK_SURFACE (self), monitor);
+        }
+    }
+}
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..92f7df62b6
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface.c
@@ -0,0 +1,473 @@
+/*
+ * 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 *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  GdkGeometry geometry;
+  GdkSurfaceHints mask;
+
+  g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+  g_assert (window != NULL);
+
+  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;
+    }
+  else
+    {
+      geometry.max_width = geometry.min_width = width;
+      geometry.max_height = geometry.min_height = height;
+      mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
+    }
+
+  _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);
+
+  /* Now present the window */
+  [(GdkMacosWindow *)window showAndMakeKey:YES];
+
+  _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
+
+  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
+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;
+}
+
+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 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];
+
+  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];
+    }
+}
+
+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];
+    }
+}
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..c10a3bb5b8
--- /dev/null
+++ b/gdk/macos/meson.build
@@ -0,0 +1,53 @@
+gdk_macos_sources = files([
+  'gdkdisplaylinksource.c',
+  'GdkMacosWindow.c',
+  'gdkmacoscairocontext.c',
+  'gdkmacoscursor.c',
+  'gdkmacosdevice.c',
+  'gdkmacosdisplay.c',
+  'gdkmacosdisplay-settings.c',
+  'gdkmacosdisplay-translate.c',
+  'gdkmacosdragsurface.c',
+  'gdkmacoseventsource.c',
+  'gdkmacoskeymap.c',
+  'gdkmacosmonitor.c',
+  'gdkmacospopupsurface.c',
+  'gdkmacosseat.c',
+  'gdkmacossurface.c',
+  'gdkmacostoplevelsurface.c',
+
+  'GdkMacosBaseView.c',
+  'GdkMacosCairoView.c',
+  # 'GdkMacosGLView.c',
+])
+
+gdk_macos_public_headers = files([
+  'gdkmacosdevice.h',
+  'gdkmacosdisplay.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',
+]
+
+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/gtk/meson.build b/gtk/meson.build
index ecccb3f642..56daeb356a 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -983,6 +983,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,
@@ -992,7 +998,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 fcd3fe2052..5eead7f5e8 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
@@ -242,6 +244,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',
@@ -317,11 +321,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')
@@ -368,8 +367,10 @@ pangocairo_dep = dependency('pangocairo', version: cairo_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,
@@ -775,7 +776,7 @@ pkg_install_dir = join_paths(get_option('libdir'), 'pkgconfig')
 pkgs = [ 'gtk4.pc' ]
 
 pkg_targets = ''
-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 73f26da7da..61c8930ab3 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


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