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



commit 8d6d387f448e939ac9640c503eb713e6a2bd0721
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                |  100 +++
 gdk/macos/GdkMacosBaseView.h                |   40 +
 gdk/macos/GdkMacosCairoView.c               |  107 +++
 gdk/macos/GdkMacosCairoView.h               |   35 +
 gdk/macos/GdkMacosView.c                    |  769 +++++++++++++++++++
 gdk/macos/GdkMacosView.h                    |   52 ++
 gdk/macos/GdkMacosWindow.c                  |  665 ++++++++++++++++
 gdk/macos/GdkMacosWindow.h                  |   64 ++
 gdk/macos/gdkmacos.h                        |   27 +
 gdk/macos/gdkmacoscairocontext-private.h    |   38 +
 gdk/macos/gdkmacoscairocontext.c            |  154 ++++
 gdk/macos/gdkmacoscursor-private.h          |   32 +
 gdk/macos/gdkmacoscursor.c                  |  135 ++++
 gdk/macos/gdkmacosdevice.c                  |   82 ++
 gdk/macos/gdkmacosdevice.h                  |   43 ++
 gdk/macos/gdkmacosdisplay-private.h         |   53 ++
 gdk/macos/gdkmacosdisplay-translate.c       |  677 +++++++++++++++++
 gdk/macos/gdkmacosdisplay.c                 |  523 +++++++++++++
 gdk/macos/gdkmacosdisplay.h                 |   47 ++
 gdk/macos/gdkmacosdragsurface-private.h     |   45 ++
 gdk/macos/gdkmacosdragsurface.c             |   80 ++
 gdk/macos/gdkmacoseventsource-private.h     |   41 +
 gdk/macos/gdkmacoseventsource.c             | 1099 +++++++++++++++++++++++++++
 gdk/macos/gdkmacoskeymap-private.h          |   35 +
 gdk/macos/gdkmacoskeymap.c                  |  833 ++++++++++++++++++++
 gdk/macos/gdkmacoskeymap.h                  |   43 ++
 gdk/macos/gdkmacosmonitor-private.h         |   38 +
 gdk/macos/gdkmacosmonitor.c                 |   82 ++
 gdk/macos/gdkmacosmonitor.h                 |   43 ++
 gdk/macos/gdkmacospopupsurface-private.h    |   45 ++
 gdk/macos/gdkmacospopupsurface.c            |  131 ++++
 gdk/macos/gdkmacosseat-private.h            |   35 +
 gdk/macos/gdkmacosseat.c                    |   65 ++
 gdk/macos/gdkmacossurface-private.h         |   83 ++
 gdk/macos/gdkmacossurface.c                 |  677 +++++++++++++++++
 gdk/macos/gdkmacossurface.h                 |   43 ++
 gdk/macos/gdkmacostoplevelsurface-private.h |   45 ++
 gdk/macos/gdkmacostoplevelsurface.c         |  420 ++++++++++
 gdk/macos/gdkmacosutils-private.h           |   29 +
 gdk/macos/meson.build                       |   50 ++
 gdk/meson.build                             |   14 +-
 gtk/meson.build                             |    8 +-
 meson.build                                 |   17 +-
 meson_options.txt                           |    2 +
 subprojects/atk.wrap                        |    5 +
 49 files changed, 7659 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..b05d9728c9
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -0,0 +1,100 @@
+/* GdkMacosBaseView.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"
+
+#import "GdkMacosBaseView.h"
+
+#include "gdkinternals.h"
+
+@implementation GdkMacosBaseView
+
+-(id)initWithFrame:(NSRect)frameRect
+{
+  if ((self = [super initWithFrame: frameRect]))
+    {
+      markedRange = NSMakeRange (NSNotFound, 0);
+      selectedRange = NSMakeRange (NSNotFound, 0);
+    }
+
+  return self;
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate
+{
+  needsInvalidateShadow = invalidate;
+}
+
+/* For information on setting up tracking rects properly, see here:
+ * http://developer.apple.com/documentation/Cocoa/Conceptual/EventOverview/EventOverview.pdf
+ */
+-(void)updateTrackingRect
+{
+  NSRect rect;
+
+  if (trackingRect)
+    {
+      [self removeTrackingRect: trackingRect];
+      trackingRect = 0;
+    }
+
+  /* Note, if we want to set assumeInside we can use:
+   * NSPointInRect ([[self window] convertScreenToBase:[NSEvent mouseLocation]], rect)
+   */
+
+  rect = [self bounds];
+  trackingRect = [self addTrackingRect: rect
+                                            owner: self
+                                         userData: nil
+                                     assumeInside: NO];
+}
+
+-(NSTrackingRectTag)trackingRect
+{
+  return trackingRect;
+}
+
+-(void)viewDidMoveToWindow
+{
+  if (![self window]) /* We are destroyed already */
+    return;
+
+  [self updateTrackingRect];
+}
+
+-(void)viewWillMoveToWindow: (NSWindow *)newWindow
+{
+  if (newWindow == nil && trackingRect)
+    {
+      [self removeTrackingRect: trackingRect];
+      trackingRect = 0;
+    }
+}
+
+-(void)setFrame: (NSRect)frame
+{
+  [super setFrame: frame];
+
+  if ([self window])
+    [self updateTrackingRect];
+}
+
+@end
diff --git a/gdk/macos/GdkMacosBaseView.h b/gdk/macos/GdkMacosBaseView.h
new file mode 100644
index 0000000000..a3c6c05e40
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -0,0 +1,40 @@
+/* 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 "gdkmacossurface.h"
+
+@interface GdkMacosBaseView : NSView
+{
+  NSTrackingRectTag trackingRect;
+  BOOL needsInvalidateShadow;
+  NSRange markedRange;
+  NSRange selectedRange;
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(NSTrackingRectTag)trackingRect;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
new file mode 100644
index 0000000000..512f59b646
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -0,0 +1,107 @@
+/* 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>
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkinternals.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;
+
+  g_clear_pointer (&self->surface, cairo_surface_destroy);
+  g_clear_pointer (&self->region, cairo_region_destroy);
+
+  self->surface = cairoSurface;
+  self->region = cairoRegion;
+
+  n_rects = cairo_region_num_rectangles (cairoRegion);
+  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)];
+    }
+}
+
+-(void)drawRect:(NSRect)rect
+{
+  cairo_surface_t *dest;
+  cairo_t *cr;
+  guint n_rects;
+
+  if (self->surface == NULL || self->region == NULL)
+    return;
+
+  dest = cairo_quartz_surface_create_for_cg_context (NSGraphicsContext.currentContext.CGContext,
+                                                     self.bounds.size.width,
+                                                     self.bounds.size.height);
+  cr = cairo_create (dest);
+
+  n_rects = cairo_region_num_rectangles (self->region);
+  for (guint i = 0; i < n_rects; i++)
+    {
+      cairo_rectangle_int_t r;
+
+      cairo_region_get_rectangle (self->region, i, &r);
+      cairo_rectangle (cr, r.x, r.y, r.width, r.height);
+    }
+
+  cairo_set_source_surface (cr, self->surface, 0, 0);
+  cairo_rectangle (cr, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
+  cairo_paint (cr);
+
+  cairo_rectangle (cr, 0, 0, self.bounds.size.width, self.bounds.size.height);
+  cairo_set_source_rgb (cr, 0, 0, 0);
+  cairo_fill (cr);
+
+  cairo_destroy (cr);
+
+  cairo_surface_flush (dest);
+  cairo_surface_destroy (dest);
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosCairoView.h
new file mode 100644
index 0000000000..c65de74eb0
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -0,0 +1,35 @@
+/* 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"
+
+@interface GdkMacosCairoView : GdkMacosBaseView
+{
+  cairo_surface_t *surface;
+  cairo_region_t  *region;
+}
+
+-(void)setCairoSurfaceWithRegion:(cairo_surface_t *)cairoSurface
+                     cairoRegion:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosView.c b/gdk/macos/GdkMacosView.c
new file mode 100644
index 0000000000..4a3f97fa5b
--- /dev/null
+++ b/gdk/macos/GdkMacosView.c
@@ -0,0 +1,769 @@
+/* GdkMacosView.m
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2011 Hiroyuki Yamamoto
+ * 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/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosView.h"
+
+#include "gdkinternals.h"
+#include "gdkmacossurface.h"
+
+@implementation GdkMacosView
+
+-(id)initWithFrame: (NSRect)frameRect
+{
+  if ((self = [super initWithFrame: frameRect]))
+    {
+      markedRange = NSMakeRange (NSNotFound, 0);
+      selectedRange = NSMakeRange (NSNotFound, 0);
+    }
+
+  return self;
+}
+
+-(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) keyDown: (NSEvent *) theEvent
+{
+  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;
+  gint ns_x;
+  gint ns_y;
+
+  g_assert (GDK_IS_MACOS_SURFACE (gdk_surface));
+
+  GDK_NOTE (EVENTS, g_message ("firstRectForCharacterRange"));
+
+  rect = g_object_get_data (G_OBJECT (gdk_surface), GIC_CURSOR_RECT);
+
+  if (rect)
+    {
+      _gdk_macos_surface_gdk_xy_to_xy (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
+{
+  g_assert (GDK_IS_MACOS_SURFACE (gdk_surface));
+
+  GDK_NOTE (EVENTS, g_message ("unmarkText"));
+
+  markedRange = selectedRange = NSMakeRange (NSNotFound, 0);
+  g_object_set_data_full (G_OBJECT (gdk_surface), TIC_MARKED_TEXT, NULL, g_free);
+}
+
+-(void)setMarkedText: (id)aString selectedRange: (NSRange)newSelection replacementRange: 
(NSRange)replacementRange
+{
+  const char *str;
+
+  g_assert (GDK_IS_MACOS_SURFACE (gdk_surface));
+
+  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 (gdk_surface), TIC_MARKED_TEXT, g_strdup (str), g_free);
+  g_object_set_data (G_OBJECT (gdk_surface), TIC_SELECTED_POS,
+                     GUINT_TO_POINTER (selectedRange.location));
+  g_object_set_data (G_OBJECT (gdk_surface), TIC_SELECTED_LEN,
+                     GUINT_TO_POINTER (selectedRange.length));
+
+  GDK_NOTE (EVENTS, g_message ("setMarkedText: set %s (%p, nsview %p): %s",
+                               TIC_MARKED_TEXT, gdk_surface, self,
+                               str ? str : "(empty)"));
+
+  /* handle text input changes by mouse events */
+  if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (gdk_surface), TIC_IN_KEY_DOWN)))
+    _gdk_macos_synthesize_null_key_event (gdk_surface);
+}
+
+-(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;
+
+  g_assert (GDK_IS_MACOS_SURFACE (gdk_surface));
+
+  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];
+      NSInputManager *currentInputManager = [NSInputManager currentInputManager];
+      [currentInputManager markedTextAbandoned:self];
+    }
+  else
+   {
+      str = [string UTF8String];
+   }
+
+  GDK_NOTE (EVENTS, g_message ("insertText: set %s (%p, nsview %p): %s",
+                               TIC_INSERT_TEXT, gdk_surface, self,
+                               str ? str : "(empty)"));
+
+  g_object_set_data_full (G_OBJECT (gdk_surface), TIC_INSERT_TEXT, g_strdup (str), g_free);
+  g_object_set_data (G_OBJECT (gdk_surface), 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 (gdk_surface), TIC_IN_KEY_DOWN)))
+    _gdk_macos_synthesize_null_key_event (gdk_surface);
+}
+
+-(void)deleteBackward: (id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("deleteBackward"));
+
+  g_object_set_data (G_OBJECT (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), 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 (gdk_surface), GIC_FILTER_KEY,
+                     GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)noop: (id)sender
+{
+  GDK_NOTE (EVENTS, g_message ("noop"));
+}
+
+-(void)dealloc
+{
+  if (trackingRect)
+    {
+      [self removeTrackingRect: trackingRect];
+      trackingRect = 0;
+    }
+
+  [super dealloc];
+}
+
+-(void)setGdkSurface: (GdkSurface *)surface
+{
+  g_assert (!surface || GDK_IS_MACOS_SURFACE (surface));
+
+  gdk_surface = surface;
+}
+
+-(GdkSurface *)gdkSurface
+{
+  return gdk_surface;
+}
+
+-(NSTrackingRectTag)trackingRect
+{
+  return trackingRect;
+}
+
+-(BOOL)isFlipped
+{
+  return YES;
+}
+
+-(BOOL)isOpaque
+{
+  if (GDK_SURFACE_DESTROYED (gdk_surface))
+    return YES;
+
+  return NO;
+}
+
+-(void)drawRect: (NSRect)rect
+{
+  GdkRectangle gdk_rect;
+  const NSRect *drawn_rects;
+  NSInteger count;
+  int i;
+  cairo_region_t *region;
+
+  if (GDK_SURFACE_DESTROYED (gdk_surface))
+    return;
+
+  if ((gdk_window->event_mask & GDK_EXPOSURE_MASK) == 0)
+    return;
+
+  if (NSEqualRects (rect, NSZeroRect))
+    return;
+
+  if (!GDK_SURFACE_IS_MAPPED (gdk_surface))
+    {
+      /* If the window is not yet mapped, clip_region_with_children
+       * will be empty causing the usual code below to draw nothing.
+       * To not see garbage on the screen, we draw an aesthetic color
+       * here. The garbage would be visible if any widget enabled
+       * the NSView's CALayer in order to add sublayers for custom
+       * native rendering.
+       */
+      [NSGraphicsContext saveGraphicsState];
+
+      [[NSColor windowBackgroundColor] setFill];
+      [NSBezierPath fillRect: rect];
+
+      [NSGraphicsContext restoreGraphicsState];
+
+      return;
+    }
+
+  /* Clear our own bookkeeping of regions that need display */
+  if (impl->needs_display_region)
+    {
+      cairo_region_destroy (impl->needs_display_region);
+      impl->needs_display_region = NULL;
+    }
+
+  [self getRectsBeingDrawn: &drawn_rects count: &count];
+  region = cairo_region_create ();
+
+  for (i = 0; i < count; i++)
+    {
+      gdk_rect.x = drawn_rects[i].origin.x;
+      gdk_rect.y = drawn_rects[i].origin.y;
+      gdk_rect.width = drawn_rects[i].size.width;
+      gdk_rect.height = drawn_rects[i].size.height;
+
+      cairo_region_union_rectangle (region, &gdk_rect);
+    }
+
+  impl->in_paint_rect_count++;
+  _gdk_surface_process_updates_recurse (gdk_surface, region);
+  impl->in_paint_rect_count--;
+
+  cairo_region_destroy (region);
+
+  if (needsInvalidateShadow)
+    {
+      [[self window] invalidateShadow];
+      needsInvalidateShadow = NO;
+    }
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate
+{
+  needsInvalidateShadow = invalidate;
+}
+
+/* For information on setting up tracking rects properly, see here:
+ * http://developer.apple.com/documentation/Cocoa/Conceptual/EventOverview/EventOverview.pdf
+ */
+-(void)updateTrackingRect
+{
+  GdkSurfaceImplMacos *impl = GDK_SURFACE_IMPL_MACOS (gdk_window->impl);
+  NSRect rect;
+
+  if (!impl || !impl->toplevel)
+    return;
+
+  if (trackingRect)
+    {
+      [self removeTrackingRect: trackingRect];
+      trackingRect = 0;
+    }
+
+  if (!impl->toplevel)
+    return;
+
+  /* Note, if we want to set assumeInside we can use:
+   * NSPointInRect ([[self window] convertScreenToBase:[NSEvent mouseLocation]], rect)
+   */
+
+  rect = [self bounds];
+  trackingRect = [self addTrackingRect: rect
+                  owner: self
+                  userData: nil
+                  assumeInside: NO];
+}
+
+-(void)viewDidMoveToWindow
+{
+  if (![self window]) /* We are destroyed already */
+    return;
+
+  [self updateTrackingRect];
+}
+
+-(void)viewWillMoveToWindow: (NSWindow *)newWindow
+{
+  if (newWindow == nil && trackingRect)
+    {
+      [self removeTrackingRect: trackingRect];
+      trackingRect = 0;
+    }
+}
+
+-(void)setFrame: (NSRect)frame
+{
+  [super setFrame: frame];
+
+  if ([self window])
+    [self updateTrackingRect];
+}
+
+-(BOOL)wantsDefaultClipping
+{
+  return NO;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosView.h b/gdk/macos/GdkMacosView.h
new file mode 100644
index 0000000000..c0a706655e
--- /dev/null
+++ b/gdk/macos/GdkMacosView.h
@@ -0,0 +1,52 @@
+/* GdkMacosView.h
+ *
+ * Copyright (C) 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 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>
+
+#include "gdk/gdk.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
+
+@interface GdkMacosView : NSView <NSTextInputClient>
+{
+  GdkSurface *gdk_surface;
+  NSTrackingRectTag trackingRect;
+  BOOL needsInvalidateShadow;
+  NSRange markedRange;
+  NSRange selectedRange;
+}
+
+- (void)setGdkSurface: (GdkSurface *)surface;
+- (GdkSurface *)gdkSurface;
+- (NSTrackingRectTag)trackingRect;
+- (void)setNeedsInvalidateShadow: (BOOL)invalidate;
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
new file mode 100644
index 0000000000..d71e07454f
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.c
@@ -0,0 +1,665 @@
+/* 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 "gdkmacossurface-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 (self->gdkSurface));
+  event = gdk_delete_event_new (GDK_SURFACE (self->gdkSurface));
+
+  _gdk_event_queue_append (display, event);
+
+  return NO;
+}
+
+-(void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  _gdk_quartz_surface_detach_from_parent (window);
+#endif
+}
+
+-(void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  gdk_synthesize_surface_state (window, 0, GDK_SURFACE_STATE_MINIMIZED);
+#endif
+}
+
+-(void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  _gdk_quartz_surface_attach_to_parent (window);
+
+  gdk_synthesize_surface_state (window, GDK_SURFACE_STATE_MINIMIZED, 0);
+#endif
+}
+
+-(void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  gdk_synthesize_surface_state (window, 0, GDK_SURFACE_STATE_FOCUSED);
+  _gdk_quartz_events_update_focus_window (window, TRUE);
+#endif
+}
+
+-(void)windowDidResignKey:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  _gdk_quartz_events_update_focus_window (window, FALSE);
+  gdk_synthesize_surface_state (window, GDK_SURFACE_STATE_FOCUSED, 0);
+#endif
+}
+
+-(void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+
+  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_quartz_surface_did_become_main (window);
+#endif
+}
+
+-(void)windowDidResignMain:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window;
+
+  window = [[self contentView] gdkSurface];
+  _gdk_quartz_surface_did_resign_main (window);
+#endif
+}
+
+/* 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
+{
+  switch ((int)[event type])
+    {
+    case NSEventTypeLeftMouseUp:
+    {
+#if 0
+      double time = ((double)[event timestamp]) * 1000.0;
+      _gdk_quartz_events_break_all_grabs (time);
+#endif
+
+      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: [view trackingRect]
+                                         userData: nil];
+
+          [NSApp postEvent:event atStart:NO];
+        }
+    }
+}
+
+-(void)windowDidMove:(NSNotification *)aNotification
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+  GdkEvent *event;
+
+  GdkSurfaceImplQuartz *impl = GDK_SURFACE_IMPL_QUARTZ (window->impl);
+  gboolean maximized = gdk_surface_get_state (window) & GDK_SURFACE_STATE_MAXIMIZED;
+
+  /* In case the window is changed when maximized remove the maximized state */
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+    {
+      gdk_synthesize_surface_state (window,
+                                   GDK_SURFACE_STATE_MAXIMIZED,
+                                   0);
+    }
+
+  _gdk_quartz_surface_update_position (window);
+
+  /* Synthesize a configure event */
+  event = gdk_event_new (GDK_CONFIGURE);
+  event->configure.window = g_object_ref (window);
+  event->configure.x = window->x;
+  event->configure.y = window->y;
+  event->configure.width = window->width;
+  event->configure.height = window->height;
+
+  _gdk_event_queue_append (gdk_display_get_default (), event);
+
+  [self checkSendEnterNotify];
+#endif
+}
+
+-(void)windowDidResize:(NSNotification *)aNotification
+{
+  NSRect content_rect;
+  GdkSurface *surface;
+  GdkDisplay *display;
+  GdkEvent *event;
+  gboolean maximized;
+
+  surface = GDK_SURFACE (self->gdkSurface);
+  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:nil];
+  [self setReleasedWhenClosed:YES];
+
+  view = [[GdkMacosCairoView alloc] initWithFrame:contentRect];
+  [self setContentView:view];
+  [view release];
+
+  return self;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+  return GDK_IS_TOPLEVEL (self->gdkSurface);
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+#if 0
+  jdkSurface *window = [[self contentView] gdkSurface];
+  GdkSurfaceImplQuartz *impl = GDK_SURFACE_IMPL_QUARTZ (window->impl);
+
+  if (!window->accept_focus)
+    return NO;
+
+  /* Popup windows should not be able to get focused in the window
+   * manager sense, it's only handled through grabs.
+   */
+  if (window->surface_type == GDK_SURFACE_TEMP)
+    return NO;
+
+  switch (impl->type_hint)
+    {
+    case GDK_SURFACE_TYPE_HINT_NORMAL:
+    case GDK_SURFACE_TYPE_HINT_DIALOG:
+    case GDK_SURFACE_TYPE_HINT_MENU:
+    case GDK_SURFACE_TYPE_HINT_TOOLBAR:
+    case GDK_SURFACE_TYPE_HINT_UTILITY:
+    case GDK_SURFACE_TYPE_HINT_DOCK:
+    case GDK_SURFACE_TYPE_HINT_DESKTOP:
+    case GDK_SURFACE_TYPE_HINT_DROPDOWN_MENU:
+    case GDK_SURFACE_TYPE_HINT_POPUP_MENU:
+    case GDK_SURFACE_TYPE_HINT_COMBO:
+      return YES;
+
+    case GDK_SURFACE_TYPE_HINT_SPLASHSCREEN:
+    case GDK_SURFACE_TYPE_HINT_TOOLTIP:
+    case GDK_SURFACE_TYPE_HINT_NOTIFICATION:
+    case GDK_SURFACE_TYPE_HINT_DND:
+      return NO;
+    }
+
+  return YES;
+#endif
+
+  return NO;
+}
+
+-(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
+{
+#if 0
+  GdkSurface *window = [[self contentView] gdkSurface];
+  GdkSurfaceImplQuartz *impl = GDK_SURFACE_IMPL_QUARTZ (window->impl);
+  NSPoint currentLocation;
+  NSPoint newOrigin;
+  NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
+  NSRect windowFrame = [self frame];
+
+  if (!inManualMove)
+    return NO;
+
+  currentLocation = [self convertBaseToScreen:[self mouseLocationOutsideOfEventStream]];
+  newOrigin.x = currentLocation.x - initialMoveLocation.x;
+  newOrigin.y = currentLocation.y - initialMoveLocation.y;
+
+  /* Clamp vertical position to below the menu bar. */
+  if (newOrigin.y + windowFrame.size.height - impl->shadow_top > screenFrame.origin.y + 
screenFrame.size.height)
+    newOrigin.y = screenFrame.origin.y + screenFrame.size.height - windowFrame.size.height + 
impl->shadow_top;
+
+  [self setFrameOrigin:newOrigin];
+#endif
+
+  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 convertBaseToScreen:[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 convertBaseToScreen:[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 convertBaseToScreen:[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;
+
+  was_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+
+  [super setStyleMask:styleMask];
+
+  is_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+
+  if (was_fullscreen != is_fullscreen)
+    _gdk_macos_surface_update_fullscreen_state (self->gdkSurface);
+}
+
+-(NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
+{
+  GdkMacosSurface *surface = self->gdkSurface;
+  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 = self->gdkSurface;
+  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 = self->gdkSurface;
+  GdkSurfaceState state = GDK_SURFACE (surface)->state;
+
+  if (state & GDK_SURFACE_STATE_MAXIMIZED)
+    {
+      lastMaximizedFrame = newFrame;
+      gdk_surface_set_state (GDK_SURFACE (surface), state & ~GDK_SURFACE_STATE_MAXIMIZED);
+    }
+  else
+    {
+      lastUnmaximizedFrame = [nsWindow frame];
+      gdk_surface_set_state (GDK_SURFACE (surface), state & 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)setGdkSurface:(GdkMacosSurface *)surface
+{
+  self->gdkSurface = 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 *)getGdkSurface
+{
+  return self->gdkSurface;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h
new file mode 100644
index 0000000000..3f1c6f337d
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.h
@@ -0,0 +1,64 @@
+/* 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 "gdkmacossurface.h"
+
+@interface GdkMacosWindow : NSWindow {
+  GdkMacosSurface *gdkSurface;
+
+  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;
+-(GdkMacosSurface *)getGdkSurface;
+-(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/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..a3ef32fc84
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#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;
+  CGContextRef cg;
+  GdkDisplay *display;
+  int scale;
+  int width;
+  int height;
+
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  cg = NSGraphicsContext.currentContext.CGContext;
+  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_quartz_surface_create_for_cg_context (cg, 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, sy;
+
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+  surface = gdk_draw_context_get_surface (draw_context);
+  cairo_region_get_extents (region, &clip_box);
+
+  self->window_surface = create_cairo_surface_for_surface (surface);
+  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));
+
+  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);
+  if (GDK_IS_MACOS_SURFACE (surface))
+    _gdk_macos_surface_damage_cairo (GDK_MACOS_SURFACE (surface),
+                                     g_steal_pointer (&self->window_surface),
+                                     painted);
+
+  g_clear_pointer (&self->paint_surface, cairo_surface_destroy);
+  g_clear_pointer (&self->window_surface, cairo_surface_destroy);
+}
+
+static void
+_gdk_macos_cairo_context_class_init (GdkMacosCairoContextClass *klass)
+{
+  GdkCairoContextClass *cairo_context_class = GDK_CAIRO_CONTEXT_CLASS (klass);
+  GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+  draw_context_class->begin_frame = _gdk_macos_cairo_context_begin_frame;
+  draw_context_class->end_frame = _gdk_macos_cairo_context_end_frame;
+
+  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..0f8e47c552
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.c
@@ -0,0 +1,82 @@
+/*
+ * 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 "gdkmacoscursor-private.h"
+#include "gdkmacosdevice.h"
+#include "gdkmacossurface.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 void
+gdk_macos_device_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gdk_macos_device_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_device_class_init (GdkMacosDeviceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
+
+  object_class->finalize = gdk_macos_device_finalize;
+
+  device_class->set_surface_cursor = gdk_macos_device_set_surface_cursor;
+}
+
+static void
+gdk_macos_device_init (GdkMacosDevice *self)
+{
+}
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..5f32411a82
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -0,0 +1,53 @@
+/*
+ * 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 "gdkmacosdisplay.h"
+
+G_BEGIN_DECLS
+
+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_mouse_modifiers  (GdkMacosDisplay *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c
new file mode 100644
index 0000000000..4be05493df
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -0,0 +1,677 @@
+/*
+ * 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 GRIP_WIDTH 15
+#define GRIP_HEIGHT 15
+#define GDK_LION_RESIZE 5
+
+static gboolean
+test_resize (NSEvent         *event,
+             GdkMacosSurface *surface,
+             gint             x,
+             gint             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] == NSLeftMouseDown &&
+      [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] == NSLeftMouseDown ||
+       [event type] == NSRightMouseDown ||
+       [event type] == NSOtherMouseDown))
+    {
+      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 & NSAlphaShiftKeyMask)
+    modifiers |= GDK_LOCK_MASK;
+  if (nsflags & NSShiftKeyMask)
+    modifiers |= GDK_SHIFT_MASK;
+  if (nsflags & NSControlKeyMask)
+    modifiers |= GDK_CONTROL_MASK;
+  if (nsflags & NSAlternateKeyMask)
+    modifiers |= GDK_ALT_MASK;
+  if (nsflags & NSCommandKeyMask)
+    modifiers |= GDK_SUPER_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]);
+}
+
+
+static GdkEvent *
+fill_button_event (GdkMacosDisplay *display,
+                   GdkMacosSurface *surface,
+                   NSEvent         *nsevent,
+                   gint             x,
+                   gint             y)
+{
+  GdkSeat *seat;
+  GdkEventType type;
+  GdkModifierType state;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  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 ([nsevent type])
+    {
+    case NSLeftMouseDown:
+    case NSRightMouseDown:
+    case NSOtherMouseDown:
+      type = GDK_BUTTON_PRESS;
+      state &= ~get_mouse_button_modifiers_from_ns_event (nsevent);
+      break;
+
+    case NSLeftMouseUp:
+    case NSRightMouseUp:
+    case NSOtherMouseUp:
+      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,
+                           gint             x,
+                           gint             y)
+{
+  GdkEventType event_type;
+  GdkModifierType state;
+  GdkSeat *seat;
+
+  switch ([nsevent type])
+    {
+    case NSMouseEntered:
+      event_type = GDK_ENTER_NOTIFY;
+      break;
+
+    case NSMouseExited:
+      event_type = GDK_LEAVE_NOTIFY;
+      break;
+
+    default:
+      return 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,
+                                 surface,
+                                 gdk_seat_get_pointer (seat),
+                                 NULL,
+                                 get_time_from_ns_event (nsevent),
+                                 state,
+                                 x,
+                                 y,
+                                 GDK_CROSSING_NORMAL,
+                                 GDK_NOTIFY_NONLINEAR);
+}
+
+static GdkEvent *
+fill_key_event (GdkMacosDisplay *display,
+                GdkMacosSurface *surface,
+                NSEvent         *nsevent,
+                GdkEventType     type)
+{
+#if 1
+  return NULL;
+#else
+  GdkTranslatedKey translated;
+  GdkTranslatedKey no_lock;
+  GdkModifierType modifiers;
+  gboolean is_modifier;
+  GdkSeat *seat;
+  guint keycode;
+
+  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));
+
+  return gdk_key_event_new (type,
+                            GDK_SURFACE (surface),
+                            gdk_seat_get_keyboard (seat),
+                            NULL,
+                            get_time_from_ns_event (nsevent),
+                            [nsevent keyCode],
+                            modifiers,
+                            is_modifier,
+                            &translated,
+                            &no_lock);
+#endif
+}
+
+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 ([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 ([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);
+}
+
+
+GdkEvent *
+_gdk_macos_display_translate (GdkMacosDisplay *self,
+                              NSEvent         *nsevent)
+{
+  GdkEvent *ret = NULL;
+  NSEventType event_type;
+  NSWindow *nswindow;
+  GdkMacosSurface *surface;
+  NSPoint point;
+  int x, y;
+
+  g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+  g_return_val_if_fail (nsevent != NULL, NULL);
+
+  /* There is no support for real desktop wide grabs, so we break
+   * grabs when the application loses focus (gets deactivated).
+   */
+  event_type = [nsevent type];
+  if (event_type == NSAppKitDefined)
+    {
+      if ([nsevent subtype] == NSApplicationDeactivatedEventType)
+        _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;
+    }
+
+  nswindow = [nsevent window];
+
+  /* Ignore events for windows not created by GDK. */
+  if (nswindow && ![[nswindow contentView] isKindOfClass:[GdkMacosBaseView class]])
+    return NULL;
+
+  /* Ignore events for ones with no windows */
+  if (!nswindow)
+    return NULL;
+
+  /* Ignore events and break grabs while the window is being
+   * dragged. This is a workaround for the window getting events for
+   * the window title.
+   */
+  if ([(GdkMacosWindow *)nswindow 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 ([(GdkMacosWindow *)nswindow isInManualResizeOrMove])
+    return NULL;
+
+  /* Get the location of the event within the toplevel */
+  point = [nsevent locationInWindow];
+  _gdk_macos_display_from_display_coords (self, point.x, point.y, &x, &y);
+
+  /* Find the right GDK surface to send the event to, taking grabs and
+   * event masks into consideration.
+   */
+  if (!(surface = [(GdkMacosWindow *)nswindow getGdkSurface]))
+    return NULL;
+
+  /* Quartz handles resizing on its own, so we want to stay out of the way. */
+  if (test_resize (nsevent, surface, x, 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 == NSRightMouseDown ||
+       event_type == NSOtherMouseDown ||
+       event_type == NSLeftMouseDown))
+    {
+      if (![NSApp isActive])
+        {
+          [NSApp activateIgnoringOtherApps:YES];
+          return FALSE;
+        }
+      else if (![nswindow isKeyWindow])
+        {
+          [nswindow makeKeyWindow];
+        }
+    }
+
+  switch (event_type)
+    {
+    case NSLeftMouseDown:
+    case NSRightMouseDown:
+    case NSOtherMouseDown:
+    case NSLeftMouseUp:
+    case NSRightMouseUp:
+    case NSOtherMouseUp:
+      ret = fill_button_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSLeftMouseDragged:
+    case NSRightMouseDragged:
+    case NSOtherMouseDragged:
+    case NSMouseMoved:
+      ret = fill_motion_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSEventTypeMagnify:
+    case NSEventTypeRotate:
+      ret = fill_pinch_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSMouseExited:
+      [[NSCursor arrowCursor] set];
+      /* fall through */
+    case NSMouseEntered:
+      ret = synthesize_crossing_event (self, surface, nsevent, x, y);
+      break;
+
+    case NSKeyDown:
+    case NSKeyUp:
+    case NSFlagsChanged: {
+      GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent);
+
+      if (type)
+        ret = fill_key_event (self, surface, nsevent, type);
+
+      break;
+    }
+
+    case NSScrollWheel:
+      //ret = fill_scroll_event (self, surface, nsevent, x, y);
+      break;
+
+    default:
+      break;
+    }
+
+#if 0
+    case NSScrollWheel:
+      {
+        GdkScrollDirection direction;
+        float dx;
+        float dy;
+
+        if ([nsevent hasPreciseScrollingDeltas])
+          {
+            dx = [nsevent scrollingDeltaX];
+            dy = [nsevent scrollingDeltaY];
+            direction = GDK_SCROLL_SMOOTH;
+
+            fill_scroll_event (window, event, nsevent, x, y, x_root, y_root,
+                               -dx, -dy, direction);
+
+            /* Fall through for scroll buttons emulation */
+          }
+
+        dx = [nsevent deltaX];
+        dy = [nsevent deltaY];
+
+        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_event;
+
+                emulated_event = gdk_event_new (GDK_SCROLL);
+                gdk_event_set_pointer_emulated (emulated_event, TRUE);
+                fill_scroll_event (window, emulated_event, nsevent,
+                                   x, y, x_root, y_root,
+                                   dx, dy, direction);
+                append_event (emulated_event, TRUE);
+              }
+            else
+              {
+                fill_scroll_event (window, event, nsevent,
+                                   x, y, x_root, y_root,
+                                   dx, dy, direction);
+              }
+          }
+      }
+      break;
+#endif
+
+  return ret;
+}
+
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
new file mode 100644
index 0000000000..a6282cb679
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -0,0 +1,523 @@
+/*
+ * 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 "gdkeventsprivate.h"
+
+#include "gdkmacoscairocontext-private.h"
+#include "gdkdisplayprivate.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");
+ * ]|
+ */
+
+struct _GdkMacosDisplay
+{
+  GdkDisplay           parent_instance;
+
+  char                *name;
+  GPtrArray           *monitors;
+  GdkMacosKeymap      *keymap;
+
+  int                  width;
+  int                  height;
+  int                  min_x;
+  int                  min_y;
+};
+
+struct _GdkMacosDisplayClass
+{
+  GdkDisplayClass parent_class;
+};
+
+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 FALSE;
+}
+
+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 void
+gdk_macos_display_load_monitors (GdkMacosDisplay *self)
+{
+  GDK_BEGIN_MACOS_ALLOC_POOL;
+
+  NSArray *screens;
+  int max_x = 0;
+  int max_y = 0;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+  screens = [NSScreen screens];
+
+  for (id obj in screens)
+    {
+      CGDirectDisplayID screen_id;
+      GdkMacosMonitor *monitor;
+      NSRect geom;
+
+      geom = [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);
+
+      screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+      monitor = _gdk_macos_monitor_new (self, screen_id);
+
+      gdk_macos_display_add_monitor (self, monitor);
+
+      g_object_unref (monitor);
+    }
+
+  self->width = max_x - self->min_x;
+  self->height = max_y - self->min_y;
+
+  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 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)
+{
+  g_warning ("Has pending");
+  return FALSE;
+}
+
+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;
+  GdkEvent *event;
+  GList *node;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  if (!(nsevent = _gdk_macos_event_source_get_pending ()))
+    return;
+
+  if (!(event = _gdk_macos_display_translate (self, nsevent)))
+    {
+      [NSApp sendEvent:nsevent];
+      _gdk_macos_event_source_release_event (nsevent);
+      return;
+    }
+
+  node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+  _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+  _gdk_macos_event_source_release_event (nsevent);
+}
+
+static GdkSurface *
+gdk_macos_display_create_surface (GdkDisplay     *display,
+                                  GdkSurfaceType  surface_type,
+                                  GdkSurface     *parent,
+                                  int             x,
+                                  int             y,
+                                  int             width,
+                                  int             height)
+{
+  GdkMacosSurface *surface;
+
+  g_assert (GDK_IS_MACOS_DISPLAY (display));
+  g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+  surface = _gdk_macos_surface_new (GDK_MACOS_DISPLAY (display),
+                                    surface_type,
+                                    parent,
+                                    x, y,
+                                    width, height);
+
+  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;
+
+  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)
+{
+  GdkMacosDisplay *self;
+  ProcessSerialNumber psn = { 0, kCurrentProcess };
+
+  GDK_NOTE (MISC, g_message ("opening display %s", display_name ? display_name : ""));
+
+  /* Make the current process a foreground application, i.e. an app
+   * with a user interface, in case we're not running from a .app bundle
+   */
+  TransformProcessType (&psn, kProcessTransformToForegroundApplication);
+
+  [NSApplication sharedApplication];
+
+  self = g_object_new (GDK_TYPE_MACOS_DISPLAY, NULL);
+  self->name = g_strdup (display_name);
+  self->keymap = _gdk_macos_keymap_new (self);
+
+  gdk_macos_display_load_seat (self);
+  gdk_macos_display_load_monitors (self);
+
+  if (event_source == NULL)
+    {
+      event_source = _gdk_macos_event_source_new (self);
+      g_source_attach (event_source, NULL);
+    }
+
+  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)
+{
+  if (out_y)
+    *out_y = self->height - y + self->min_y;
+
+  if (out_x)
+    *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)
+{
+  GList *devices = NULL;
+  GdkSeat *seat;
+
+  g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+  seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+
+  devices = g_list_prepend (devices, gdk_seat_get_keyboard (seat));
+  devices = g_list_prepend (devices, gdk_seat_get_pointer (seat));
+
+  for (const GList *l = devices; l; l = l->next)
+    {
+      GdkDevice *device = l->data;
+      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);
+    }
+
+  g_list_free (devices);
+}
+
+void
+_gdk_macos_display_queue_events (GdkMacosDisplay *self)
+{
+  gdk_macos_display_queue_events (GDK_DISPLAY (self));
+}
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..9de9d0ef08
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource-private.h
@@ -0,0 +1,41 @@
+/*
+ * 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);
+void      _gdk_macos_event_source_release_event (NSEvent         *event);
+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..21cf081316
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource.c
@@ -0,0 +1,1099 @@
+/* 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;
+}
+
+void
+_gdk_macos_event_source_release_event (NSEvent *event)
+{
+  [event release];
+}
+
+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..28eaa091ac
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap-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_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);
+
+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..0c2cd1d5bd
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap.c
@@ -0,0 +1,833 @@
+/*
+ * 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)])
+
+static inline UniChar
+macroman2ucs (unsigned char c)
+{
+  /* Precalculated table mapping MacRoman-128 to Unicode. Generated
+     by creating single element CFStringRefs then extracting the
+     first character. */
+
+  static const unsigned short table[128] = {
+    0xc4, 0xc5, 0xc7, 0xc9, 0xd1, 0xd6, 0xdc, 0xe1,
+    0xe0, 0xe2, 0xe4, 0xe3, 0xe5, 0xe7, 0xe9, 0xe8,
+    0xea, 0xeb, 0xed, 0xec, 0xee, 0xef, 0xf1, 0xf3,
+    0xf2, 0xf4, 0xf6, 0xf5, 0xfa, 0xf9, 0xfb, 0xfc,
+    0x2020, 0xb0, 0xa2, 0xa3, 0xa7, 0x2022, 0xb6, 0xdf,
+    0xae, 0xa9, 0x2122, 0xb4, 0xa8, 0x2260, 0xc6, 0xd8,
+    0x221e, 0xb1, 0x2264, 0x2265, 0xa5, 0xb5, 0x2202, 0x2211,
+    0x220f, 0x3c0, 0x222b, 0xaa, 0xba, 0x3a9, 0xe6, 0xf8,
+    0xbf, 0xa1, 0xac, 0x221a, 0x192, 0x2248, 0x2206, 0xab,
+    0xbb, 0x2026, 0xa0, 0xc0, 0xc3, 0xd5, 0x152, 0x153,
+    0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0xf7, 0x25ca,
+    0xff, 0x178, 0x2044, 0x20ac, 0x2039, 0x203a, 0xfb01, 0xfb02,
+    0x2021, 0xb7, 0x201a, 0x201e, 0x2030, 0xc2, 0xca, 0xc1,
+    0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0xd3, 0xd4,
+    0xf8ff, 0xd2, 0xda, 0xdb, 0xd9, 0x131, 0x2c6, 0x2dc,
+    0xaf, 0x2d8, 0x2d9, 0x2da, 0xb8, 0x2dd, 0x2db, 0x2c7
+  };
+
+  if (c < 128)
+    return c;
+  else
+    return table[c - 128];
+}
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+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,    NSCommandKeyMask },
+  {  55, GDK_KEY_Meta_L,    NSCommandKeyMask },
+  {  56, GDK_KEY_Shift_L,   NSShiftKeyMask },
+  {  57, GDK_KEY_Caps_Lock, NSAlphaShiftKeyMask },
+  {  58, GDK_KEY_Alt_L,     NSAlternateKeyMask },
+  {  59, GDK_KEY_Control_L, NSControlKeyMask },
+  {  60, GDK_KEY_Shift_R,   NSShiftKeyMask },
+  {  61, GDK_KEY_Alt_R,     NSAlternateKeyMask },
+  {  62, GDK_KEY_Control_R, NSControlKeyMask }
+};
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+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;
+
+  /* Note: we could check only if building against the 10.5 SDK instead, but
+   * that would make non-xml layouts not work in 32-bit which would be a quite
+   * bad regression. This way, old unsupported layouts will just not work in
+   * 64-bit.
+   */
+#ifdef __LP64__
+  TISInputSourceRef new_layout = TISCopyCurrentKeyboardLayoutInputSource ();
+  CFDataRef layout_data_ref;
+
+#else
+  KeyboardLayoutRef new_layout;
+  KeyboardLayoutKind layout_kind;
+
+  KLGetCurrentKeyboardLayout (&new_layout);
+#endif
+
+  g_free (keyval_array);
+  keyval_array = g_new0 (guint, NUM_KEYCODES * KEYVALS_PER_KEYCODE);
+
+#ifdef __LP64__
+  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;
+    }
+#else
+
+  /* Get the layout kind */
+  KLGetKeyboardLayoutProperty (new_layout, kKLKind, (const void **)&layout_kind);
+
+  /* 8-bit-only keyabord layout */
+  if (layout_kind == kKLKCHRKind)
+    {
+      /* Get chr data */
+      KLGetKeyboardLayoutProperty (new_layout, kKLKCHRData, (const void **)&chr_data);
+
+      for (i = 0; i < NUM_KEYCODES; i++)
+        {
+          int j;
+          UInt32 modifiers[] = {0, shiftKey, optionKey, shiftKey | optionKey};
+
+          p = keyval_array + i * KEYVALS_PER_KEYCODE;
+
+          for (j = 0; j < KEYVALS_PER_KEYCODE; j++)
+            {
+              UInt32 c, state = 0;
+              UInt16 key_code;
+              UniChar uc;
+
+              key_code = modifiers[j] | i;
+              c = KeyTranslate (chr_data, key_code, &state);
+
+              if (state != 0)
+                {
+                  UInt32 state2 = 0;
+                  c = KeyTranslate (chr_data, key_code | 128, &state2);
+                }
+
+              if (c != 0 && c != 0x10)
+                {
+                  int k;
+                  gboolean found = FALSE;
+
+                  /* FIXME: some keyboard layouts (e.g. Russian) use a
+                   * different 8-bit character set. We should check
+                   * for this. Not a serious problem, because most
+                   * (all?) of these layouts also have a uchr version.
+                   */
+                  uc = macroman2ucs (c);
+
+                  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;
+        }
+    }
+  /* unicode keyboard layout */
+  else if (layout_kind == kKLKCHRuchrKind || layout_kind == kKLuchrKind)
+    {
+      /* Get chr data */
+      KLGetKeyboardLayoutProperty (new_layout, kKLuchrData, (const void **)&chr_data);
+#endif
+
+      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;
+        }
+#ifndef __LP64__
+    }
+  else
+    {
+      g_error ("unknown type of keyboard layout (neither KCHR nor uchr)"
+               " - not supported right now");
+    }
+#endif
+
+  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,
+                                         GdkKeymapKey **keys,
+                                         gint          *n_keys)
+{
+  GArray *keys_array;
+  guint i;
+
+  g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+  g_assert (keys != NULL);
+  g_assert (n_keys != NULL);
+
+  *n_keys = 0;
+  keys_array = g_array_new (FALSE, FALSE, sizeof (GdkKeymapKey));
+
+  for (i = 0; i < NUM_KEYCODES * KEYVALS_PER_KEYCODE; i++)
+    {
+      GdkKeymapKey key;
+
+      if (keyval_array[i] != keyval)
+        continue;
+
+      (*n_keys)++;
+
+      key.keycode = i / KEYVALS_PER_KEYCODE;
+      key.group = (i % KEYVALS_PER_KEYCODE) >= 2;
+      key.level = i % 2;
+
+      g_array_append_val (keys_array, key);
+    }
+
+  *keys = (GdkKeymapKey *)(gpointer)g_array_free (keys_array, FALSE);
+
+  return *n_keys > 0;
+}
+
+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 (keys != NULL);
+  g_assert (keyvals != NULL);
+  g_assert (n_entries != NULL);
+
+  *keys = 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 ([event type])
+    {
+    case NSKeyDown:
+      return GDK_KEY_PRESS;
+    case NSKeyUp:
+      return GDK_KEY_RELEASE;
+    case NSFlagsChanged:
+      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;
+}
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..8e65e73c3d
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor-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_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);
+
+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..3b98691695
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.c
@@ -0,0 +1,82 @@
+/*
+ * 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 "gdkmacosmonitor-private.h"
+
+struct _GdkMacosMonitor
+{
+  GdkMonitor        parent_instance;
+  CGDirectDisplayID screen_id;
+};
+
+struct _GdkMacosMonitorClass
+{
+  GdkMonitorClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosMonitor, gdk_macos_monitor, GDK_TYPE_MONITOR)
+
+static void
+gdk_macos_monitor_finalize (GObject *object)
+{
+  GdkMacosMonitor *self = (GdkMacosMonitor *)object;
+
+  G_OBJECT_CLASS (gdk_macos_monitor_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_monitor_class_init (GdkMacosMonitorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gdk_macos_monitor_finalize;
+}
+
+static void
+gdk_macos_monitor_init (GdkMacosMonitor *self)
+{
+}
+
+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;
+
+  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..6b243d70fb
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface-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_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);
+
+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..7b15d193da
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkpopupprivate.h"
+
+#include "gdkmacospopupsurface-private.h"
+
+struct _GdkMacosPopupSurface
+{
+  GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosPopupSurfaceClass
+{
+  GdkMacosSurfaceClass parent_class;
+};
+
+static void
+popup_interface_init (GdkPopupInterface *iface)
+{
+}
+
+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)
+{
+  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)
+{
+  GdkMacosPopupSurface *self = GDK_MACOS_POPUP_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      break;
+
+    case LAST_PROP + GDK_POPUP_PROP_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)
+{
+  GdkMacosPopupSurface *self = GDK_MACOS_POPUP_SURFACE (object);
+
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      break;
+
+    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+      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)
+{
+  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_POPUP_SURFACE,
+                       "display", display,
+                       "frame-clock", frame_clock,
+                       NULL);
+}
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..5829888151
--- /dev/null
+++ b/gdk/macos/gdkmacossurface-private.h
@@ -0,0 +1,83 @@
+/*
+ * 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"
+
+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;
+};
+
+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,
+                                                               int                 scale);
+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);
+
+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..f6d307174b
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.c
@@ -0,0 +1,677 @@
+/*
+ * 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"
+#import "GdkMacosWindow.h"
+
+#include "gdkframeclockidleprivate.h"
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+typedef struct
+{
+  GdkMacosWindow *window;
+
+  char *title;
+
+  int shadow_top;
+  int shadow_right;
+  int shadow_bottom;
+  int shadow_left;
+
+  int scale;
+
+  guint modal_hint : 1;
+} GdkMacosSurfacePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (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)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  return ([priv->window styleMask] & NSWindowStyleMaskFullScreen) != 0;
+}
+
+static void
+gdk_macos_surface_set_input_region (GdkSurface     *surface,
+                                    cairo_region_t *region)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+  /* TODO: */
+}
+
+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;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  [priv->window hide];
+}
+
+static gint
+gdk_macos_surface_get_scale_factor (GdkSurface *surface)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  return [priv->window backingScaleFactor];
+}
+
+static void
+gdk_macos_surface_set_shadow_width (GdkSurface *surface,
+                                    int         left,
+                                    int         right,
+                                    int         top,
+                                    int         bottom)
+{
+  GdkMacosSurface *self = (GdkMacosSurface *)surface;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  priv->shadow_top = top;
+  priv->shadow_right = right;
+  priv->shadow_bottom = bottom;
+  priv->shadow_left = left;
+
+  if (top || right || bottom || left)
+    [priv->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;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  [priv->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;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  [priv->window beginManualMove];
+}
+
+static void
+gdk_macos_surface_predict_presentation_time (GdkMacosSurface *self)
+{
+  g_assert (GDK_IS_MACOS_SURFACE (self));
+
+}
+
+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)
+{
+  g_assert (GDK_IS_MACOS_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_predict_presentation_time (self);
+  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;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  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 = [priv->window contentRectForFrameRect:[priv->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;
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  GdkMacosWindow *window = g_steal_pointer (&priv->window);
+
+  g_clear_pointer (&priv->title, g_free);
+
+  if (window != NULL)
+    [window close];
+
+  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);
+
+  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);
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_NATIVE:
+      g_value_set_pointer (value, priv->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);
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_NATIVE:
+      priv->window = g_value_get_pointer (value);
+      [priv->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)
+{
+}
+
+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)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (top)
+    *top = priv->shadow_top;
+
+  if (left)
+    *left = priv->shadow_left;
+
+  if (bottom)
+    *bottom = priv->shadow_bottom;
+
+  if (right)
+    *right = priv->shadow_right;
+}
+
+const char *
+_gdk_macos_surface_get_title (GdkMacosSurface *self)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  return priv->title;
+}
+
+void
+_gdk_macos_surface_set_title (GdkMacosSurface *self,
+                              const gchar     *title)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (title == NULL)
+    title = "";
+
+  if (g_strcmp0 (priv->title, title) != 0)
+    {
+      g_free (priv->title);
+      priv->title = g_strdup (title);
+
+      GDK_BEGIN_MACOS_ALLOC_POOL;
+      [priv->window setTitle:[NSString stringWithUTF8String:title]];
+      GDK_END_MACOS_ALLOC_POOL;
+    }
+}
+
+gboolean
+_gdk_macos_surface_get_modal_hint (GdkMacosSurface *self)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE);
+
+  return priv->modal_hint;
+}
+
+void
+_gdk_macos_surface_set_modal_hint (GdkMacosSurface *self,
+                                   gboolean         modal_hint)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  priv->modal_hint = !!modal_hint;
+}
+
+CGDirectDisplayID
+_gdk_macos_surface_get_screen_id (GdkMacosSurface *self)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), (CGDirectDisplayID)-1);
+
+  if (priv->window != NULL)
+    {
+      NSScreen *screen = [priv->window screen];
+      return [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+    }
+
+  return (CGDirectDisplayID)-1;
+}
+
+NSWindow *
+_gdk_macos_surface_get_native (GdkMacosSurface *self)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+
+  g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+  return (NSWindow *)priv->window;
+}
+
+void
+_gdk_macos_surface_set_geometry_hints (GdkMacosSurface   *self,
+                                       const GdkGeometry *geometry,
+                                       GdkSurfaceHints    geom_mask)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  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 (priv->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);
+
+  [priv->window setMaxSize:max_size];
+  [priv->window setMinSize:min_size];
+}
+
+void
+_gdk_macos_surface_resize (GdkMacosSurface *self,
+                           int              width,
+                           int              height,
+                           int              scale)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  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 = [priv->window frameRectForContentRect:content_rect];
+  [priv->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;
+
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  GdkSurface *surface = GDK_SURFACE (self);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  NSRect frame_rect = [priv->window frame];
+  NSRect content_rect = [priv->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)
+{
+  GdkMacosSurfacePrivate *priv = gdk_macos_surface_get_instance_private (self);
+  GdkMacosCairoView *view;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+  g_return_if_fail (surface != NULL);
+
+  view = (GdkMacosCairoView *)[priv->window contentView];
+  [view setCairoSurfaceWithRegion:surface
+                      cairoRegion:painted];
+}
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..b62f0f2818
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface-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_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);
+
+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..2132d91b63
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface.c
@@ -0,0 +1,420 @@
+/*
+ * 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;
+
+  GdkMacosSurface *transient_for;
+
+  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, -1);
+
+  /* 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];
+
+  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 *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+  [window makeKeyAndOrderFront:window];
+}
+
+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));
+
+  g_set_object (&self->transient_for, parent);
+}
+
+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_destroy (GdkSurface *surface,
+                                     gboolean    foreign_destroy)
+{
+  GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+
+  g_clear_object (&self->transient_for);
+
+  GDK_SURFACE_CLASS (_gdk_macos_toplevel_surface_parent_class)->destroy (surface, foreign_destroy);
+}
+
+static void
+_gdk_macos_toplevel_surface_constructed (GObject *object)
+{
+  //GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)object;
+
+  G_OBJECT_CLASS (_gdk_macos_toplevel_surface_parent_class)->constructed (object);
+
+}
+
+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, toplevel->transient_for);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+      g_value_set_boolean (value, _gdk_macos_surface_get_modal_hint (base));
+      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_macos_surface_set_modal_hint (base, 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->constructed = _gdk_macos_toplevel_surface_constructed;
+  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;
+
+  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);
+}
diff --git a/gdk/macos/gdkmacosutils-private.h b/gdk/macos/gdkmacosutils-private.h
new file mode 100644
index 0000000000..49185551a4
--- /dev/null
+++ b/gdk/macos/gdkmacosutils-private.h
@@ -0,0 +1,29 @@
+/*
+ * 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]
+
+#endif /* __GDK_MACOS_UTILS_PRIVATE_H__ */
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
new file mode 100644
index 0000000000..03fae86055
--- /dev/null
+++ b/gdk/macos/meson.build
@@ -0,0 +1,50 @@
+gdk_macos_sources = files([
+  'GdkMacosWindow.c',
+  'gdkmacoscairocontext.c',
+  'gdkmacoscursor.c',
+  'gdkmacosdevice.c',
+  'gdkmacosdisplay.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',
+  '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 108fa2826e..cda37a6834 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -985,6 +985,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,
@@ -994,7 +1000,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]