[gtk/wip/chergert/quartz4u] macos: start on macos GDK backend
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/quartz4u] macos: start on macos GDK backend
- Date: Wed, 13 May 2020 21:42:33 +0000 (UTC)
commit 46d964405e4a4f96a5eddc80fd719ad597e42988
Author: Christian Hergert <chergert redhat com>
Date: Thu Apr 23 16:36:46 2020 -0700
macos: start on macos GDK backend
gdk/gdkconfig.h.meson | 1 +
gdk/gdkcontentdeserializer.c | 12 +-
gdk/gdkcontentserializer.c | 18 +-
gdk/gdkdisplaymanager.c | 7 +
gdk/macos/GdkMacosBaseView.c | 702 +++++++++++++++++
gdk/macos/GdkMacosBaseView.h | 43 ++
gdk/macos/GdkMacosCairoView.c | 125 +++
gdk/macos/GdkMacosCairoView.h | 36 +
gdk/macos/GdkMacosWindow.c | 626 +++++++++++++++
gdk/macos/GdkMacosWindow.h | 68 ++
gdk/macos/gdkdisplaylinksource.c | 253 +++++++
gdk/macos/gdkdisplaylinksource.h | 48 ++
gdk/macos/gdkmacos.h | 27 +
gdk/macos/gdkmacoscairocontext-private.h | 38 +
gdk/macos/gdkmacoscairocontext.c | 166 ++++
gdk/macos/gdkmacoscursor-private.h | 32 +
gdk/macos/gdkmacoscursor.c | 135 ++++
gdk/macos/gdkmacosdevice.c | 219 ++++++
gdk/macos/gdkmacosdevice.h | 43 ++
gdk/macos/gdkmacosdisplay-private.h | 137 ++++
gdk/macos/gdkmacosdisplay-settings.c | 194 +++++
gdk/macos/gdkmacosdisplay-translate.c | 870 +++++++++++++++++++++
gdk/macos/gdkmacosdisplay.c | 955 +++++++++++++++++++++++
gdk/macos/gdkmacosdisplay.h | 47 ++
gdk/macos/gdkmacosdragsurface-private.h | 45 ++
gdk/macos/gdkmacosdragsurface.c | 80 ++
gdk/macos/gdkmacoseventsource-private.h | 40 +
gdk/macos/gdkmacoseventsource.c | 1094 +++++++++++++++++++++++++++
gdk/macos/gdkmacoskeymap-private.h | 36 +
gdk/macos/gdkmacoskeymap.c | 698 +++++++++++++++++
gdk/macos/gdkmacoskeymap.h | 43 ++
gdk/macos/gdkmacosmonitor-private.h | 39 +
gdk/macos/gdkmacosmonitor.c | 284 +++++++
gdk/macos/gdkmacosmonitor.h | 43 ++
gdk/macos/gdkmacospopupsurface-private.h | 47 ++
gdk/macos/gdkmacospopupsurface.c | 347 +++++++++
gdk/macos/gdkmacosseat-private.h | 35 +
gdk/macos/gdkmacosseat.c | 65 ++
gdk/macos/gdkmacossurface-private.h | 121 +++
gdk/macos/gdkmacossurface.c | 887 ++++++++++++++++++++++
gdk/macos/gdkmacossurface.h | 43 ++
gdk/macos/gdkmacostoplevelsurface-private.h | 47 ++
gdk/macos/gdkmacostoplevelsurface.c | 473 ++++++++++++
gdk/macos/gdkmacosutils-private.h | 36 +
gdk/macos/meson.build | 53 ++
gdk/meson.build | 14 +-
gtk/meson.build | 8 +-
meson.build | 17 +-
meson_options.txt | 2 +
subprojects/atk.wrap | 5 +
50 files changed, 9374 insertions(+), 30 deletions(-)
---
diff --git a/gdk/gdkconfig.h.meson b/gdk/gdkconfig.h.meson
index 80666b25f0..867b430e43 100644
--- a/gdk/gdkconfig.h.meson
+++ b/gdk/gdkconfig.h.meson
@@ -12,6 +12,7 @@ G_BEGIN_DECLS
#mesondefine GDK_WINDOWING_X11
#mesondefine GDK_WINDOWING_BROADWAY
+#mesondefine GDK_WINDOWING_MACOS
#mesondefine GDK_WINDOWING_WAYLAND
#mesondefine GDK_WINDOWING_WIN32
diff --git a/gdk/gdkcontentdeserializer.c b/gdk/gdkcontentdeserializer.c
index 12594b1fd8..3846da9745 100644
--- a/gdk/gdkcontentdeserializer.c
+++ b/gdk/gdkcontentdeserializer.c
@@ -41,7 +41,7 @@
typedef struct _Deserializer Deserializer;
-struct _Deserializer
+struct _Deserializer
{
const char * mime_type; /* interned */
GType type;
@@ -424,7 +424,7 @@ lookup_deserializer (const char *mime_type,
deserializer->type == type)
return deserializer;
}
-
+
return NULL;
}
@@ -835,8 +835,8 @@ init (void)
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
- gchar *name;
-
+ gchar *name;
+
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
@@ -849,7 +849,7 @@ init (void)
}
g_free (name);
- }
+ }
for (f = formats; f; f = f->next)
{
@@ -875,7 +875,7 @@ init (void)
g_slist_free (formats);
-#ifdef G_OS_UNIX
+#if defined(G_OS_UNIX) && !defined(__APPLE__)
file_transfer_portal_register ();
#endif
diff --git a/gdk/gdkcontentserializer.c b/gdk/gdkcontentserializer.c
index 63d1232167..ce7af1cb3f 100644
--- a/gdk/gdkcontentserializer.c
+++ b/gdk/gdkcontentserializer.c
@@ -43,7 +43,7 @@
typedef struct _Serializer Serializer;
-struct _Serializer
+struct _Serializer
{
const char * mime_type; /* interned */
GType type;
@@ -427,7 +427,7 @@ lookup_serializer (const char *mime_type,
serializer->type == type)
return serializer;
}
-
+
return NULL;
}
@@ -606,7 +606,7 @@ pixbuf_serializer (GdkContentSerializer *serializer)
const GValue *value;
GdkPixbuf *pixbuf;
const char *name;
-
+
name = gdk_content_serializer_get_user_data (serializer);
value = gdk_content_serializer_get_value (serializer);
@@ -732,7 +732,7 @@ file_uri_serializer (GdkContentSerializer *serializer)
else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
{
GSList *l;
-
+
for (l = g_value_get_boxed (value); l; l = l->next)
{
uri = g_file_get_uri (l->data);
@@ -776,7 +776,7 @@ file_text_serializer (GdkContentSerializer *serializer)
{
GString *str;
GSList *l;
-
+
str = g_string_new (NULL);
for (l = g_value_get_boxed (value); l; l = l->next)
@@ -864,8 +864,8 @@ init (void)
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
- gchar *name;
-
+ gchar *name;
+
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
@@ -878,7 +878,7 @@ init (void)
}
g_free (name);
- }
+ }
for (f = formats; f; f = f->next)
{
@@ -907,7 +907,7 @@ init (void)
g_slist_free (formats);
-#ifdef G_OS_UNIX
+#if defined(G_OS_UNIX) && !defined(__APPLE__)
file_transfer_portal_register ();
#endif
diff --git a/gdk/gdkdisplaymanager.c b/gdk/gdkdisplaymanager.c
index 8a359b77c6..2452f110e1 100644
--- a/gdk/gdkdisplaymanager.c
+++ b/gdk/gdkdisplaymanager.c
@@ -50,6 +50,10 @@
#include "broadway/gdkprivate-broadway.h"
#endif
+#ifdef GDK_WINDOWING_MACOS
+#include "macos/gdkmacosdisplay-private.h"
+#endif
+
#ifdef GDK_WINDOWING_WIN32
#include "win32/gdkwin32.h"
#include "win32/gdkprivate-win32.h"
@@ -262,6 +266,9 @@ static GdkBackend gdk_backends[] = {
#ifdef GDK_WINDOWING_QUARTZ
{ "quartz", _gdk_quartz_display_open },
#endif
+#ifdef GDK_WINDOWING_MACOS
+ { "macos", _gdk_macos_display_open },
+#endif
#ifdef GDK_WINDOWING_WIN32
{ "win32", _gdk_win32_display_open },
#endif
diff --git a/gdk/macos/GdkMacosBaseView.c b/gdk/macos/GdkMacosBaseView.c
new file mode 100644
index 0000000000..825b022546
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -0,0 +1,702 @@
+/* GdkMacosBaseView.c
+ *
+ * Copyright 2005-2007 Imendio AB
+ * Copyright 2011 Hiroyuki Yamamoto
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+/* Text Input Client */
+#define TIC_MARKED_TEXT "tic-marked-text"
+#define TIC_SELECTED_POS "tic-selected-pos"
+#define TIC_SELECTED_LEN "tic-selected-len"
+#define TIC_INSERT_TEXT "tic-insert-text"
+#define TIC_IN_KEY_DOWN "tic-in-key-down"
+
+/* GtkIMContext */
+#define GIC_CURSOR_RECT "gic-cursor-rect"
+#define GIC_FILTER_KEY "gic-filter-key"
+#define GIC_FILTER_PASSTHRU 0
+#define GIC_FILTER_FILTERED 1
+
+@implementation GdkMacosBaseView
+
+-(id)initWithFrame:(NSRect)frameRect
+{
+ if ((self = [super initWithFrame: frameRect]))
+ {
+ NSRect rect = NSMakeRect (0, 0, 0, 0);
+ NSTrackingAreaOptions options;
+
+ markedRange = NSMakeRange (NSNotFound, 0);
+ selectedRange = NSMakeRange (0, 0);
+ [self setValue: @(YES) forKey: @"postsFrameChangedNotifications"];
+
+ options = (NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingInVisibleRect |
+ NSTrackingActiveAlways);
+ trackingArea = [[NSTrackingArea alloc] initWithRect:rect
+ options:options
+ owner:(id)self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+ }
+
+ return self;
+}
+
+-(BOOL)acceptsFirstMouse
+{
+ return YES;
+}
+
+-(BOOL)acceptsFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("acceptsFirstResponder"));
+ return YES;
+}
+
+-(BOOL)becomeFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("becomeFirstResponder"));
+ return YES;
+}
+
+-(BOOL)resignFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("resignFirstResponder"));
+ return YES;
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate
+{
+ needsInvalidateShadow = invalidate;
+}
+
+-(NSTrackingArea *)trackingArea
+{
+ return trackingArea;
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+ return [(GdkMacosWindow *)[self window] gdkSurface];
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+ GdkMacosSurface *surface = [self gdkSurface];
+ GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (surface));
+
+ return GDK_MACOS_DISPLAY (display);
+}
+
+-(void)keyDown:(NSEvent *)theEvent
+{
+ /* NOTE: When user press Cmd+A, interpretKeyEvents: will call noop:
+ * method. When user press and hold A to show the accented char window,
+ * it consumed repeating key down events for key 'A' do NOT call
+ * any other method. We use this behavior to determine if this key
+ * down event is filtered by interpretKeyEvents.
+ */
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+ GDK_NOTE (EVENTS, g_message ("keyDown"));
+ [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
+}
+
+-(void)flagsChanged: (NSEvent *)theEvent
+{
+}
+
+-(NSUInteger)characterIndexForPoint:(NSPoint)aPoint
+{
+ GDK_NOTE (EVENTS, g_message ("characterIndexForPoint"));
+ return 0;
+}
+
+-(NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange: (NSRangePointer)actualRange
+{
+ GdkRectangle *rect;
+
+ GDK_NOTE (EVENTS, g_message ("firstRectForCharacterRange"));
+
+ if ((rect = g_object_get_data (G_OBJECT ([self gdkSurface]), GIC_CURSOR_RECT)))
+ {
+ GdkMacosDisplay *display = [self gdkDisplay];
+ int ns_x, ns_y;
+
+ _gdk_macos_display_to_display_coords (display,
+ rect->x, rect->y + rect->height,
+ &ns_x, &ns_y);
+
+ return NSMakeRect (ns_x, ns_y, rect->width, rect->height);
+ }
+
+ return NSMakeRect (0, 0, 0, 0);
+}
+
+-(NSArray *)validAttributesForMarkedText
+{
+ GDK_NOTE (EVENTS, g_message ("validAttributesForMarkedText"));
+ return [NSArray arrayWithObjects: NSUnderlineStyleAttributeName, nil];
+}
+
+-(NSAttributedString *)attributedSubstringForProposedRange: (NSRange)aRange actualRange:
(NSRangePointer)actualRange
+{
+ GDK_NOTE (EVENTS, g_message ("attributedSubstringForProposedRange"));
+ return nil;
+}
+
+-(BOOL)hasMarkedText
+{
+ GDK_NOTE (EVENTS, g_message ("hasMarkedText"));
+ return markedRange.location != NSNotFound && markedRange.length != 0;
+}
+
+-(NSRange)markedRange
+{
+ GDK_NOTE (EVENTS, g_message ("markedRange"));
+ return markedRange;
+}
+
+-(NSRange)selectedRange
+{
+ GDK_NOTE (EVENTS, g_message ("selectedRange"));
+ return selectedRange;
+}
+
+-(void)unmarkText
+{
+ GDK_NOTE (EVENTS, g_message ("unmarkText"));
+
+ selectedRange = NSMakeRange (0, 0);
+ markedRange = NSMakeRange (NSNotFound, 0);
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, NULL, g_free);
+}
+
+-(void)setMarkedText:(id)aString selectedRange: (NSRange)newSelection replacementRange:
(NSRange)replacementRange
+{
+ const char *str;
+
+ GDK_NOTE (EVENTS, g_message ("setMarkedText"));
+
+ if (replacementRange.location == NSNotFound)
+ {
+ markedRange = NSMakeRange (newSelection.location, [aString length]);
+ selectedRange = NSMakeRange (newSelection.location, newSelection.length);
+ }
+ else
+ {
+ markedRange = NSMakeRange (replacementRange.location, [aString length]);
+ selectedRange = NSMakeRange (replacementRange.location + newSelection.location, newSelection.length);
+ }
+
+ if ([aString isKindOfClass: [NSAttributedString class]])
+ str = [[aString string] UTF8String];
+ else
+ str = [aString UTF8String];
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, g_strdup (str), g_free);
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ TIC_SELECTED_POS,
+ GUINT_TO_POINTER (selectedRange.location));
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ TIC_SELECTED_LEN,
+ GUINT_TO_POINTER (selectedRange.length));
+
+ GDK_NOTE (EVENTS, g_message ("setMarkedText: set %s (%p, nsview %p): %s",
+ TIC_MARKED_TEXT, [self gdkSurface], self,
+ str ? str : "(empty)"));
+
+ /* handle text input changes by mouse events */
+ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+ _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)doCommandBySelector:(SEL)aSelector
+{
+ GDK_NOTE (EVENTS, g_message ("doCommandBySelector"));
+
+ if ([self respondsToSelector: aSelector])
+ [self performSelector: aSelector];
+}
+
+-(void)insertText:(id)aString replacementRange: (NSRange)replacementRange
+{
+ const char *str;
+ NSString *string;
+
+ GDK_NOTE (EVENTS, g_message ("insertText"));
+
+ if ([self hasMarkedText])
+ [self unmarkText];
+
+ if ([aString isKindOfClass: [NSAttributedString class]])
+ string = [aString string];
+ else
+ string = aString;
+
+ NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
+ NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ if ([string rangeOfCharacterFromSet:ctrlChars].length &&
+ [string rangeOfCharacterFromSet:wsnlChars].length == 0)
+ {
+ /* discard invalid text input with Chinese input methods */
+ str = "";
+ [self unmarkText];
+ [[NSTextInputContext currentInputContext] discardMarkedText];
+ }
+ else
+ {
+ str = [string UTF8String];
+ }
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_INSERT_TEXT, g_strdup (str), g_free);
+ GDK_NOTE (EVENTS, g_message ("insertText: set %s (%p, nsview %p): %s",
+ TIC_INSERT_TEXT, [self gdkSurface], self,
+ str ? str : "(empty)"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+ /* handle text input changes by mouse events */
+ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+ _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)deleteBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToBeginningOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteToBeginningOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToEndOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteToEndOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteWordBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteWordForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertBacktab:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertBacktab"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertNewline:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertNewline"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertTab:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertTab"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveBackwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDown:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveDown"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDownAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveDownAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveForwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeft:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveLeft"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeftAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveLeftAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRight:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveRight"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRightAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveRightAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocument:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocument"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocumentAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocumentAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLineAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLineAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocument:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfDocument"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocumentAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfDocumentAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLineAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfLineAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUp:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveUp"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUpAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveUpAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordBackwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordForwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeft:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordLeft"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeftAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordLeftAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRight:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordRight"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRightAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordRightAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDown:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageDown"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDownAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageDownAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUp:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageUp"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUpAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageUpAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectAll:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectAll"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectWord:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectWord"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)noop: (id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("noop"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+@end
diff --git a/gdk/macos/GdkMacosBaseView.h b/gdk/macos/GdkMacosBaseView.h
new file mode 100644
index 0000000000..03510db3b5
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -0,0 +1,43 @@
+/* GdkMacosBaseView.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+
+@interface GdkMacosBaseView : NSView
+{
+ NSTrackingArea *trackingArea;
+ BOOL needsInvalidateShadow;
+ NSRange markedRange;
+ NSRange selectedRange;
+}
+
+-(GdkMacosSurface *)gdkSurface;
+-(GdkMacosDisplay *)gdkDisplay;
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(NSTrackingArea *)trackingArea;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
new file mode 100644
index 0000000000..48d763e739
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -0,0 +1,125 @@
+/* GdkMacosCairoView.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <cairo-quartz.h>
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoView
+
+-(void)dealloc
+{
+ g_clear_pointer (&self->surface, cairo_surface_destroy);
+ [super dealloc];
+}
+
+-(BOOL)isOpaque
+{
+ return NO;
+}
+
+-(BOOL)isFlipped
+{
+ return YES;
+}
+
+-(void)setCairoSurfaceWithRegion:(cairo_surface_t *)cairoSurface
+ cairoRegion:(cairo_region_t *)cairoRegion
+{
+ guint n_rects = cairo_region_num_rectangles (cairoRegion);
+
+ if (self->surface == NULL)
+ {
+ NSRect rect = [self bounds];
+ rect.origin.x = rect.origin.y = 0;
+ [self setNeedsDisplayInRect:rect];
+ }
+ else
+ {
+ for (guint i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+
+ cairo_region_get_rectangle (cairoRegion, i, &rect);
+ [self setNeedsDisplayInRect:NSMakeRect (rect.x, rect.y, rect.width, rect.height)];
+ }
+ }
+
+ g_clear_pointer (&self->surface, cairo_surface_destroy);
+ self->surface = cairo_surface_reference (cairoSurface);
+}
+
+-(void)drawRect:(NSRect)rect
+{
+ GdkMacosSurface *gdkSurface;
+ CGContextRef cg_context;
+ cairo_surface_t *dest;
+ const NSRect *rects = NULL;
+ NSInteger n_rects = 0;
+ cairo_t *cr;
+ int scale_factor;
+
+ if (self->surface == NULL)
+ return;
+
+ gdkSurface = [self gdkSurface];
+ cg_context = _gdk_macos_surface_acquire_context (gdkSurface, TRUE, TRUE);
+ scale_factor = gdk_surface_get_scale_factor (GDK_SURFACE (gdkSurface));
+
+ dest = cairo_quartz_surface_create_for_cg_context (cg_context,
+ self.bounds.size.width * scale_factor,
+ self.bounds.size.height * scale_factor);
+ cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+
+ cr = cairo_create (dest);
+
+ cairo_set_source_surface (cr, self->surface, 0, 0);
+
+ [self getRectsBeingDrawn:&rects count:&n_rects];
+ for (NSInteger i = 0; i < n_rects; i++)
+ {
+ const NSRect *r = &rects[i];
+ cairo_rectangle (cr,
+ r->origin.x,
+ r->origin.y,
+ r->size.width,
+ r->size.height);
+ }
+ cairo_clip (cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ cairo_surface_flush (dest);
+ cairo_surface_destroy (dest);
+
+ _gdk_macos_surface_release_context (gdkSurface, cg_context);
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosCairoView.h
new file mode 100644
index 0000000000..c66e8b730a
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -0,0 +1,36 @@
+/* GdkMacosCairoView.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <cairo.h>
+
+#import "GdkMacosBaseView.h"
+
+#define GDK_IS_MACOS_CAIRO_VIEW(obj) ([obj isKindOfClass:[GdkMacosCairoView class]])
+
+@interface GdkMacosCairoView : GdkMacosBaseView
+{
+ cairo_surface_t *surface;
+}
+
+-(void)setCairoSurfaceWithRegion:(cairo_surface_t *)cairoSurface
+ cairoRegion:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
new file mode 100644
index 0000000000..a809e19bcb
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.c
@@ -0,0 +1,626 @@
+/* GdkMacosWindow.m
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdk.h>
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosCairoView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+
+#include "gdksurfaceprivate.h"
+
+@implementation GdkMacosWindow
+
+-(void)windowWillClose:(NSNotification*)notification
+{
+ /* Clears the delegate when window is going to be closed; since EL
+ * Capitan it is possible that the methods of delegate would get called
+ * after the window has been closed.
+ */
+ [self setDelegate:nil];
+}
+
+-(BOOL)windowShouldClose:(id)sender
+{
+ GdkDisplay *display;
+ GdkEvent *event;
+
+ display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+ event = gdk_delete_event_new (GDK_SURFACE (gdk_surface));
+
+ _gdk_event_queue_append (display, event);
+
+ return NO;
+}
+
+-(void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+ if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+ _gdk_macos_toplevel_surface_detach_from_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+ else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+ _gdk_macos_popup_surface_detach_from_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+}
+
+-(void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MINIMIZED);
+}
+
+-(void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+ if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+ _gdk_macos_toplevel_surface_attach_to_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+ else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+ _gdk_macos_popup_surface_attach_to_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MINIMIZED, 0);
+}
+
+-(void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_FOCUSED);
+ _gdk_surface_update_viewable (GDK_SURFACE (gdk_surface));
+ _gdk_macos_display_surface_became_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignKey:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_FOCUSED, 0);
+ _gdk_surface_update_viewable (GDK_SURFACE (gdk_surface));
+ _gdk_macos_display_surface_resigned_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+ if (![self isVisible])
+ {
+ /* Note: This is a hack needed because for unknown reasons, hidden
+ * windows get shown when clicking the dock icon when the application
+ * is not already active.
+ */
+ [self orderOut:nil];
+ return;
+ }
+
+ _gdk_macos_display_surface_became_main ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignMain:(NSNotification *)aNotification
+{
+ _gdk_macos_display_surface_resigned_main ([self gdkDisplay], gdk_surface);
+}
+
+/* Used in combination with NSLeftMouseUp in sendEvent to keep track
+ * of when the window is being moved with the mouse.
+ */
+-(void)windowWillMove:(NSNotification *)aNotification
+{
+ inMove = YES;
+}
+
+-(void)sendEvent:(NSEvent *)event
+{
+ NSEventType event_type = [event type];
+
+ switch ((int)event_type)
+ {
+ case NSEventTypeLeftMouseUp: {
+ GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+ double time = ((double)[event timestamp]) * 1000.0;
+
+ _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time);
+
+ inManualMove = NO;
+ inManualResize = NO;
+ inMove = NO;
+
+ break;
+ }
+
+ case NSEventTypeLeftMouseDragged:
+ if ([self trackManualMove] || [self trackManualResize])
+ return;
+ break;
+
+ default:
+ break;
+ }
+
+ [super sendEvent:event];
+}
+
+-(BOOL)isInMove
+{
+ return inMove;
+}
+
+-(void)checkSendEnterNotify
+{
+ /* When a new window has been created, and the mouse is in the window
+ * area, we will not receive an NSEventTypeMouseEntered event.
+ * Therefore, we synthesize an enter notify event manually.
+ */
+ if (!initialPositionKnown)
+ {
+ initialPositionKnown = YES;
+
+ if (NSPointInRect ([NSEvent mouseLocation], [self frame]))
+ {
+ GdkMacosBaseView *view = (GdkMacosBaseView *)[self contentView];
+ NSEvent *event;
+
+ event = [NSEvent enterExitEventWithType: NSEventTypeMouseEntered
+ location: [self mouseLocationOutsideOfEventStream]
+ modifierFlags: 0
+ timestamp: [[NSApp currentEvent] timestamp]
+ windowNumber: [self windowNumber]
+ context: NULL
+ eventNumber: 0
+ trackingNumber: (NSInteger)[view trackingArea]
+ userData: nil];
+
+ [NSApp postEvent:event atStart:NO];
+ }
+ }
+}
+
+-(void)windowDidMove:(NSNotification *)aNotification
+{
+ GdkSurface *surface = GDK_SURFACE (gdk_surface);
+ GdkDisplay *display = gdk_surface_get_display (surface);
+ gboolean maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+ GdkEvent *event;
+
+ /* In case the window is changed when maximized remove the maximized state */
+ if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+ gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+ _gdk_macos_surface_update_position (gdk_surface);
+
+ event = gdk_configure_event_new (surface, surface->width, surface->height);
+ _gdk_event_queue_append (GDK_DISPLAY (display), event);
+
+ [self checkSendEnterNotify];
+}
+
+-(void)windowDidResize:(NSNotification *)aNotification
+{
+ NSRect content_rect;
+ GdkSurface *surface;
+ GdkDisplay *display;
+ GdkEvent *event;
+ gboolean maximized;
+
+ surface = GDK_SURFACE (gdk_surface);
+ display = gdk_surface_get_display (surface);
+
+ content_rect = [self contentRectForFrameRect:[self frame]];
+ maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+
+ /* see same in windowDidMove */
+ if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+ gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+ surface->width = content_rect.size.width;
+ surface->height = content_rect.size.height;
+
+ /* Certain resize operations (e.g. going fullscreen), also move the
+ * origin of the window.
+ */
+ _gdk_macos_surface_update_position (GDK_MACOS_SURFACE (surface));
+
+ [[self contentView] setFrame:NSMakeRect (0, 0, surface->width, surface->height)];
+
+ _gdk_surface_update_size (surface);
+
+ /* Synthesize a configure event */
+ event = gdk_configure_event_new (surface,
+ content_rect.size.width,
+ content_rect.size.height);
+ _gdk_event_queue_append (display, event);
+
+ [self checkSendEnterNotify];
+}
+
+-(id)initWithContentRect:(NSRect)contentRect
+ styleMask:(NSWindowStyleMask)styleMask
+ backing:(NSBackingStoreType)backingType
+ defer:(BOOL)flag
+ screen:(NSScreen *)screen
+{
+ GdkMacosCairoView *view;
+
+ self = [super initWithContentRect:contentRect
+ styleMask:styleMask
+ backing:backingType
+ defer:flag
+ screen:screen];
+
+ [self setAcceptsMouseMovedEvents:YES];
+ [self setDelegate:(id<NSWindowDelegate>)self];
+ [self setReleasedWhenClosed:YES];
+
+ view = [[GdkMacosCairoView alloc] initWithFrame:contentRect];
+ [self setContentView:view];
+ [view release];
+
+ return self;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+ return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+ return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(void)showAndMakeKey:(BOOL)makeKey
+{
+ inShowOrHide = YES;
+
+ if (makeKey)
+ [self makeKeyAndOrderFront:nil];
+ else
+ [self orderFront:nil];
+
+ inShowOrHide = NO;
+
+ [self checkSendEnterNotify];
+}
+
+-(void)hide
+{
+ inShowOrHide = YES;
+ [self orderOut:nil];
+ inShowOrHide = NO;
+
+ initialPositionKnown = NO;
+}
+
+-(BOOL)trackManualMove
+{
+ NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
+ NSRect windowFrame = [self frame];
+ NSPoint currentLocation;
+ NSPoint newOrigin;
+ int shadow_top = 0;
+
+ if (!inManualMove)
+ return NO;
+
+ currentLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+ newOrigin.x = currentLocation.x - initialMoveLocation.x;
+ newOrigin.y = currentLocation.y - initialMoveLocation.y;
+
+ _gdk_macos_surface_get_shadow (gdk_surface, &shadow_top, NULL, NULL, NULL);
+
+ /* Clamp vertical position to below the menu bar. */
+ if (newOrigin.y + windowFrame.size.height - shadow_top > screenFrame.origin.y + screenFrame.size.height)
+ newOrigin.y = screenFrame.origin.y + screenFrame.size.height - windowFrame.size.height + shadow_top;
+
+ [self setFrameOrigin:newOrigin];
+
+ return YES;
+}
+
+/* Used by gdkevents-quartz.c to decide if our sendEvent() handler above
+ * will see the event or if it will be subjected to standard processing
+ * by GDK.
+*/
+-(BOOL)isInManualResizeOrMove
+{
+ return inManualResize || inManualMove;
+}
+
+-(void)beginManualMove
+{
+ NSRect frame = [self frame];
+
+ if (inMove || inManualMove || inManualResize)
+ return;
+
+ inManualMove = YES;
+
+ initialMoveLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+ initialMoveLocation.x -= frame.origin.x;
+ initialMoveLocation.y -= frame.origin.y;
+}
+
+-(BOOL)trackManualResize
+{
+ NSPoint mouse_location;
+ NSRect new_frame;
+ float mdx, mdy, dw, dh, dx, dy;
+ NSSize min_size;
+
+ if (!inManualResize || inTrackManualResize)
+ return NO;
+
+ inTrackManualResize = YES;
+
+ mouse_location = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+ mdx = initialResizeLocation.x - mouse_location.x;
+ mdy = initialResizeLocation.y - mouse_location.y;
+
+ /* Set how a mouse location delta translates to changes in width,
+ * height and position.
+ */
+ dw = dh = dx = dy = 0.0;
+ if (resizeEdge == GDK_SURFACE_EDGE_EAST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+ {
+ dw = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_NORTH ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST)
+ {
+ dh = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_SOUTH ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+ {
+ dh = 1.0;
+ dy = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST)
+ {
+ dw = 1.0;
+ dx = -1.0;
+ }
+
+ /* Apply changes to the frame captured when we started resizing */
+ new_frame = initialResizeFrame;
+ new_frame.origin.x += mdx * dx;
+ new_frame.origin.y += mdy * dy;
+ new_frame.size.width += mdx * dw;
+ new_frame.size.height += mdy * dh;
+
+ /* In case the resulting window would be too small reduce the
+ * change to both size and position.
+ */
+ min_size = [self contentMinSize];
+
+ if (new_frame.size.width < min_size.width)
+ {
+ if (dx)
+ new_frame.origin.x -= min_size.width - new_frame.size.width;
+ new_frame.size.width = min_size.width;
+ }
+
+ if (new_frame.size.height < min_size.height)
+ {
+ if (dy)
+ new_frame.origin.y -= min_size.height - new_frame.size.height;
+ new_frame.size.height = min_size.height;
+ }
+
+ /* We could also apply aspect ratio:
+ new_frame.size.height = new_frame.size.width / [self aspectRatio].width * [self aspectRatio].height;
+ */
+
+ [self setFrame:new_frame display:YES];
+
+ /* Let the resizing be handled by GTK+. */
+ if (g_main_context_pending (NULL))
+ g_main_context_iteration (NULL, FALSE);
+
+ inTrackManualResize = NO;
+
+ return YES;
+}
+
+-(void)beginManualResize:(GdkSurfaceEdge)edge
+{
+ if (inMove || inManualMove || inManualResize)
+ return;
+
+ inManualResize = YES;
+ resizeEdge = edge;
+
+ initialResizeFrame = [self frame];
+ initialResizeLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return NSDragOperationNone;
+}
+
+-(void)draggingEnded:(id <NSDraggingInfo>)sender
+{
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return NSDragOperationNone;
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ return YES;
+}
+
+-(BOOL)wantsPeriodicDraggingUpdates
+{
+ return NO;
+}
+
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+}
+
+-(void)setStyleMask:(NSWindowStyleMask)styleMask
+{
+ gboolean was_fullscreen;
+ gboolean is_fullscreen;
+ gboolean was_opaque;
+ gboolean is_opaque;
+
+ was_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+ was_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+ [super setStyleMask:styleMask];
+
+ is_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+ is_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+ if (was_fullscreen != is_fullscreen)
+ _gdk_macos_surface_update_fullscreen_state (gdk_surface);
+
+ if (was_opaque != is_opaque)
+ {
+ [self setOpaque:is_opaque];
+
+ if (!is_opaque)
+ [self setBackgroundColor:[NSColor clearColor]];
+ }
+}
+
+-(NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
+{
+ GdkMacosSurface *surface = gdk_surface;
+ NSRect rect;
+ gint shadow_top;
+
+ /* Allow the window to move up "shadow_top" more than normally allowed
+ * by the default impl. This makes it possible to move windows with
+ * client side shadow right up to the screen's menu bar. */
+ _gdk_macos_surface_get_shadow (surface, &shadow_top, NULL, NULL, NULL);
+ rect = [super constrainFrameRect:frameRect toScreen:screen];
+ if (frameRect.origin.y > rect.origin.y)
+ rect.origin.y = MIN (frameRect.origin.y, rect.origin.y + shadow_top);
+
+ return rect;
+}
+
+-(NSRect)windowWillUseStandardFrame:(NSWindow *)nsWindow
+ defaultFrame:(NSRect)newFrame
+{
+ NSRect screenFrame = [[self screen] visibleFrame];
+ GdkMacosSurface *surface = gdk_surface;
+ gboolean maximized = GDK_SURFACE (surface)->state & GDK_SURFACE_STATE_MAXIMIZED;
+
+ if (!maximized)
+ return screenFrame;
+ else
+ return lastUnmaximizedFrame;
+}
+
+-(BOOL)windowShouldZoom:(NSWindow *)nsWindow
+ toFrame:(NSRect)newFrame
+{
+ GdkMacosSurface *surface = gdk_surface;
+ GdkSurfaceState state = GDK_SURFACE (surface)->state;
+
+ if (state & GDK_SURFACE_STATE_MAXIMIZED)
+ {
+ lastMaximizedFrame = newFrame;
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MAXIMIZED, 0);
+ }
+ else
+ {
+ lastUnmaximizedFrame = [nsWindow frame];
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MAXIMIZED);
+ }
+
+ inMaximizeTransition = YES;
+
+ return YES;
+}
+
+-(void)windowDidEndLiveResize:(NSNotification *)aNotification
+{
+ inMaximizeTransition = NO;
+}
+
+-(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
+{
+ return [[window screen] frame].size;
+}
+
+-(void)windowWillEnterFullScreen:(NSNotification *)aNotification
+{
+ lastUnfullscreenFrame = [self frame];
+}
+
+-(void)windowWillExitFullScreen:(NSNotification *)aNotification
+{
+ [self setFrame:lastUnfullscreenFrame display:YES];
+}
+
+-(void)windowDidExitFullScreen:(NSNotification *)aNotification
+{
+}
+
+-(void)windowDidChangeScreen:(NSNotification *)aNotification
+{
+ _gdk_macos_surface_monitor_changed (gdk_surface);
+}
+
+-(void)setGdkSurface:(GdkMacosSurface *)surface
+{
+ self->gdk_surface = surface;
+}
+
+-(void)setDecorated:(BOOL)decorated
+{
+ NSWindowStyleMask style_mask = [self styleMask];
+
+ [self setHasShadow:decorated];
+
+ if (decorated)
+ style_mask |= NSWindowStyleMaskTitled;
+ else
+ style_mask &= ~NSWindowStyleMaskTitled;
+
+ [self setStyleMask:style_mask];
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+ return self->gdk_surface;
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+ return GDK_MACOS_DISPLAY (GDK_SURFACE (self->gdk_surface)->display);
+}
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h
new file mode 100644
index 0000000000..f4a343021b
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.h
@@ -0,0 +1,68 @@
+/* GdkMacosWindow.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+
+#define GDK_IS_MACOS_WINDOW(obj) ([obj isKindOfClass:[GdkMacosWindow class]])
+
+@interface GdkMacosWindow : NSWindow {
+ GdkMacosSurface *gdk_surface;
+
+ BOOL inMove;
+ BOOL inShowOrHide;
+ BOOL initialPositionKnown;
+
+ /* Manually triggered move/resize (not by the window manager) */
+ BOOL inManualMove;
+ BOOL inManualResize;
+ BOOL inTrackManualResize;
+ NSPoint initialMoveLocation;
+ NSPoint initialResizeLocation;
+ NSRect initialResizeFrame;
+ GdkSurfaceEdge resizeEdge;
+
+ NSRect lastUnmaximizedFrame;
+ NSRect lastMaximizedFrame;
+ NSRect lastUnfullscreenFrame;
+ BOOL inMaximizeTransition;
+}
+
+-(void)beginManualMove;
+-(void)beginManualResize:(GdkSurfaceEdge)edge;
+-(void)hide;
+-(BOOL)isInManualResizeOrMove;
+-(BOOL)isInMove;
+-(GdkMacosDisplay *)gdkDisplay;
+-(GdkMacosSurface *)gdkSurface;
+-(void)setGdkSurface:(GdkMacosSurface *)surface;
+-(void)setStyleMask:(NSWindowStyleMask)styleMask;
+-(void)showAndMakeKey:(BOOL)makeKey;
+-(BOOL)trackManualMove;
+-(BOOL)trackManualResize;
+-(void)setDecorated:(BOOL)decorated;
+
+@end
diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c
new file mode 100644
index 0000000000..2961042724
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.c
@@ -0,0 +1,253 @@
+/* gdkdisplaylinksource.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian Hergert <christian hergert me>
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+#include <mach/mach_time.h>
+
+#include "gdkdisplaylinksource.h"
+
+#include "gdkmacoseventsource-private.h"
+
+static gint64 host_to_frame_clock_time (gint64 host_time);
+
+static gboolean
+gdk_display_link_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ gint64 now;
+
+ now = g_source_get_time (source);
+
+ if (now < impl->presentation_time)
+ *timeout_ = (impl->presentation_time - now) / 1000L;
+ else
+ *timeout_ = -1;
+
+ return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_check (GSource *source)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ gboolean ret = G_SOURCE_CONTINUE;
+
+ impl->needs_dispatch = FALSE;
+
+ if (callback != NULL)
+ ret = callback (user_data);
+
+ return ret;
+}
+
+static void
+gdk_display_link_source_finalize (GSource *source)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+
+ CVDisplayLinkStop (impl->display_link);
+ CVDisplayLinkRelease (impl->display_link);
+}
+
+static GSourceFuncs gdk_display_link_source_funcs = {
+ gdk_display_link_source_prepare,
+ gdk_display_link_source_check,
+ gdk_display_link_source_dispatch,
+ gdk_display_link_source_finalize
+};
+
+void
+gdk_display_link_source_pause (GdkDisplayLinkSource *source)
+{
+ CVDisplayLinkStop (source->display_link);
+}
+
+void
+gdk_display_link_source_unpause (GdkDisplayLinkSource *source)
+{
+ CVDisplayLinkStart (source->display_link);
+}
+
+static CVReturn
+gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link,
+ const CVTimeStamp *inNow,
+ const CVTimeStamp *inOutputTime,
+ CVOptionFlags flagsIn,
+ CVOptionFlags *flagsOut,
+ void *user_data)
+{
+ GdkDisplayLinkSource *impl = user_data;
+ gint64 presentation_time;
+ gboolean needs_wakeup;
+
+ needs_wakeup = !g_atomic_int_get (&impl->needs_dispatch);
+
+ presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
+
+ impl->presentation_time = presentation_time;
+ impl->needs_dispatch = TRUE;
+
+ if (needs_wakeup)
+ {
+ NSEvent *event;
+
+ /* Post a message so we'll break out of the message loop.
+ *
+ * We don't use g_main_context_wakeup() here because that
+ * would result in sending a message to the pipe(2) fd in
+ * the select thread which would then send this message as
+ * well. Lots of extra work.
+ */
+ event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: 0
+ context: nil
+ subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+ data1: 0
+ data2: 0];
+
+ [NSApp postEvent:event atStart:YES];
+ }
+
+ return kCVReturnSuccess;
+}
+
+/**
+ * gdk_display_link_source_new:
+ *
+ * Creates a new #GSource that will activate the dispatch function upon
+ * notification from a CVDisplayLink that a new frame should be drawn.
+ *
+ * Effort is made to keep the transition from the high-priority
+ * CVDisplayLink thread into this GSource lightweight. However, this is
+ * somewhat non-ideal since the best case would be to do the drawing
+ * from the high-priority thread.
+ *
+ * Returns: (transfer full): A newly created #GSource.
+ */
+GSource *
+gdk_display_link_source_new (void)
+{
+ GdkDisplayLinkSource *impl;
+ GSource *source;
+ CVReturn ret;
+ double period;
+
+ source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl);
+ impl = (GdkDisplayLinkSource *)source;
+
+ /*
+ * Create our link based on currently connected displays.
+ * If there are multiple displays, this will be something that tries
+ * to work for all of them. In the future, we may want to explore multiple
+ * links based on the connected displays.
+ */
+ ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link);
+ if (ret != kCVReturnSuccess)
+ {
+ g_warning ("Failed to initialize CVDisplayLink!");
+ return source;
+ }
+
+ /*
+ * Determine our nominal period between frames.
+ */
+ period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link);
+ if (period == 0.0)
+ period = 1.0 / 60.0;
+ impl->refresh_interval = period * 1000000L;
+
+ /*
+ * Wire up our callback to be executed within the high-priority thread.
+ */
+ CVDisplayLinkSetOutputCallback (impl->display_link,
+ gdk_display_link_source_frame_cb,
+ source);
+
+ g_source_set_name (source, "[gdk] quartz frame clock");
+
+ return source;
+}
+
+static gint64
+host_to_frame_clock_time (gint64 host_time)
+{
+ static mach_timebase_info_data_t timebase_info;
+
+ /*
+ * NOTE:
+ *
+ * This code is taken from GLib to match g_get_monotonic_time().
+ */
+ if (G_UNLIKELY (timebase_info.denom == 0))
+ {
+ /* This is a fraction that we must use to scale
+ * mach_absolute_time() by in order to reach nanoseconds.
+ *
+ * We've only ever observed this to be 1/1, but maybe it could be
+ * 1000/1 if mach time is microseconds already, or 1/1000 if
+ * picoseconds. Try to deal nicely with that.
+ */
+ mach_timebase_info (&timebase_info);
+
+ /* We actually want microseconds... */
+ if (timebase_info.numer % 1000 == 0)
+ timebase_info.numer /= 1000;
+ else
+ timebase_info.denom *= 1000;
+
+ /* We want to make the numer 1 to avoid having to multiply... */
+ if (timebase_info.denom % timebase_info.numer == 0)
+ {
+ timebase_info.denom /= timebase_info.numer;
+ timebase_info.numer = 1;
+ }
+ else
+ {
+ /* We could just multiply by timebase_info.numer below, but why
+ * bother for a case that may never actually exist...
+ *
+ * Plus -- performing the multiplication would risk integer
+ * overflow. If we ever actually end up in this situation, we
+ * should more carefully evaluate the correct course of action.
+ */
+ mach_timebase_info (&timebase_info); /* Get a fresh copy for a better message */
+ g_error ("Got weird mach timebase info of %d/%d. Please file a bug against GLib.",
+ timebase_info.numer, timebase_info.denom);
+ }
+ }
+
+ return host_time / timebase_info.denom;
+}
diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h
new file mode 100644
index 0000000000..7493b0c0d4
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.h
@@ -0,0 +1,48 @@
+/* gdkdisplaylinksource.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Christian Hergert <christian hergert me>
+ */
+
+#ifndef GDK_DISPLAY_LINK_SOURCE_H
+#define GDK_DISPLAY_LINK_SOURCE_H
+
+#include <glib.h>
+
+#include <QuartzCore/QuartzCore.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ GSource source;
+
+ CVDisplayLinkRef display_link;
+ gint64 refresh_interval;
+
+ volatile gint64 presentation_time;
+ volatile guint needs_dispatch;
+} GdkDisplayLinkSource;
+
+GSource *gdk_display_link_source_new (void);
+void gdk_display_link_source_pause (GdkDisplayLinkSource *source);
+void gdk_display_link_source_unpause (GdkDisplayLinkSource *source);
+
+G_END_DECLS
+
+#endif /* GDK_DISPLAY_LINK_SOURCE_H */
diff --git a/gdk/macos/gdkmacos.h b/gdk/macos/gdkmacos.h
new file mode 100644
index 0000000000..f7eea95502
--- /dev/null
+++ b/gdk/macos/gdkmacos.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_H__
+#define __GDK_MACOS_H__
+
+#include <gdk/gdk.h>
+
+#include "gdkmacosdisplay.h"
+
+#endif /* __GDK_MACOS_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext-private.h b/gdk/macos/gdkmacoscairocontext-private.h
new file mode 100644
index 0000000000..1afede8591
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__
+#define __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__
+
+#include "gdkcairocontextprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosCairoContext GdkMacosCairoContext;
+typedef struct _GdkMacosCairoContextClass GdkMacosCairoContextClass;
+
+#define GDK_TYPE_MACOS_CAIRO_CONTEXT (_gdk_macos_cairo_context_get_type())
+#define GDK_MACOS_CAIRO_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object),
GDK_TYPE_MACOS_CAIRO_CONTEXT, GdkMacosCairoContext))
+#define GDK_IS_MACOS_CAIRO_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object),
GDK_TYPE_MACOS_CAIRO_CONTEXT))
+
+GType _gdk_macos_cairo_context_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c
new file mode 100644
index 0000000000..21d05dc6fc
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2016 Benjamin Otte
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkconfig.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <cairo-quartz.h>
+
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosCairoContext
+{
+ GdkCairoContext parent_instance;
+
+ cairo_surface_t *window_surface;
+ cairo_surface_t *paint_surface;
+};
+
+struct _GdkMacosCairoContextClass
+{
+ GdkCairoContextClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT)
+
+static cairo_surface_t *
+create_cairo_surface_for_surface (GdkSurface *surface)
+{
+ cairo_surface_t *cairo_surface;
+ GdkDisplay *display;
+ int scale;
+ int width;
+ int height;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ display = gdk_surface_get_display (surface);
+ scale = gdk_surface_get_scale_factor (surface);
+ width = scale * gdk_surface_get_width (surface);
+ height = scale * gdk_surface_get_height (surface);
+
+ cairo_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+ if (cairo_surface != NULL)
+ cairo_surface_set_device_scale (cairo_surface, scale, scale);
+
+ return cairo_surface;
+}
+
+static cairo_t *
+_gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)cairo_context;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+ return cairo_create (self->paint_surface);
+}
+
+static void
+_gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
+ cairo_region_t *region)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+ GdkRectangle clip_box;
+ GdkSurface *surface;
+ double sx = 1.0;
+ double sy = 1.0;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+ g_assert (self->paint_surface == NULL);
+
+ surface = gdk_draw_context_get_surface (draw_context);
+
+ if (self->window_surface == NULL)
+ self->window_surface = create_cairo_surface_for_surface (surface);
+
+ cairo_region_get_extents (region, &clip_box);
+ self->paint_surface = gdk_surface_create_similar_surface (surface,
+ cairo_surface_get_content (self->window_surface),
+ MAX (clip_box.width, 1),
+ MAX (clip_box.height, 1));
+ sx = sy = 1;
+ cairo_surface_get_device_scale (self->paint_surface, &sx, &sy);
+ cairo_surface_set_device_offset (self->paint_surface, -clip_box.x*sx, -clip_box.y*sy);
+}
+
+static void
+_gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context,
+ cairo_region_t *painted)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+ GdkSurface *surface;
+ cairo_t *cr;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+ g_assert (self->paint_surface != NULL);
+ g_assert (self->window_surface != NULL);
+
+ cr = cairo_create (self->window_surface);
+
+ cairo_set_source_surface (cr, self->paint_surface, 0, 0);
+ gdk_cairo_region (cr, painted);
+ cairo_clip (cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ cairo_surface_flush (self->window_surface);
+
+ surface = gdk_draw_context_get_surface (draw_context);
+ _gdk_macos_surface_damage_cairo (GDK_MACOS_SURFACE (surface),
+ self->window_surface,
+ painted);
+ g_clear_pointer (&self->paint_surface, cairo_surface_destroy);
+}
+
+static void
+_gdk_macos_cairo_context_surface_resized (GdkDrawContext *draw_context)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+ g_clear_pointer (&self->window_surface, cairo_surface_destroy);
+ g_clear_pointer (&self->paint_surface, cairo_surface_destroy);
+}
+
+static void
+_gdk_macos_cairo_context_class_init (GdkMacosCairoContextClass *klass)
+{
+ GdkCairoContextClass *cairo_context_class = GDK_CAIRO_CONTEXT_CLASS (klass);
+ GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+ draw_context_class->begin_frame = _gdk_macos_cairo_context_begin_frame;
+ draw_context_class->end_frame = _gdk_macos_cairo_context_end_frame;
+ draw_context_class->surface_resized = _gdk_macos_cairo_context_surface_resized;
+
+ cairo_context_class->cairo_create = _gdk_macos_cairo_context_cairo_create;
+}
+
+static void
+_gdk_macos_cairo_context_init (GdkMacosCairoContext *self)
+{
+}
diff --git a/gdk/macos/gdkmacoscursor-private.h b/gdk/macos/gdkmacoscursor-private.h
new file mode 100644
index 0000000000..b3e5bac70d
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor-private.h
@@ -0,0 +1,32 @@
+/* gdkmacoscursor-private.h
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_MACOS_CURSOR_PRIVATE_H__
+#define __GDK_MACOS_CURSOR_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+NSCursor *_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CURSOR_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscursor.c b/gdk/macos/gdkmacoscursor.c
new file mode 100644
index 0000000000..884fcd9abf
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor.c
@@ -0,0 +1,135 @@
+/* gdkcursor-macos.c
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gdkmacoscursor-private.h"
+
+/* OS X only exports a number of cursor types in its public NSCursor interface.
+ * By overriding the private _coreCursorType method, we can tell OS X to load
+ * one of its internal cursors instead (since cursor images are loaded on demand
+ * instead of in advance). WebKit does this too.
+ */
+
+@interface gdkCoreCursor : NSCursor {
+@private
+ int type;
+ BOOL override;
+}
+@end
+
+@implementation gdkCoreCursor
+
+- (long long)_coreCursorType
+{
+ if (self->override)
+ return self->type;
+ return [super _coreCursorType];
+}
+
+#define CUSTOM_CURSOR_CTOR(name, id) \
+ + (gdkCoreCursor *)name \
+ { \
+ gdkCoreCursor *obj; \
+ obj = [self new]; \
+ if (obj) { \
+ obj->override = YES; \
+ obj->type = id; \
+ } \
+ return obj; \
+ }
+CUSTOM_CURSOR_CTOR(gdkHelpCursor, 40)
+CUSTOM_CURSOR_CTOR(gdkProgressCursor, 4)
+/* TODO OS X doesn't seem to have a way to get this. There is an undocumented
+ * method +[NSCursor _waitCursor], but it doesn't actually return this cursor,
+ * but rather some odd low-quality non-animating version of this cursor. Use
+ * the progress cursor instead for now.
+ */
+CUSTOM_CURSOR_CTOR(gdkWaitCursor, 4)
+CUSTOM_CURSOR_CTOR(gdkAliasCursor, 2)
+CUSTOM_CURSOR_CTOR(gdkMoveCursor, 39)
+/* TODO OS X doesn't seem to provide one; copy the move cursor for now
+ * since it looks similar to what we want. */
+CUSTOM_CURSOR_CTOR(gdkAllScrollCursor, 39)
+CUSTOM_CURSOR_CTOR(gdkNEResizeCursor, 29)
+CUSTOM_CURSOR_CTOR(gdkNWResizeCursor, 33)
+CUSTOM_CURSOR_CTOR(gdkSEResizeCursor, 35)
+CUSTOM_CURSOR_CTOR(gdkSWResizeCursor, 37)
+CUSTOM_CURSOR_CTOR(gdkEWResizeCursor, 28)
+CUSTOM_CURSOR_CTOR(gdkNSResizeCursor, 32)
+CUSTOM_CURSOR_CTOR(gdkNESWResizeCursor, 30)
+CUSTOM_CURSOR_CTOR(gdkNWSEResizeCursor, 34)
+CUSTOM_CURSOR_CTOR(gdkZoomInCursor, 42)
+CUSTOM_CURSOR_CTOR(gdkZoomOutCursor, 43)
+#undef CUSTOM_CURSOR_CTOR
+
+@end
+
+struct CursorsByName {
+ const gchar *name;
+ NSString *selector;
+};
+
+static const struct CursorsByName cursors_by_name[] = {
+ /* Link & Status */
+ { "context-menu", @"contextualMenuCursor" },
+ { "help", @"gdkHelpCursor" },
+ { "pointer", @"pointingHandCursor" },
+ { "progress", @"gdkProgressCursor" },
+ { "wait", @"gdkWaitCursor" },
+ /* Selection */
+ { "cell", @"crosshairCursor" },
+ { "crosshair", @"crosshairCursor" },
+ { "text", @"IBeamCursor" },
+ { "vertical-text", @"IBeamCursorForVerticalLayout" },
+ /* Drag & Drop */
+ { "alias", @"gdkAliasCursor" },
+ { "copy", @"dragCopyCursor" },
+ { "move", @"gdkMoveCursor" },
+ { "no-drop", @"operationNotAllowedCursor" },
+ { "not-allowed", @"operationNotAllowedCursor" },
+ { "grab", @"openHandCursor" },
+ { "grabbing", @"closedHandCursor" },
+ /* Resize & Scrolling */
+ { "all-scroll", @"gdkAllScrollCursor" },
+ { "col-resize", @"resizeLeftRightCursor" },
+ { "row-resize", @"resizeUpDownCursor" },
+ { "n-resize", @"resizeUpCursor" },
+ { "e-resize", @"resizeRightCursor" },
+ { "s-resize", @"resizeDownCursor" },
+ { "w-resize", @"resizeLeftCursor" },
+ { "ne-resize", @"gdkNEResizeCursor" },
+ { "nw-resize", @"gdkNWResizeCursor" },
+ { "se-resize", @"gdkSEResizeCursor" },
+ { "sw-resize", @"gdkSWResizeCursor" },
+ { "ew-resize", @"gdkEWResizeCursor" },
+ { "ns-resize", @"gdkNSResizeCursor" },
+ { "nesw-resize", @"gdkNESWResizeCursor" },
+ { "nwse-resize", @"gdkNWSEResizeCursor" },
+ /* Zoom */
+ { "zoom-in", @"gdkZoomInCursor" },
+ { "zoom-out", @"gdkZoomOutCursor" },
+ { NULL, NULL },
+};
+
+NSCursor *
+_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor)
+{
+ return NULL;
+}
diff --git a/gdk/macos/gdkmacosdevice.c b/gdk/macos/gdkmacosdevice.c
new file mode 100644
index 0000000000..9406bf9245
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2009 Carlos Garnacho <carlosg gnome org>
+ * Copyright 2010 Kristian Rietveld <kris gtk org>
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdk.h>
+
+#include "gdkdeviceprivate.h"
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacoscursor-private.h"
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosDevice
+{
+ GdkDevice parent_instance;
+};
+
+struct _GdkMacosDeviceClass
+{
+ GdkDeviceClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosDevice, gdk_macos_device, GDK_TYPE_DEVICE)
+
+static void
+gdk_macos_device_set_surface_cursor (GdkDevice *device,
+ GdkSurface *surface,
+ GdkCursor *cursor)
+{
+ NSCursor *nscursor;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (!cursor || GDK_IS_CURSOR (cursor));
+
+ nscursor = _gdk_macos_cursor_get_ns_cursor (cursor);
+
+ if (nscursor != NULL)
+ {
+ [nscursor set];
+ [nscursor release];
+ }
+}
+
+static GdkSurface *
+gdk_macos_device_surface_at_position (GdkDevice *device,
+ gdouble *win_x,
+ gdouble *win_y,
+ GdkModifierType *state,
+ gboolean get_toplevel)
+{
+ GdkMacosDisplay *display;
+ GdkMacosSurface *surface;
+ NSPoint point;
+ gint x;
+ gint y;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (win_x != NULL);
+ g_assert (win_y != NULL);
+
+ point = [NSEvent mouseLocation];
+ display = GDK_MACOS_DISPLAY (gdk_device_get_display (device));
+
+ if (state != NULL)
+ *state = (_gdk_macos_display_get_current_keyboard_modifiers (display) |
+ _gdk_macos_display_get_current_mouse_modifiers (display));
+
+ surface = _gdk_macos_display_get_surface_at_display_coords (display, point.x, point.y, &x, &y);
+
+ *win_x = x;
+ *win_y = y;
+
+ return GDK_SURFACE (surface);
+}
+
+static GdkGrabStatus
+gdk_macos_device_grab (GdkDevice *device,
+ GdkSurface *window,
+ gboolean owner_events,
+ GdkEventMask event_mask,
+ GdkSurface *confine_to,
+ GdkCursor *cursor,
+ guint32 time_)
+{
+ /* Should remain empty */
+ return GDK_GRAB_SUCCESS;
+}
+
+static void
+gdk_macos_device_ungrab (GdkDevice *device,
+ guint32 time_)
+{
+ GdkMacosDevice *self = (GdkMacosDevice *)device;
+ GdkDeviceGrabInfo *grab;
+ GdkDisplay *display;
+
+ g_assert (GDK_IS_MACOS_DEVICE (self));
+
+ display = gdk_device_get_display (device);
+ grab = _gdk_display_get_last_device_grab (display, device);
+
+ if (grab != NULL)
+ grab->serial_end = 0;
+
+ _gdk_display_device_grab_update (display, device, NULL, 0);
+}
+
+static gboolean
+gdk_macos_device_get_history (GdkDevice *device,
+ GdkSurface *surface,
+ guint32 start,
+ guint32 stop,
+ GdkTimeCoord ***events,
+ gint *n_events)
+{
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ return FALSE;
+}
+
+static void
+gdk_macos_device_get_state (GdkDevice *device,
+ GdkSurface *surface,
+ gdouble *axes,
+ GdkModifierType *mask)
+{
+ gdouble x_pos, y_pos;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ gdk_surface_get_device_position (surface, device, &x_pos, &y_pos, mask);
+
+ if (axes != NULL)
+ {
+ axes[0] = x_pos;
+ axes[1] = y_pos;
+ }
+}
+
+static void
+gdk_macos_device_query_state (GdkDevice *device,
+ GdkSurface *surface,
+ GdkSurface **child_surface,
+ gdouble *win_x,
+ gdouble *win_y,
+ GdkModifierType *mask)
+{
+ GdkDisplay *display;
+ NSPoint point;
+ gint x_tmp;
+ gint y_tmp;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ if (child_surface)
+ *child_surface = surface;
+
+ display = gdk_device_get_display (device);
+ point = [NSEvent mouseLocation];
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+ point.x, point.y,
+ &x_tmp, &y_tmp);
+
+ if (win_x)
+ *win_x = x_tmp;
+
+ if (win_y)
+ *win_y = y_tmp;
+
+ if (mask)
+ *mask = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display)) |
+ _gdk_macos_display_get_current_mouse_modifiers (GDK_MACOS_DISPLAY (display));
+}
+
+static void
+gdk_macos_device_class_init (GdkMacosDeviceClass *klass)
+{
+ GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
+
+ device_class->get_history = gdk_macos_device_get_history;
+ device_class->get_state = gdk_macos_device_get_state;
+ device_class->grab = gdk_macos_device_grab;
+ device_class->query_state = gdk_macos_device_query_state;
+ device_class->set_surface_cursor = gdk_macos_device_set_surface_cursor;
+ device_class->surface_at_position = gdk_macos_device_surface_at_position;
+ device_class->ungrab = gdk_macos_device_ungrab;
+}
+
+static void
+gdk_macos_device_init (GdkMacosDevice *self)
+{
+ _gdk_device_add_axis (GDK_DEVICE (self), NULL, GDK_AXIS_X, 0, 0, 1);
+ _gdk_device_add_axis (GDK_DEVICE (self), NULL, GDK_AXIS_Y, 0, 0, 1);
+}
diff --git a/gdk/macos/gdkmacosdevice.h b/gdk/macos/gdkmacosdevice.h
new file mode 100644
index 0000000000..c81fde5d1a
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DEVICE_H__
+#define __GDK_MACOS_DEVICE_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDevice GdkMacosDevice;
+typedef struct _GdkMacosDeviceClass GdkMacosDeviceClass;
+
+#define GDK_TYPE_MACOS_DEVICE (gdk_macos_device_get_type())
+#define GDK_MACOS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DEVICE,
GdkMacosDevice))
+#define GDK_IS_MACOS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DEVICE))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_device_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DEVICE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h
new file mode 100644
index 0000000000..5d8bb9bc3c
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DISPLAY_PRIVATE_H__
+#define __GDK_MACOS_DISPLAY_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacoskeymap.h"
+#include "gdkmacossurface.h"
+
+G_BEGIN_DECLS
+
+struct _GdkMacosDisplay
+{
+ GdkDisplay parent_instance;
+
+ char *name;
+ GdkMacosKeymap *keymap;
+
+ /* An array of GdkMacosMonitor. The first instance is always the primary
+ * monitor (which contains the 0,0 coordinate.
+ */
+ GPtrArray *monitors;
+
+ /* A queue of surfaces that have been made "main" so that we can update the
+ * main status to the next surface when a window has lost main status (such
+ * as when destroyed). This uses the GdkMacosSurface main link.
+ */
+ GQueue main_surfaces;
+
+ /* A queue of surfaces sorted by their front-to-back ordering on the screen.
+ * This is updated occasionally when we know that the data we have cached
+ * has been invalidated. This uses the GdkMacosSurface.sorted link.
+ */
+ GQueue sorted_surfaces;
+
+ /* Our CVDisplayLink based GSource which we use to freeze/thaw the
+ * GdkFrameClock for the surface.
+ */
+ GSource *frame_source;
+
+ /* A queue of surfaces which we know are awaiting frames to be drawn. This
+ * uses the GdkMacosSurface.frame link.
+ */
+ GQueue awaiting_frames;
+
+ /* The surface that is receiving keyboard events */
+ GdkMacosSurface *keyboard_surface;
+
+ int width;
+ int height;
+ int min_x;
+ int min_y;
+};
+
+struct _GdkMacosDisplayClass
+{
+ GdkDisplayClass parent_class;
+};
+
+
+GdkDisplay *_gdk_macos_display_open (const gchar *display_name);
+int _gdk_macos_display_get_fd (GdkMacosDisplay *self);
+void _gdk_macos_display_queue_events (GdkMacosDisplay *self);
+void _gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y);
+void _gdk_macos_display_from_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y);
+NSScreen *_gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y);
+GdkEvent *_gdk_macos_display_translate (GdkMacosDisplay *self,
+ NSEvent *event);
+void _gdk_macos_display_break_all_grabs (GdkMacosDisplay *self,
+ guint32 time);
+GdkModifierType _gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self);
+GdkModifierType _gdk_macos_display_get_current_mouse_modifiers (GdkMacosDisplay *self);
+GdkMacosSurface *_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self,
+ double x,
+ double y,
+ int *surface_x,
+ int *surface_y);
+void _gdk_macos_display_stacking_changed (GdkMacosDisplay *self);
+void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self);
+void _gdk_macos_display_surface_removed (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_add_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_synthesize_motion (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+NSWindow *_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self,
+ int *x,
+ int *y);
+gboolean _gdk_macos_display_get_setting (GdkMacosDisplay *self,
+ const gchar *setting,
+ GValue *value);
+void _gdk_macos_display_reload_settings (GdkMacosDisplay *self);
+void _gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_became_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_became_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-settings.c b/gdk/macos/gdkmacosdisplay-settings.c
new file mode 100644
index 0000000000..558e0b81e0
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-settings.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright © 1998-2002 Tor Lillqvist
+ * Copyright © 2005-2008 Imendio AB
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosutils-private.h"
+
+typedef struct
+{
+ const char *font_name;
+ int xft_dpi;
+ int double_click_time;
+ int cursor_blink_timeout;
+ guint enable_animations : 1;
+ guint shell_shows_desktop : 1;
+ guint shell_shows_menubar : 1;
+ guint primary_button_warps_slider : 1;
+} GdkMacosSettings;
+
+static GdkMacosSettings current_settings;
+static gboolean current_settings_initialized;
+
+static void
+_gdk_macos_settings_load (GdkMacosSettings *settings)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *name;
+ NSInteger ival;
+ float fval;
+ char *str;
+ int pt_size;
+
+ g_assert (settings != NULL);
+
+ settings->shell_shows_desktop = TRUE;
+ settings->shell_shows_menubar = TRUE;
+ settings->enable_animations = TRUE;
+ settings->xft_dpi = 72 * 1024;
+
+ ival = [defaults integerForKey:@"NSTextInsertionPointBlinkPeriod"];
+ if (ival > 0)
+ settings->cursor_blink_timeout = ival;
+ else
+ settings->cursor_blink_timeout = 1000;
+
+ settings->primary_button_warps_slider =
+ [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"] == YES;
+
+ fval = [defaults floatForKey:@"com.apple.mouse.doubleClickThreshold"];
+ if (fval == 0.0)
+ fval = 0.5;
+ settings->double_click_time = fval * 1000;
+
+ name = [[NSFont systemFontOfSize:0] familyName];
+ pt_size = (gint)[[NSFont userFontOfSize:0] pointSize];
+ /* Let's try to use the "views" font size (12pt) by default. This is
+ * used for lists/text/other "content" which is the largest parts of
+ * apps, using the "regular control" size (13pt) looks a bit out of
+ * place. We might have to tweak this.
+ *
+ * The size has to be hardcoded as there doesn't seem to be a way to
+ * get the views font size programmatically.
+ */
+ str = g_strdup_printf ("%s %d", [name UTF8String], pt_size);
+ settings->font_name = g_intern_string (str);
+ g_free (str);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+gboolean
+_gdk_macos_display_get_setting (GdkMacosDisplay *self,
+ const gchar *setting,
+ GValue *value)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), FALSE);
+ g_return_val_if_fail (setting != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ if (!current_settings_initialized)
+ {
+ _gdk_macos_settings_load (¤t_settings);
+ current_settings_initialized = TRUE;
+ }
+
+ if (FALSE) {}
+ else if (strcmp (setting, "gtk-enable-animations") == 0)
+ {
+ g_value_set_boolean (value, current_settings.enable_animations);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-xft-dpi") == 0)
+ {
+ g_value_set_int (value, current_settings.xft_dpi);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-cursor-blink-timeout") == 0)
+ {
+ g_value_set_int (value, current_settings.cursor_blink_timeout);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-double-click-time") == 0)
+ {
+ g_value_set_int (value, current_settings.double_click_time);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-font-name") == 0)
+ {
+ g_value_set_static_string (value, current_settings.font_name);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-primary-button-warps-slider") == 0)
+ {
+ g_value_set_boolean (value, current_settings.primary_button_warps_slider);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-shell-shows-desktop") == 0)
+ {
+ g_value_set_boolean (value, current_settings.shell_shows_desktop);
+ ret = TRUE;
+ }
+ else if (strcmp (setting, "gtk-shell-shows-menubar") == 0)
+ {
+ g_value_set_boolean (value, current_settings.shell_shows_menubar);
+ ret = TRUE;
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return ret;
+}
+
+void
+_gdk_macos_display_reload_settings (GdkMacosDisplay *self)
+{
+ GdkMacosSettings old_settings;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ old_settings = current_settings;
+ _gdk_macos_settings_load (¤t_settings);
+ current_settings_initialized = TRUE;
+
+ if (old_settings.xft_dpi != current_settings.xft_dpi)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-xft-dpi");
+
+ if (old_settings.double_click_time != current_settings.double_click_time)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-double-click-time");
+
+ if (old_settings.enable_animations != current_settings.enable_animations)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-enable-animations");
+
+ if (old_settings.font_name != current_settings.font_name)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-font-name");
+
+ if (old_settings.primary_button_warps_slider != current_settings.primary_button_warps_slider)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-primary-button-warps-slider");
+
+ if (old_settings.shell_shows_menubar != current_settings.shell_shows_menubar)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-shell-shows-menubar");
+
+ if (old_settings.shell_shows_desktop != current_settings.shell_shows_desktop)
+ gdk_display_setting_changed (GDK_DISPLAY (self), "gtk-shell-shows-desktop");
+}
diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c
new file mode 100644
index 0000000000..73bbc485b8
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -0,0 +1,870 @@
+/*
+ * Copyright © 2005 Imendio AB
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosWindow.h"
+#import "GdkMacosBaseView.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacossurface-private.h"
+
+#define GDK_MOD2_MASK (1 << 4)
+#define GRIP_WIDTH 15
+#define GRIP_HEIGHT 15
+#define GDK_LION_RESIZE 5
+
+static gboolean
+test_resize (NSEvent *event,
+ GdkMacosSurface *surface,
+ int x,
+ int y)
+{
+ NSWindow *window;
+
+ g_assert (event != NULL);
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ window = _gdk_macos_surface_get_native (surface);
+
+ /* Resizing from the resize indicator only begins if an NSLeftMouseButton
+ * event is received in the resizing area.
+ */
+ if ([event type] == NSEventTypeLeftMouseDown &&
+ [window showsResizeIndicator])
+ {
+ NSRect frame;
+
+ /* If the resize indicator is visible and the event is in the lower
+ * right 15x15 corner, we leave these events to Cocoa as to be
+ * handled as resize events. Applications may have widgets in this
+ * area. These will most likely be larger than 15x15 and for scroll
+ * bars there are also other means to move the scroll bar. Since
+ * the resize indicator is the only way of resizing windows on Mac
+ * OS, it is too important to not make functional.
+ */
+ frame = [[window contentView] bounds];
+ if (x > frame.size.width - GRIP_WIDTH &&
+ x < frame.size.width &&
+ y > frame.size.height - GRIP_HEIGHT &&
+ y < frame.size.height)
+ return TRUE;
+ }
+
+ /* If we're on Lion and within 5 pixels of an edge, then assume that the
+ * user wants to resize, and return NULL to let Quartz get on with it.
+ * We check the selector isRestorable to see if we're on 10.7. This
+ * extra check is in case the user starts dragging before GDK recognizes
+ * the grab.
+ *
+ * We perform this check for a button press of all buttons, because we
+ * do receive, for instance, a right mouse down event for a GDK surface
+ * for x-coordinate range [-3, 0], but we do not want to forward this
+ * into GDK. Forwarding such events into GDK will confuse the pointer
+ * window finding code, because there are no GdkSurfaces present in
+ * the range [-3, 0].
+ */
+ if (([event type] == NSEventTypeLeftMouseDown ||
+ [event type] == NSEventTypeRightMouseDown ||
+ [event type] == NSEventTypeOtherMouseDown))
+ {
+ if (x < GDK_LION_RESIZE ||
+ x > GDK_SURFACE (surface)->width - GDK_LION_RESIZE ||
+ y > GDK_SURFACE (surface)->height - GDK_LION_RESIZE)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static guint32
+get_time_from_ns_event (NSEvent *event)
+{
+ double time = [event timestamp];
+ /* cast via double->uint64 conversion to make sure that it is
+ * wrapped on 32-bit machines when it overflows
+ */
+ return (guint32) (guint64) (time * 1000.0);
+}
+
+static int
+get_mouse_button_from_ns_event (NSEvent *event)
+{
+ NSInteger button = [event buttonNumber];
+
+ switch (button)
+ {
+ case 0:
+ return 1;
+ case 1:
+ return 3;
+ case 2:
+ return 2;
+ default:
+ return button + 1;
+ }
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_buttons (NSUInteger nsbuttons)
+{
+ GdkModifierType modifiers = 0;
+
+ if (nsbuttons & (1 << 0))
+ modifiers |= GDK_BUTTON1_MASK;
+ if (nsbuttons & (1 << 1))
+ modifiers |= GDK_BUTTON3_MASK;
+ if (nsbuttons & (1 << 2))
+ modifiers |= GDK_BUTTON2_MASK;
+ if (nsbuttons & (1 << 3))
+ modifiers |= GDK_BUTTON4_MASK;
+ if (nsbuttons & (1 << 4))
+ modifiers |= GDK_BUTTON5_MASK;
+
+ return modifiers;
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_event (NSEvent *event)
+{
+ GdkModifierType state = 0;
+ int button;
+
+ /* This maps buttons 1 to 5 to GDK_BUTTON[1-5]_MASK */
+ button = get_mouse_button_from_ns_event (event);
+ if (button >= 1 && button <= 5)
+ state = (1 << (button + 7));
+
+ return state;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_flags (NSUInteger nsflags)
+{
+ GdkModifierType modifiers = 0;
+
+ if (nsflags & NSEventModifierFlagCapsLock)
+ modifiers |= GDK_LOCK_MASK;
+ if (nsflags & NSEventModifierFlagShift)
+ modifiers |= GDK_SHIFT_MASK;
+ if (nsflags & NSEventModifierFlagControl)
+ modifiers |= GDK_CONTROL_MASK;
+ if (nsflags & NSEventModifierFlagOption)
+ modifiers |= GDK_ALT_MASK;
+ if (nsflags & NSEventModifierFlagCommand)
+ modifiers |= GDK_MOD2_MASK;
+
+ return modifiers;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_event (NSEvent *nsevent)
+{
+ return get_keyboard_modifiers_from_ns_flags ([nsevent modifierFlags]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_mouse_modifiers (GdkMacosDisplay *self)
+{
+ return get_mouse_button_modifiers_from_ns_buttons ([NSEvent pressedMouseButtons]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self)
+{
+ return get_keyboard_modifiers_from_ns_flags ([NSEvent modifierFlags]);
+}
+
+static GdkEvent *
+fill_button_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkSeat *seat;
+ GdkEventType type;
+ GdkModifierType state;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ /* Ignore button events outside the window coords */
+ if (x < 0 || x > GDK_SURFACE (surface)->width ||
+ y < 0 || y > GDK_SURFACE (surface)->height)
+ return NULL;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ type = GDK_BUTTON_PRESS;
+ state &= ~get_mouse_button_modifiers_from_ns_event (nsevent);
+ break;
+
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseUp:
+ type = GDK_BUTTON_RELEASE;
+ state |= get_mouse_button_modifiers_from_ns_event (nsevent);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return gdk_button_event_new (type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ get_mouse_button_from_ns_event (nsevent),
+ x,
+ y,
+ NULL);
+}
+
+static GdkEvent *
+synthesize_crossing_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkEventType event_type;
+ GdkModifierType state;
+ GdkSeat *seat;
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeMouseEntered:
+ event_type = GDK_ENTER_NOTIFY;
+ break;
+
+ case NSEventTypeMouseExited:
+ event_type = GDK_LEAVE_NOTIFY;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+ return gdk_crossing_event_new (event_type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ x,
+ y,
+ GDK_CROSSING_NORMAL,
+ GDK_NOTIFY_NONLINEAR);
+}
+
+static inline guint
+get_group_from_ns_event (NSEvent *nsevent)
+{
+ return ([nsevent modifierFlags] & NSEventModifierFlagOption) ? 1 : 0;
+}
+
+static void
+add_virtual_modifiers (GdkModifierType *state)
+{
+ if (*state & GDK_MOD2_MASK)
+ *state |= GDK_META_MASK;
+}
+
+static GdkEvent *
+fill_key_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ GdkEventType type)
+{
+ GdkTranslatedKey translated = {0};
+ GdkTranslatedKey no_lock = {0};
+ GdkModifierType consumed;
+ GdkModifierType state;
+ GdkKeymap *keymap;
+ gboolean is_modifier;
+ GdkSeat *seat;
+ guint keycode;
+ guint keyval;
+ guint group;
+ int layout;
+ int level;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ keymap = gdk_display_get_keymap (GDK_DISPLAY (display));
+ keycode = [nsevent keyCode];
+ keyval = GDK_KEY_VoidSymbol;
+ state = get_keyboard_modifiers_from_ns_event (nsevent);
+ group = get_group_from_ns_event (nsevent);
+ is_modifier = _gdk_macos_keymap_is_modifier (keycode);
+
+ gdk_keymap_translate_keyboard_state (keymap, keycode, state, group,
+ &keyval, &layout, &level, &consumed);
+
+ /* If the key press is a modifier, the state should include the mask for
+ * that modifier but only for releases, not presses. This matches the
+ * X11 backend behavior.
+ */
+ if (is_modifier)
+ {
+ guint mask = 0;
+
+ switch (keyval)
+ {
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Meta_L:
+ mask = GDK_MOD2_MASK;
+ break;
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Shift_L:
+ mask = GDK_SHIFT_MASK;
+ break;
+ case GDK_KEY_Caps_Lock:
+ mask = GDK_LOCK_MASK;
+ break;
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Alt_L:
+ mask = GDK_ALT_MASK;
+ break;
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Control_L:
+ mask = GDK_CONTROL_MASK;
+ break;
+ default:
+ mask = 0;
+ }
+
+ if (type == GDK_KEY_PRESS)
+ state &= ~mask;
+ else if (type == GDK_KEY_RELEASE)
+ state |= mask;
+ }
+
+ state |= _gdk_macos_display_get_current_mouse_modifiers (display);
+ add_virtual_modifiers (&state);
+
+ translated.keyval = keyval;
+ translated.consumed = consumed;
+ translated.layout = layout;
+ translated.level = level;
+
+ if (state & GDK_LOCK_MASK)
+ {
+ gdk_keymap_translate_keyboard_state (keymap,
+ keycode,
+ state & ~GDK_LOCK_MASK,
+ group,
+ &keyval,
+ &layout,
+ &level,
+ &consumed);
+
+ no_lock.keyval = keycode;
+ no_lock.consumed = consumed;
+ no_lock.layout = layout;
+ no_lock.level = level;
+ }
+ else
+ {
+ no_lock = translated;
+ }
+
+ return gdk_key_event_new (type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_keyboard (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ [nsevent keyCode],
+ state,
+ is_modifier,
+ &translated,
+ &no_lock);
+}
+
+static GdkEvent *
+fill_pinch_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ static double last_scale = 1.0;
+ static enum {
+ FP_STATE_IDLE,
+ FP_STATE_UPDATE
+ } last_state = FP_STATE_IDLE;
+ GdkSeat *seat;
+ GdkTouchpadGesturePhase phase;
+ gdouble angle_delta = 0.0;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ /* fill_pinch_event handles the conversion from the two OSX gesture events
+ * NSEventTypeMagnfiy and NSEventTypeRotate to the GDK_TOUCHPAD_PINCH event.
+ * The normal behavior of the OSX events is that they produce as sequence of
+ * 1 x NSEventPhaseBegan,
+ * n x NSEventPhaseChanged,
+ * 1 x NSEventPhaseEnded
+ * This can happen for both the Magnify and the Rotate events independently.
+ * As both events are summarized in one GDK_TOUCHPAD_PINCH event sequence, a
+ * little state machine handles the case of two NSEventPhaseBegan events in
+ * a sequence, e.g. Magnify(Began), Magnify(Changed)..., Rotate(Began)...
+ * such that PINCH(STARTED), PINCH(UPDATE).... will not show a second
+ * PINCH(STARTED) event.
+ */
+
+ switch ((int)[nsevent phase])
+ {
+ case NSEventPhaseBegan:
+ switch (last_state)
+ {
+ case FP_STATE_IDLE:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
+ last_state = FP_STATE_UPDATE;
+ last_scale = 1.0;
+ break;
+ case FP_STATE_UPDATE:
+ /* We have already received a PhaseBegan event but no PhaseEnded
+ event. This can happen, e.g. Magnify(Began), Magnify(Change)...
+ Rotate(Began), Rotate (Change),...., Magnify(End) Rotate(End)
+ */
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+ }
+ break;
+
+ case NSEventPhaseChanged:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+
+ case NSEventPhaseEnded:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
+ switch (last_state)
+ {
+ case FP_STATE_IDLE:
+ /* We are idle but have received a second PhaseEnded event.
+ This can happen because we have Magnify and Rotate OSX
+ event sequences. We just send a second end GDK_PHASE_END.
+ */
+ break;
+ case FP_STATE_UPDATE:
+ last_state = FP_STATE_IDLE;
+ break;
+ }
+ break;
+
+ case NSEventPhaseCancelled:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+ last_state = FP_STATE_IDLE;
+ break;
+
+ case NSEventPhaseMayBegin:
+ case NSEventPhaseStationary:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeMagnify:
+ last_scale *= [nsevent magnification] + 1.0;
+ angle_delta = 0.0;
+ break;
+
+ case NSEventTypeRotate:
+ angle_delta = - [nsevent rotation] * G_PI / 180.0;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+ return gdk_touchpad_event_new_pinch (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ get_keyboard_modifiers_from_ns_event (nsevent),
+ phase,
+ x,
+ y,
+ 2,
+ 0.0,
+ 0.0,
+ last_scale,
+ angle_delta);
+
+}
+
+static GdkEvent *
+fill_motion_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkSeat *seat;
+ GdkModifierType state;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+
+ return gdk_motion_event_new (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ x,
+ y,
+ NULL);
+}
+
+static GdkEvent *
+fill_scroll_event (GdkMacosDisplay *self,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkScrollDirection direction = 0;
+ GdkModifierType state;
+ GdkDevice *pointer;
+ GdkEvent *ret = NULL;
+ GdkSeat *seat;
+ gdouble dx;
+ gdouble dy;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ pointer = gdk_seat_get_pointer (seat);
+ state = _gdk_macos_display_get_current_mouse_modifiers (self) |
+ _gdk_macos_display_get_current_keyboard_modifiers (self);
+
+ dx = [nsevent deltaX];
+ dy = [nsevent deltaY];
+
+ if ([nsevent hasPreciseScrollingDeltas])
+ {
+ gdouble sx;
+ gdouble sy;
+
+ /*
+ * TODO: We probably need another event type for the
+ * high precision scroll events since sx and dy
+ * are in a unit we don't quite support. For now,
+ * to slow it down multiply by .1.
+ */
+
+ sx = [nsevent scrollingDeltaX] * .1;
+ sy = [nsevent scrollingDeltaY] * .1;
+
+ if (sx != 0.0 || dx != 0.0)
+ ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ -sx,
+ -sy,
+ FALSE);
+
+ /* Fall through for scroll emulation */
+ }
+
+ if (dy != 0.0)
+ {
+ if (dy < 0.0)
+ direction = GDK_SCROLL_DOWN;
+ else
+ direction = GDK_SCROLL_UP;
+
+ dy = fabs (dy);
+ dx = 0.0;
+ }
+ else if (dx != 0.0)
+ {
+ if (dx < 0.0)
+ direction = GDK_SCROLL_RIGHT;
+ else
+ direction = GDK_SCROLL_LEFT;
+
+ dx = fabs (dx);
+ dy = 0.0;
+ }
+
+ if (dx != 0.0 || dy != 0.0)
+ {
+ if ([nsevent hasPreciseScrollingDeltas])
+ {
+ GdkEvent *emulated;
+
+ emulated = gdk_scroll_event_new_discrete (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ direction,
+ TRUE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), emulated);
+ }
+ else
+ {
+ g_assert (ret == NULL);
+
+ ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ dx,
+ dy,
+ FALSE);
+ }
+ }
+
+ return g_steal_pointer (&ret);
+}
+
+GdkEvent *
+_gdk_macos_display_translate (GdkMacosDisplay *self,
+ NSEvent *nsevent)
+{
+ GdkMacosSurface *surface;
+ GdkMacosWindow *window;
+ NSEventType event_type;
+ NSWindow *nswindow;
+ GdkEvent *ret = NULL;
+ NSPoint point;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+ g_return_val_if_fail (nsevent != NULL, NULL);
+
+ /* There is no support for real desktop wide grabs, so we break
+ * grabs when the application loses focus (gets deactivated).
+ */
+ event_type = [nsevent type];
+ if (event_type == NSEventTypeAppKitDefined)
+ {
+ if ([nsevent subtype] == NSEventSubtypeApplicationDeactivated)
+ _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+
+ /* This could potentially be used to break grabs when clicking
+ * on the title. The subtype 20 is undocumented so it's probably
+ * not a good idea: else if (subtype == 20) break_all_grabs ();
+ */
+
+ /* Leave all AppKit events to AppKit. */
+ return NULL;
+ }
+
+ /* Make sure the event has a window */
+ if (!(nswindow = [nsevent window]))
+ {
+ int x_tmp;
+ int y_tmp;
+
+ if (!(nswindow = _gdk_macos_display_find_native_under_pointer (self, &x_tmp, &y_tmp)))
+ return NULL;
+
+ point.x = x_tmp;
+ point.y = y_tmp;
+ }
+ else
+ {
+ point = [[nswindow contentView] convertPoint:[nsevent locationInWindow] fromView:nil];
+ }
+
+ /* Ignore unless it is for a GdkMacosWindow */
+ if (!GDK_IS_MACOS_WINDOW (nswindow))
+ return NULL;
+
+ window = (GdkMacosWindow *)nswindow;
+
+ /* Ignore events and break grabs while the window is being
+ * dragged. This is a workaround for the window getting events for
+ * the window title.
+ */
+ if ([window isInMove])
+ {
+ _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+ return NULL;
+ }
+
+ /* Also when in a manual resize or move , we ignore events so that
+ * these are pushed to GdkMacosNSWindow's sendEvent handler.
+ */
+ if ([window isInManualResizeOrMove])
+ return NULL;
+
+ /* Make sure we have a GdkSurface */
+ if (!(surface = [window gdkSurface]))
+ return NULL;
+
+ /* Quartz handles resizing on its own, so stay out of the way. */
+ if (test_resize (nsevent, surface, point.x, point.y))
+ return NULL;
+
+ /* If the app is not active leave the event to AppKit so the window gets
+ * focused correctly and don't do click-through (so we behave like most
+ * native apps). If the app is active, we focus the window and then handle
+ * the event, also to match native apps.
+ */
+ if ((event_type == NSEventTypeRightMouseDown ||
+ event_type == NSEventTypeOtherMouseDown ||
+ event_type == NSEventTypeLeftMouseDown))
+ {
+ if (![NSApp isActive])
+ {
+ [NSApp activateIgnoringOtherApps:YES];
+ return NULL;
+ }
+ else if (![nswindow isKeyWindow])
+ {
+ [nswindow makeKeyWindow];
+ }
+ }
+
+ switch ((int)event_type)
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseUp:
+ ret = fill_button_event (self, surface, nsevent, point.x, point.y);
+ break;
+
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDragged:
+ case NSEventTypeMouseMoved:
+ ret = fill_motion_event (self, surface, nsevent, point.x, point.y);
+ break;
+
+ case NSEventTypeMagnify:
+ case NSEventTypeRotate:
+ ret = fill_pinch_event (self, surface, nsevent, point.x, point.y);
+ break;
+
+ case NSEventTypeMouseExited:
+ if (_gdk_macos_surface_is_tracking (surface, [nsevent trackingArea]))
+ {
+ [[NSCursor arrowCursor] set];
+ ret = synthesize_crossing_event (self, surface, nsevent, point.x, point.y);
+ }
+ break;
+
+ case NSEventTypeMouseEntered:
+ if (_gdk_macos_surface_is_tracking (surface, [nsevent trackingArea]))
+ ret = synthesize_crossing_event (self, surface, nsevent, point.x, point.y);
+ break;
+
+ case NSEventTypeKeyDown:
+ case NSEventTypeKeyUp:
+ case NSEventTypeFlagsChanged: {
+ GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent);
+
+ if (type)
+ ret = fill_key_event (self, surface, nsevent, type);
+
+ break;
+ }
+
+ case NSEventTypeScrollWheel:
+ ret = fill_scroll_event (self, surface, nsevent, point.x, point.y);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+void
+_gdk_macos_display_synthesize_motion (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ GdkModifierType state;
+ GdkEvent *event;
+ GdkSeat *seat;
+ NSPoint point;
+ GList *node;
+ int x;
+ int y;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ point = [NSEvent mouseLocation];
+ _gdk_macos_display_from_display_coords (self, point.x, point.y, &x, &y);
+
+ state = _gdk_macos_display_get_current_keyboard_modifiers (self) |
+ _gdk_macos_display_get_current_mouse_modifiers (self);
+
+ event = gdk_motion_event_new (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event ([NSApp currentEvent]),
+ state,
+ x,
+ y,
+ NULL);
+ node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+}
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
new file mode 100644
index 0000000000..6cb6437659
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -0,0 +1,955 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+
+#import "GdkMacosWindow.h"
+
+#include "gdkdisplayprivate.h"
+#include "gdkeventsprivate.h"
+
+#include "gdkdisplaylinksource.h"
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacosseat-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacosutils-private.h"
+
+/**
+ * SECTION:macos_interaction
+ * @Short_description: macOS backend-specific functions
+ * @Title: macOS Interaction
+ * @Include: gdk/macos/gdkmacos.h
+ *
+ * The functions in this section are specific to the GDK macOS backend.
+ * To use them, you need to include the `<gdk/macos/gdkmacos.h>` header and
+ * use the macOS-specific pkg-config `gtk4-macos` file to build your
+ * application.
+ *
+ * To make your code compile with other GDK backends, guard backend-specific
+ * calls by an ifdef as follows. Since GDK may be built with multiple
+ * backends, you should also check for the backend that is in use (e.g. by
+ * using the GDK_IS_MACOS_DISPLAY() macro).
+ * |[<!-- language="C" -->
+ * #ifdef GDK_WINDOWING_MACOS
+ * if (GDK_IS_MACOS_DISPLAY (display))
+ * {
+ * // make macOS-specific calls here
+ * }
+ * else
+ * #endif
+ * #ifdef GDK_WINDOWING_X11
+ * if (GDK_IS_X11_DISPLAY (display))
+ * {
+ * // make X11-specific calls here
+ * }
+ * else
+ * #endif
+ * g_error ("Unsupported GDK backend");
+ * ]|
+ */
+
+G_DEFINE_TYPE (GdkMacosDisplay, gdk_macos_display, GDK_TYPE_DISPLAY)
+
+static GSource *event_source;
+
+static gboolean
+gdk_macos_display_get_setting (GdkDisplay *display,
+ const gchar *setting,
+ GValue *value)
+{
+ return _gdk_macos_display_get_setting (GDK_MACOS_DISPLAY (display), setting, value);
+}
+
+static int
+gdk_macos_display_get_n_monitors (GdkDisplay *display)
+{
+ return GDK_MACOS_DISPLAY (display)->monitors->len;
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor (GdkDisplay *display,
+ int index)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (index >= 0);
+
+ if (index < self->monitors->len)
+ return g_ptr_array_index (self->monitors, index);
+
+ return NULL;
+}
+
+static GdkMonitor *
+gdk_macos_display_get_primary_monitor (GdkDisplay *display)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ return g_ptr_array_index (self->monitors, 0);
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor_at_surface (GdkDisplay *display,
+ GdkSurface *surface)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ CGDirectDisplayID screen_id;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ screen_id = _gdk_macos_surface_get_screen_id (GDK_MACOS_SURFACE (surface));
+
+ for (guint i = 0; i < self->monitors->len; i++)
+ {
+ GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, i);
+
+ if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+ return GDK_MONITOR (monitor);
+ }
+
+ return gdk_macos_display_get_primary_monitor (display);
+}
+
+static void
+gdk_macos_display_add_monitor (GdkMacosDisplay *self,
+ GdkMacosMonitor *monitor)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_MONITOR (monitor));
+
+ g_ptr_array_add (self->monitors, g_object_ref (monitor));
+ gdk_display_monitor_added (GDK_DISPLAY (self), GDK_MONITOR (monitor));
+}
+
+static void
+gdk_macos_display_remove_monitor (GdkMacosDisplay *self,
+ GdkMacosMonitor *monitor)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_MONITOR (monitor));
+
+ g_object_ref (monitor);
+
+ if (g_ptr_array_remove (self->monitors, monitor))
+ gdk_display_monitor_removed (GDK_DISPLAY (self), GDK_MONITOR (monitor));
+
+ g_object_unref (monitor);
+}
+
+static GdkMacosMonitor *
+gdk_macos_display_find_monitor (GdkMacosDisplay *self,
+ CGDirectDisplayID screen_id)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ for (guint i = 0; i < self->monitors->len; i++)
+ {
+ GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, i);
+
+ if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+ return monitor;
+ }
+
+ return NULL;
+}
+
+static void
+gdk_macos_display_update_bounds (GdkMacosDisplay *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ int max_x = 0;
+ int max_y = 0;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ self->min_x = 0;
+ self->min_y = 0;
+
+ for (id obj in [NSScreen screens])
+ {
+ NSRect geom = [(NSScreen *)obj frame];
+
+ self->min_x = MIN (self->min_x, geom.origin.x);
+ self->min_y = MIN (self->min_y, geom.origin.y);
+ max_x = MAX (max_x, geom.origin.x + geom.size.width);
+ max_y = MAX (max_y, geom.origin.y + geom.size.height);
+ }
+
+ self->width = max_x - self->min_x;
+ self->height = max_y - self->min_y;
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ GdkMacosDisplay *self = observer;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ _gdk_macos_display_reload_monitors (self);
+}
+
+static void
+gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ GdkMacosDisplay *self = observer;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ _gdk_macos_display_reload_settings (self);
+}
+
+void
+_gdk_macos_display_reload_monitors (GdkMacosDisplay *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GArray *seen;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ gdk_macos_display_update_bounds (self);
+
+ seen = g_array_new (FALSE, FALSE, sizeof (CGDirectDisplayID));
+
+ for (id obj in [NSScreen screens])
+ {
+ CGDirectDisplayID screen_id;
+ GdkMacosMonitor *monitor;
+
+ screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+
+ if ((monitor = gdk_macos_display_find_monitor (self, screen_id)))
+ {
+ _gdk_macos_monitor_reconfigure (monitor);
+ }
+ else
+ {
+ monitor = _gdk_macos_monitor_new (self, screen_id);
+ gdk_macos_display_add_monitor (self, monitor);
+ g_object_unref (monitor);
+ }
+
+ g_array_append_val (seen, screen_id);
+ }
+
+ for (guint i = self->monitors->len; i > 0; i--)
+ {
+ GdkMacosMonitor *monitor = g_ptr_array_index (self->monitors, i - 1);
+ CGDirectDisplayID screen_id = _gdk_macos_monitor_get_screen_id (monitor);
+ gboolean found = FALSE;
+
+ for (guint j = 0; j < seen->len; j++)
+ {
+ if (screen_id == g_array_index (seen, CGDirectDisplayID, j))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ gdk_macos_display_remove_monitor (self, monitor);
+ }
+
+ g_array_unref (seen);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_load_seat (GdkMacosDisplay *self)
+{
+ GdkSeat *seat;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ seat = _gdk_macos_seat_new (self);
+ gdk_display_add_seat (GDK_DISPLAY (self), seat);
+ g_object_unref (seat);
+}
+
+static gboolean
+gdk_macos_display_frame_cb (gpointer data)
+{
+ GdkMacosDisplay *self = data;
+ GdkDisplayLinkSource *source;
+ gint64 presentation_time;
+ gint64 now;
+ GList *iter;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ source = (GdkDisplayLinkSource *)self->frame_source;
+
+ presentation_time = source->presentation_time;
+ now = g_source_get_time ((GSource *)source);
+
+ iter = self->awaiting_frames.head;
+
+ while (iter != NULL)
+ {
+ GdkMacosSurface *surface = iter->data;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ iter = iter->next;
+
+ _gdk_macos_display_remove_frame_callback (self, surface);
+ _gdk_macos_surface_thaw (surface,
+ source->presentation_time,
+ source->refresh_interval);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gdk_macos_display_load_display_link (GdkMacosDisplay *self)
+{
+ self->frame_source = gdk_display_link_source_new ();
+ g_source_set_callback (self->frame_source,
+ gdk_macos_display_frame_cb,
+ self,
+ NULL);
+ g_source_attach (self->frame_source, NULL);
+}
+
+static const gchar *
+gdk_macos_display_get_name (GdkDisplay *display)
+{
+ return GDK_MACOS_DISPLAY (display)->name;
+}
+
+static void
+gdk_macos_display_beep (GdkDisplay *display)
+{
+ NSBeep ();
+}
+
+static void
+gdk_macos_display_flush (GdkDisplay *display)
+{
+ /* Not Supported */
+}
+
+static void
+gdk_macos_display_sync (GdkDisplay *display)
+{
+ /* Not Supported */
+}
+
+static gboolean
+gdk_macos_display_supports_shapes (GdkDisplay *display)
+{
+ return FALSE;
+}
+
+static gboolean
+gdk_macos_display_supports_input_shapes (GdkDisplay *display)
+{
+ return FALSE;
+}
+
+static gulong
+gdk_macos_display_get_next_serial (GdkDisplay *display)
+{
+ return 0;
+}
+
+static gboolean
+gdk_macos_display_has_pending (GdkDisplay *display)
+{
+ return _gdk_event_queue_find_first (display) ||
+ _gdk_macos_event_source_check_pending ();
+}
+
+static void
+gdk_macos_display_notify_startup_complete (GdkDisplay *display,
+ const gchar *startup_notification_id)
+{
+ /* Not Supported */
+}
+
+static void
+gdk_macos_display_queue_events (GdkDisplay *display)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ NSEvent *nsevent;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if ((nsevent = _gdk_macos_event_source_get_pending ()))
+ {
+ GdkEvent *event = _gdk_macos_display_translate (self, nsevent);
+
+ if (event != NULL)
+ _gdk_windowing_got_event (GDK_DISPLAY (self),
+ _gdk_event_queue_append (GDK_DISPLAY (self), event),
+ event,
+ 0);
+ else
+ [NSApp sendEvent:nsevent];
+
+ [nsevent release];
+ }
+}
+
+static void
+_gdk_macos_display_surface_added (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (!queue_contains (&self->sorted_surfaces, &surface->sorted));
+ g_assert (!queue_contains (&self->main_surfaces, &surface->main));
+ g_assert (!queue_contains (&self->awaiting_frames, &surface->frame));
+ g_assert (surface->sorted.data == surface);
+ g_assert (surface->main.data == surface);
+ g_assert (surface->frame.data == surface);
+
+ g_queue_push_tail_link (&self->sorted_surfaces, &surface->sorted);
+
+ if (GDK_IS_TOPLEVEL (surface))
+ g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+}
+
+void
+_gdk_macos_display_surface_removed (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (self->keyboard_surface == surface)
+ _gdk_macos_display_surface_resigned_key (self, surface);
+
+ g_queue_unlink (&self->sorted_surfaces, &surface->sorted);
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ g_queue_unlink (&self->main_surfaces, &surface->main);
+
+ if (queue_contains (&self->awaiting_frames, &surface->frame))
+ g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+ g_return_if_fail (self->keyboard_surface != surface);
+}
+
+void
+_gdk_macos_display_surface_became_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ GdkDevice *keyboard;
+ GdkEvent *event;
+ GdkSeat *seat;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+ g_return_if_fail (self->keyboard_surface == NULL);
+
+ self->keyboard_surface = surface;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ keyboard = gdk_seat_get_keyboard (seat);
+ event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, TRUE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), event);
+
+ _gdk_macos_display_stacking_changed (self);
+
+ /* We just became the active window. Unlike X11, Mac OS X does
+ * not send us motion events while the window does not have focus
+ * ("is not key"). We send a dummy motion notify event now, so that
+ * everything in the window is set to correct state.
+ */
+ _gdk_macos_display_synthesize_motion (self, surface);
+}
+
+void
+_gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+
+ if (self->keyboard_surface == surface)
+ {
+ GdkDevice *keyboard;
+ GdkEvent *event;
+ GdkSeat *seat;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ keyboard = gdk_seat_get_keyboard (seat);
+ event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, FALSE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ }
+
+ self->keyboard_surface = NULL;
+}
+
+void
+_gdk_macos_display_surface_became_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ g_queue_unlink (&self->main_surfaces, &surface->main);
+
+ g_queue_push_head_link (&self->main_surfaces, &surface->main);
+
+ _gdk_macos_display_stacking_changed (self);
+}
+
+void
+_gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ g_queue_unlink (&self->main_surfaces, &surface->main);
+
+ for (const GList *iter = self->main_surfaces.head; iter; iter = iter->next)
+ {
+ GdkMacosSurface *new_surface = iter->data;
+
+ g_assert (GDK_IS_MACOS_SURFACE (new_surface));
+ g_assert (new_surface != surface);
+
+ if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (new_surface)) &&
+ GDK_IS_TOPLEVEL (new_surface))
+ {
+ NSWindow *nswindow = _gdk_macos_surface_get_native (new_surface);
+ [nswindow makeKeyAndOrderFront:nswindow];
+ break;
+ }
+ }
+
+ g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+}
+
+void
+_gdk_macos_display_stacking_changed (GdkMacosDisplay *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ NSArray *ordered;
+ GQueue sorted = G_QUEUE_INIT;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ /* "orderedWindows" gives us the stacking order starting from front-to-back.
+ * We maintain this so that we can resolve X,Y coordinates in the topmost
+ * surfaces while processing GDK operations from devices, etc.
+ */
+
+ ordered = [NSApp orderedWindows];
+
+ for (id nswindow in ordered)
+ {
+ GdkMacosSurface *surface;
+ GList *link;
+
+ if (!GDK_IS_MACOS_WINDOW (nswindow))
+ continue;
+
+ surface = [(GdkMacosWindow *)nswindow gdkSurface];
+
+ link = &surface->sorted;
+ link->prev = NULL;
+ link->next = NULL;
+
+ g_queue_push_tail_link (&sorted, link);
+ }
+
+ self->sorted_surfaces = sorted;
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static GdkSurface *
+gdk_macos_display_create_surface (GdkDisplay *display,
+ GdkSurfaceType surface_type,
+ GdkSurface *parent,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ GdkMacosSurface *surface;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+ surface = _gdk_macos_surface_new (self, surface_type, parent, x, y, width, height);
+
+ if (surface != NULL)
+ _gdk_macos_display_surface_added (self, surface);
+
+ return GDK_SURFACE (surface);
+}
+
+static GdkKeymap *
+gdk_macos_display_get_keymap (GdkDisplay *display)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ return GDK_KEYMAP (self->keymap);
+}
+
+static void
+gdk_macos_display_finalize (GObject *object)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)object;
+
+ CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+ NULL);
+
+ CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ CFSTR ("NSUserDefaultsDidChangeNotification"),
+ NULL);
+
+ g_clear_pointer (&self->frame_source, g_source_unref);
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (gdk_macos_display_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_display_class_init (GdkMacosDisplayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkDisplayClass *display_class = GDK_DISPLAY_CLASS (klass);
+
+ object_class->finalize = gdk_macos_display_finalize;
+
+ display_class->cairo_context_type = GDK_TYPE_MACOS_CAIRO_CONTEXT;
+
+ display_class->beep = gdk_macos_display_beep;
+ display_class->create_surface = gdk_macos_display_create_surface;
+ display_class->flush = gdk_macos_display_flush;
+ display_class->get_keymap = gdk_macos_display_get_keymap;
+ display_class->get_monitor = gdk_macos_display_get_monitor;
+ display_class->get_monitor_at_surface = gdk_macos_display_get_monitor_at_surface;
+ display_class->get_next_serial = gdk_macos_display_get_next_serial;
+ display_class->get_n_monitors = gdk_macos_display_get_n_monitors;
+ display_class->get_name = gdk_macos_display_get_name;
+ display_class->get_primary_monitor = gdk_macos_display_get_primary_monitor;
+ display_class->get_setting = gdk_macos_display_get_setting;
+ display_class->has_pending = gdk_macos_display_has_pending;
+ display_class->notify_startup_complete = gdk_macos_display_notify_startup_complete;
+ display_class->queue_events = gdk_macos_display_queue_events;
+ display_class->supports_input_shapes = gdk_macos_display_supports_input_shapes;
+ display_class->supports_shapes = gdk_macos_display_supports_shapes;
+ display_class->sync = gdk_macos_display_sync;
+}
+
+static void
+gdk_macos_display_init (GdkMacosDisplay *self)
+{
+ self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+GdkDisplay *
+_gdk_macos_display_open (const gchar *display_name)
+{
+ static GdkMacosDisplay *self;
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+
+ /* Until we can have multiple GdkMacosEventSource instances
+ * running concurrently, we can't exactly support multiple
+ * display connections. So just short-circuit if we already
+ * have one active.
+ */
+ if (self != NULL)
+ return NULL;
+
+ GDK_NOTE (MISC, g_message ("opening display %s", display_name ? display_name : ""));
+
+ /* Make the current process a foreground application, i.e. an app
+ * with a user interface, in case we're not running from a .app bundle
+ */
+ TransformProcessType (&psn, kProcessTransformToForegroundApplication);
+
+ [NSApplication sharedApplication];
+
+ self = g_object_new (GDK_TYPE_MACOS_DISPLAY, NULL);
+ self->name = g_strdup (display_name);
+ self->keymap = _gdk_macos_keymap_new (self);
+
+ gdk_macos_display_load_seat (self);
+ _gdk_macos_display_reload_monitors (self);
+ gdk_macos_display_load_display_link (self);
+
+ CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ gdk_macos_display_monitors_changed_cb,
+ CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ gdk_macos_display_user_defaults_changed_cb,
+ CFSTR ("NSUserDefaultsDidChangeNotification"),
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ if (event_source == NULL)
+ {
+ event_source = _gdk_macos_event_source_new (self);
+ g_source_attach (event_source, NULL);
+ }
+
+ g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+ gdk_display_emit_opened (GDK_DISPLAY (self));
+
+ return GDK_DISPLAY (self);
+}
+
+void
+_gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if (out_y)
+ *out_y = self->height - y + self->min_y;
+
+ if (out_x)
+ *out_x = x + self->min_x;
+}
+
+void
+_gdk_macos_display_from_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if (out_y != NULL)
+ *out_y = self->height - y + self->min_y;
+
+ if (out_x != NULL)
+ *out_x = x - self->min_x;
+}
+
+NSScreen *
+_gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ NSArray *screens;
+ NSScreen *screen = NULL;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+ screens = [NSScreen screens];
+
+ for (id obj in screens)
+ {
+ NSRect geom = [obj frame];
+
+ if (x >= geom.origin.x && x <= geom.origin.x + geom.size.width &&
+ y >= geom.origin.y && y <= geom.origin.y + geom.size.height)
+ {
+ screen = obj;
+ break;
+ }
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return screen;
+}
+
+void
+_gdk_macos_display_break_all_grabs (GdkMacosDisplay *self,
+ guint32 time)
+{
+ GdkDevice *devices[2];
+ GdkSeat *seat;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+
+ devices[0] = gdk_seat_get_keyboard (seat);
+ devices[1] = gdk_seat_get_pointer (seat);
+
+ for (guint i = 0; i < G_N_ELEMENTS (devices); i++)
+ {
+ GdkDevice *device = devices[i];
+ GdkDeviceGrabInfo *grab;
+
+ grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), device);
+
+ if (grab != NULL)
+ {
+ grab->serial_end = 0;
+ grab->implicit_ungrab = TRUE;
+ }
+
+ _gdk_display_device_grab_update (GDK_DISPLAY (self), device, NULL, 0);
+ }
+}
+
+void
+_gdk_macos_display_queue_events (GdkMacosDisplay *self)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ gdk_macos_display_queue_events (GDK_DISPLAY (self));
+}
+
+GdkMacosSurface *
+_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self,
+ double x,
+ double y,
+ int *surface_x,
+ int *surface_y)
+{
+ int x_gdk;
+ int y_gdk;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+ g_return_val_if_fail (surface_x != NULL, NULL);
+ g_return_val_if_fail (surface_y != NULL, NULL);
+
+ _gdk_macos_display_from_display_coords (self, x, y, &x_gdk, &y_gdk);
+
+ for (const GList *iter = self->sorted_surfaces.head; iter; iter = iter->next)
+ {
+ GdkSurface *surface = iter->data;
+ NSWindow *nswindow;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+ if (![nswindow isVisible])
+ continue;
+
+ if (x_gdk >= surface->x &&
+ y_gdk >= surface->y &&
+ x_gdk <= (surface->x + surface->width) &&
+ y_gdk >= (surface->y + surface->height))
+ {
+ *surface_x = x_gdk - surface->x;
+ *surface_y = y_gdk - surface->y;
+
+ return GDK_MACOS_SURFACE (surface);
+ }
+ }
+
+ *surface_x = 0;
+ *surface_y = 0;
+
+ return NULL;
+}
+
+void
+_gdk_macos_display_add_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (!queue_contains (&self->awaiting_frames, &surface->frame))
+ {
+ g_queue_push_tail_link (&self->awaiting_frames, &surface->frame);
+
+ if (self->awaiting_frames.length == 1)
+ gdk_display_link_source_unpause ((GdkDisplayLinkSource *)self->frame_source);
+ }
+}
+
+void
+_gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->awaiting_frames, &surface->frame))
+ {
+ g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+ if (self->awaiting_frames.length == 0)
+ gdk_display_link_source_pause ((GdkDisplayLinkSource *)self->frame_source);
+ }
+}
+
+NSWindow *
+_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self,
+ int *x,
+ int *y)
+{
+ GdkMacosSurface *surface;
+ NSPoint point;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ point = [NSEvent mouseLocation];
+ surface = _gdk_macos_display_get_surface_at_display_coords (self, point.x, point.y, x, y);
+ if (surface != NULL)
+ return _gdk_macos_surface_get_native (surface);
+
+ return NULL;
+}
+
diff --git a/gdk/macos/gdkmacosdisplay.h b/gdk/macos/gdkmacosdisplay.h
new file mode 100644
index 0000000000..7c5730d0c7
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DISPLAY_H__
+#define __GDK_MACOS_DISPLAY_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#ifdef GTK_COMPILATION
+typedef struct _GdkMacosDisplay GdkMacosDisplay;
+#else
+typedef GdkDisplay GdkMacosDisplay;
+#endif
+typedef struct _GdkMacosDisplayClass GdkMacosDisplayClass;
+
+#define GDK_TYPE_MACOS_DISPLAY (gdk_macos_display_get_type())
+#define GDK_MACOS_DISPLAY(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DISPLAY,
GdkMacosDisplay))
+#define GDK_IS_MACOS_DISPLAY(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DISPLAY))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_display_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_H__ */
diff --git a/gdk/macos/gdkmacosdragsurface-private.h b/gdk/macos/gdkmacosdragsurface-private.h
new file mode 100644
index 0000000000..94dec116fc
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface-private.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDragSurface GdkMacosDragSurface;
+typedef struct _GdkMacosDragSurfaceClass GdkMacosDragSurfaceClass;
+
+#define GDK_TYPE_MACOS_DRAG_SURFACE (_gdk_macos_drag_surface_get_type())
+#define GDK_MACOS_DRAG_SURFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object),
GDK_TYPE_MACOS_DRAG_SURFACE, GdkMacosDragSurface))
+#define GDK_IS_MACOS_DRAG_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object),
GDK_TYPE_MACOS_DRAG_SURFACE))
+
+GType _gdk_macos_drag_surface_get_type (void);
+GdkMacosSurface *_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdragsurface.c b/gdk/macos/gdkmacosdragsurface.c
new file mode 100644
index 0000000000..000eb8f602
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkdragsurfaceprivate.h"
+
+#include "gdkmacosdragsurface-private.h"
+
+struct _GdkMacosDragSurface
+{
+ GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosDragSurfaceClass
+{
+ GdkMacosSurfaceClass parent_instance;
+};
+
+static void
+drag_surface_iface_init (GdkDragSurfaceInterface *iface)
+{
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosDragSurface, _gdk_macos_drag_surface, GDK_TYPE_MACOS_SURFACE,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_DRAG_SURFACE, drag_surface_iface_init))
+
+static void
+_gdk_macos_drag_surface_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (_gdk_macos_drag_surface_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_drag_surface_class_init (GdkMacosDragSurfaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = _gdk_macos_drag_surface_finalize;
+}
+
+static void
+_gdk_macos_drag_surface_init (GdkMacosDragSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+ g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+ g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
+
+ return g_object_new (GDK_TYPE_MACOS_DRAG_SURFACE,
+ "display", display,
+ "frame-clock", frame_clock,
+ NULL);
+}
diff --git a/gdk/macos/gdkmacoseventsource-private.h b/gdk/macos/gdkmacoseventsource-private.h
new file mode 100644
index 0000000000..09853a18ac
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource-private.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+#define __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP,
+} GdkMacosEventSubType;
+
+GSource *_gdk_macos_event_source_new (GdkMacosDisplay *display);
+NSEvent *_gdk_macos_event_source_get_pending (void);
+gboolean _gdk_macos_event_source_check_pending (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoseventsource.c b/gdk/macos/gdkmacoseventsource.c
new file mode 100644
index 0000000000..c9c416b136
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource.c
@@ -0,0 +1,1094 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "gdkdisplayprivate.h"
+#include "gdkinternals.h"
+
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-private.h"
+
+/*
+ * This file implementations integration between the GLib main loop and
+ * the native system of the Core Foundation run loop and Cocoa event
+ * handling. There are basically two different cases that we need to
+ * handle: either the GLib main loop is in control (the application
+ * has called gtk_main(), or is otherwise iterating the main loop), or
+ * CFRunLoop is in control (we are in a modal operation such as window
+ * resizing or drag-and-drop.)
+ *
+ * When the GLib main loop is in control we integrate in native event
+ * handling in two ways: first we add a GSource that handles checking
+ * whether there are native events available, translating native events
+ * to GDK events, and dispatching GDK events. Second we replace the
+ * "poll function" of the GLib main loop with our own version that knows
+ * how to wait for both the file descriptors and timeouts that GLib is
+ * interested in and also for incoming native events.
+ *
+ * When CFRunLoop is in control, we integrate in GLib main loop handling
+ * by adding a "run loop observer" that gives us notification at various
+ * points in the run loop cycle. We map these points onto the corresponding
+ * stages of the GLib main loop (prepare, check, dispatch), and make the
+ * appropriate calls into GLib.
+ *
+ * Both cases share a single problem: the OS X API’s don’t allow us to
+ * wait simultaneously for file descriptors and for events. So when we
+ * need to do a blocking wait that includes file descriptor activity, we
+ * push the actual work of calling select() to a helper thread (the
+ * "select thread") and wait for native events in the main thread.
+ *
+ * The main known limitation of this code is that if a callback is triggered
+ * via the OS X run loop while we are "polling" (in either case described
+ * above), iteration of the GLib main loop is not possible from within
+ * that callback. If the programmer tries to do so explicitly, then they
+ * will get a warning from GLib "main loop already active in another thread".
+ */
+
+/******* State for run loop iteration *******/
+
+/* Count of number of times we've gotten an "Entry" notification for
+ * our run loop observer.
+ */
+static int current_loop_level = 0;
+
+/* Run loop level at which we acquired ownership of the GLib main
+ * loop. See note in run_loop_entry(). -1 means that we don’t have
+ * ownership
+ */
+static int acquired_loop_level = -1;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * whether we we need to call select_thread_collect_poll()
+ */
+static gboolean run_loop_polling_async = FALSE;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * max_prioritiy to pass to g_main_loop_check()
+ */
+static gint run_loop_max_priority;
+
+/* Timer that we've added to wake up the run loop when a GLib timeout
+ */
+static CFRunLoopTimerRef run_loop_timer = NULL;
+
+/* These are the file descriptors that are we are polling out of
+ * the run loop. (We keep the array around and reuse it to avoid
+ * constant allocations.)
+ */
+#define RUN_LOOP_POLLFDS_INITIAL_SIZE 16
+static GPollFD *run_loop_pollfds;
+static guint run_loop_pollfds_size; /* Allocated size of the array */
+static guint run_loop_n_pollfds; /* Number of file descriptors in the array */
+
+/******* Other global variables *******/
+
+/* Since we count on replacing the GLib main loop poll function as our
+ * method of integrating Cocoa event handling into the GLib main loop
+ * we need to make sure that the poll function is always called even
+ * when there are no file descriptors that need to be polled. To do
+ * this, we add a dummy GPollFD to our event source with a file
+ * descriptor of “-1”. Then any time that GLib is polling the event
+ * source, it will call our poll function.
+ */
+static GPollFD event_poll_fd;
+
+/* Current NSEvents that we've gotten from Cocoa but haven't yet converted
+ * to GdkEvents. We wait until our dispatch() function to do the conversion
+ * since the conversion can conceivably cause signals to be emmitted
+ * or other things that shouldn’t happen inside a poll function.
+ */
+static GQueue *current_events;
+
+/* The default poll function for GLib; we replace this with our own
+ * Cocoa-aware version and then call the old version to do actual
+ * file descriptor polling. There’s no actual need to chain to the
+ * old one; we could reimplement the same functionality from scratch,
+ * but since the default implementation does the right thing, why
+ * bother.
+ */
+static GPollFunc old_poll_func;
+
+/* Reference to the run loop of the main thread. (There is a unique
+ * CFRunLoop per thread.)
+ */
+static CFRunLoopRef main_thread_run_loop;
+
+/* Normally the Cocoa main loop maintains an NSAutoReleasePool and frees
+ * it on every iteration. Since we are replacing the main loop we have
+ * to provide this functionality ourself. We free and replace the
+ * auto-release pool in our sources prepare() function.
+ */
+static NSAutoreleasePool *autorelease_pool;
+
+/* Flag when we've called nextEventMatchingMask ourself; this triggers
+ * a run loop iteration, so we need to detect that and avoid triggering
+ * our "run the GLib main looop while the run loop is active machinery.
+ */
+static gint getting_events = 0;
+
+/************************************************************
+ ********* Select Thread *********
+ ************************************************************/
+
+/* The states in our state machine, see comments in select_thread_func()
+ * for descriptiions of each state
+ */
+typedef enum {
+ BEFORE_START,
+ WAITING,
+ POLLING_QUEUED,
+ POLLING_RESTART,
+ POLLING_DESCRIPTORS,
+} SelectThreadState;
+
+#ifdef G_ENABLE_DEBUG
+static const char *const state_names[] = {
+ "BEFORE_START",
+ "WAITING",
+ "POLLING_QUEUED",
+ "POLLING_RESTART",
+ "POLLING_DESCRIPTORS"
+};
+#endif
+
+static SelectThreadState select_thread_state = BEFORE_START;
+
+static pthread_t select_thread;
+static pthread_mutex_t select_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t select_thread_cond = PTHREAD_COND_INITIALIZER;
+
+#define SELECT_THREAD_LOCK() pthread_mutex_lock (&select_thread_mutex)
+#define SELECT_THREAD_UNLOCK() pthread_mutex_unlock (&select_thread_mutex)
+#define SELECT_THREAD_SIGNAL() pthread_cond_signal (&select_thread_cond)
+#define SELECT_THREAD_WAIT() pthread_cond_wait (&select_thread_cond, &select_thread_mutex)
+
+/* These are the file descriptors that the select thread is currently
+ * polling.
+ */
+static GPollFD *current_pollfds;
+static guint current_n_pollfds;
+
+/* These are the file descriptors that the select thread should pick
+ * up and start polling when it has a chance.
+ */
+static GPollFD *next_pollfds;
+static guint next_n_pollfds;
+
+/* Pipe used to wake up the select thread */
+static gint select_thread_wakeup_pipe[2];
+
+/* Run loop source used to wake up the main thread */
+static CFRunLoopSourceRef select_main_thread_source;
+
+static void
+select_thread_set_state (SelectThreadState new_state)
+{
+ gboolean old_state;
+
+ if (select_thread_state == new_state)
+ return;
+
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Select thread state: %s => %s",
state_names[select_thread_state], state_names[new_state]));
+
+ old_state = select_thread_state;
+ select_thread_state = new_state;
+ if (old_state == WAITING && new_state != WAITING)
+ SELECT_THREAD_SIGNAL ();
+}
+
+static void
+signal_main_thread (void)
+{
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Waking up main thread"));
+
+ /* If we are in nextEventMatchingMask, then we need to make sure an
+ * event gets queued, otherwise it's enough to simply wake up the
+ * main thread run loop
+ */
+ if (!run_loop_polling_async)
+ CFRunLoopSourceSignal (select_main_thread_source);
+
+ /* Don't check for CFRunLoopIsWaiting() here because it causes a
+ * race condition (the loop could go into waiting state right after
+ * we checked).
+ */
+ CFRunLoopWakeUp (main_thread_run_loop);
+}
+
+static void *
+select_thread_func (void *arg)
+{
+ char c;
+
+ SELECT_THREAD_LOCK ();
+
+ while (TRUE)
+ {
+ switch (select_thread_state)
+ {
+ case BEFORE_START:
+ /* The select thread has not been started yet
+ */
+ g_assert_not_reached ();
+
+ case WAITING:
+ /* Waiting for a set of file descriptors to be submitted by the main thread
+ *
+ * => POLLING_QUEUED: main thread thread submits a set of file descriptors
+ */
+ SELECT_THREAD_WAIT ();
+ break;
+
+ case POLLING_QUEUED:
+ /* Waiting for a set of file descriptors to be submitted by the main thread
+ *
+ * => POLLING_DESCRIPTORS: select thread picks up the file descriptors to begin polling
+ */
+ g_free (current_pollfds);
+
+ current_pollfds = next_pollfds;
+ current_n_pollfds = next_n_pollfds;
+
+ next_pollfds = NULL;
+ next_n_pollfds = 0;
+
+ select_thread_set_state (POLLING_DESCRIPTORS);
+ break;
+
+ case POLLING_RESTART:
+ /* Select thread is currently polling a set of file descriptors, main thread has
+ * began a new iteration with the same set of file descriptors. We don't want to
+ * wake the select thread up and wait for it to restart immediately, but to avoid
+ * a race (described below in select_thread_start_polling()) we need to recheck after
+ * polling completes.
+ *
+ * => POLLING_DESCRIPTORS: select completes, main thread rechecks by polling again
+ * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+ */
+ select_thread_set_state (POLLING_DESCRIPTORS);
+ break;
+
+ case POLLING_DESCRIPTORS:
+ /* In the process of polling the file descriptors
+ *
+ * => WAITING: polling completes when a file descriptor becomes active
+ * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+ * => POLLING_RESTART: main thread begins a new iteration with the same set file descriptors
+ */
+ SELECT_THREAD_UNLOCK ();
+ old_poll_func (current_pollfds, current_n_pollfds, -1);
+ SELECT_THREAD_LOCK ();
+
+ read (select_thread_wakeup_pipe[0], &c, 1);
+
+ if (select_thread_state == POLLING_DESCRIPTORS)
+ {
+ signal_main_thread ();
+ select_thread_set_state (WAITING);
+ }
+ break;
+ }
+ }
+}
+
+static void
+got_fd_activity (void *info)
+{
+ NSEvent *event;
+
+ /* Post a message so we'll break out of the message loop */
+ event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: 0
+ context: nil
+ subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+ data1: 0
+ data2: 0];
+
+ [NSApp postEvent:event atStart:YES];
+}
+
+static void
+select_thread_start (void)
+{
+ g_return_if_fail (select_thread_state == BEFORE_START);
+
+ pipe (select_thread_wakeup_pipe);
+ fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
+
+ CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
got_fd_activity };
+ select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
+
+ CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopCommonModes);
+
+ select_thread_state = WAITING;
+
+ while (TRUE)
+ {
+ if (pthread_create (&select_thread, NULL, select_thread_func, NULL) == 0)
+ break;
+
+ g_warning ("Failed to create select thread, sleeping and trying again");
+ sleep (1);
+ }
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+dump_poll_result (GPollFD *ufds,
+ guint nfds)
+{
+ GString *s;
+ gint i;
+
+ s = g_string_new ("");
+ for (i = 0; i < nfds; i++)
+ {
+ if (ufds[i].fd >= 0 && ufds[i].revents)
+ {
+ g_string_append_printf (s, " %d:", ufds[i].fd);
+ if (ufds[i].revents & G_IO_IN)
+ g_string_append (s, " in");
+ if (ufds[i].revents & G_IO_OUT)
+ g_string_append (s, " out");
+ if (ufds[i].revents & G_IO_PRI)
+ g_string_append (s, " pri");
+ g_string_append (s, "\n");
+ }
+ }
+ g_message ("%s", s->str);
+ g_string_free (s, TRUE);
+}
+#endif
+
+static gboolean
+pollfds_equal (GPollFD *old_pollfds,
+ guint old_n_pollfds,
+ GPollFD *new_pollfds,
+ guint new_n_pollfds)
+{
+ gint i;
+
+ if (old_n_pollfds != new_n_pollfds)
+ return FALSE;
+
+ for (i = 0; i < old_n_pollfds; i++)
+ {
+ if (old_pollfds[i].fd != new_pollfds[i].fd ||
+ old_pollfds[i].events != new_pollfds[i].events)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Begins a polling operation with the specified GPollFD array; the
+ * timeout is used only to tell if the polling operation is blocking
+ * or non-blocking.
+ *
+ * Returns:
+ * -1: No file descriptors ready, began asynchronous poll
+ * 0: No file descriptors ready, asynchronous poll not needed
+ * > 0: Number of file descriptors ready
+ */
+static gint
+select_thread_start_poll (GPollFD *ufds,
+ guint nfds,
+ gint timeout)
+{
+ gint n_ready;
+ gboolean have_new_pollfds = FALSE;
+ gint poll_fd_index = -1;
+ gint i;
+
+ for (i = 0; i < nfds; i++)
+ if (ufds[i].fd == -1)
+ {
+ poll_fd_index = i;
+ break;
+ }
+
+ if (nfds == 0 ||
+ (nfds == 1 && poll_fd_index >= 0))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Nothing to poll"));
+ return 0;
+ }
+
+ /* If we went immediately to an async poll, then we might decide to
+ * dispatch idle functions when higher priority file descriptor sources
+ * are ready to be dispatched. So we always need to first check
+ * check synchronously with a timeout of zero, and only when no
+ * sources are immediately ready, go to the asynchronous poll.
+ *
+ * Of course, if the timeout passed in is 0, then the synchronous
+ * check is sufficient and we never need to do the asynchronous poll.
+ */
+ n_ready = old_poll_func (ufds, nfds, 0);
+ if (n_ready > 0 || timeout == 0)
+ {
+#ifdef G_ENABLE_DEBUG
+ if ((_gdk_debug_flags & GDK_DEBUG_EVENTLOOP) && n_ready > 0)
+ {
+ g_message ("EventLoop: Found ready file descriptors before waiting");
+ dump_poll_result (ufds, nfds);
+ }
+#endif
+
+ return n_ready;
+ }
+
+ SELECT_THREAD_LOCK ();
+
+ if (select_thread_state == BEFORE_START)
+ {
+ select_thread_start ();
+ }
+
+ if (select_thread_state == POLLING_QUEUED)
+ {
+ /* If the select thread hasn't picked up the set of file descriptors yet
+ * then we can simply replace an old stale set with a new set.
+ */
+ if (!pollfds_equal (ufds, nfds, next_pollfds, next_n_pollfds - 1))
+ {
+ g_free (next_pollfds);
+ next_pollfds = NULL;
+ next_n_pollfds = 0;
+
+ have_new_pollfds = TRUE;
+ }
+ }
+ else if (select_thread_state == POLLING_RESTART || select_thread_state == POLLING_DESCRIPTORS)
+ {
+ /* If we are already in the process of polling the right set of file descriptors,
+ * there's no need for us to immediately force the select thread to stop polling
+ * and then restart again. And avoiding doing so increases the efficiency considerably
+ * in the common case where we have a set of basically inactive file descriptors that
+ * stay unchanged present as we process many events.
+ *
+ * However, we have to be careful that we don't hit the following race condition
+ * Select Thread Main Thread
+ * ----------------- ---------------
+ * Polling Completes
+ * Reads data or otherwise changes file descriptor state
+ * Checks if polling is current
+ * Does nothing (*)
+ * Releases lock
+ * Acquires lock
+ * Marks polling as complete
+ * Wakes main thread
+ * Receives old stale file descriptor state
+ *
+ * To avoid this, when the new set of poll descriptors is the same as the current
+ * one, we transition to the POLLING_RESTART stage at the point marked (*). When
+ * the select thread wakes up from the poll because a file descriptor is active, if
+ * the state is POLLING_RESTART it immediately begins polling same the file descriptor
+ * set again. This normally will just return the same set of active file descriptors
+ * as the first time, but in sequence described above will properly update the
+ * file descriptor state.
+ *
+ * Special case: this RESTART logic is not needed if the only FD is the internal GLib
+ * "wakeup pipe" that is presented when threads are initialized.
+ *
+ * P.S.: The harm in the above sequence is mostly that sources can be signalled
+ * as ready when they are no longer ready. This may prompt a blocking read
+ * from a file descriptor that hangs.
+ */
+ if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
+ have_new_pollfds = TRUE;
+ else
+ {
+ if (!((nfds == 1 && poll_fd_index < 0 && g_thread_supported ()) ||
+ (nfds == 2 && poll_fd_index >= 0 && g_thread_supported ())))
+ select_thread_set_state (POLLING_RESTART);
+ }
+ }
+ else
+ have_new_pollfds = TRUE;
+
+ if (have_new_pollfds)
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Submitting a new set of file descriptor to the select
thread"));
+
+ g_assert (next_pollfds == NULL);
+
+ next_n_pollfds = nfds + 1;
+ next_pollfds = g_new (GPollFD, nfds + 1);
+ memcpy (next_pollfds, ufds, nfds * sizeof (GPollFD));
+
+ next_pollfds[nfds].fd = select_thread_wakeup_pipe[0];
+ next_pollfds[nfds].events = G_IO_IN;
+
+ if (select_thread_state != POLLING_QUEUED && select_thread_state != WAITING)
+ {
+ if (select_thread_wakeup_pipe[1])
+ {
+ char c = 'A';
+ write (select_thread_wakeup_pipe[1], &c, 1);
+ }
+ }
+
+ select_thread_set_state (POLLING_QUEUED);
+ }
+
+ SELECT_THREAD_UNLOCK ();
+
+ return -1;
+}
+
+/* End an asynchronous polling operation started with
+ * select_thread_collect_poll(). This must be called if and only if
+ * select_thread_start_poll() return -1. The GPollFD array passed
+ * in must be identical to the one passed to select_thread_start_poll().
+ *
+ * The results of the poll are written into the GPollFD array passed in.
+ *
+ * Returns: number of file descriptors ready
+ */
+static int
+select_thread_collect_poll (GPollFD *ufds, guint nfds)
+{
+ gint i;
+ gint n_ready = 0;
+
+ SELECT_THREAD_LOCK ();
+
+ if (select_thread_state == WAITING) /* The poll completed */
+ {
+ for (i = 0; i < nfds; i++)
+ {
+ if (ufds[i].fd == -1)
+ continue;
+
+ g_assert (ufds[i].fd == current_pollfds[i].fd);
+ g_assert (ufds[i].events == current_pollfds[i].events);
+
+ if (current_pollfds[i].revents)
+ {
+ ufds[i].revents = current_pollfds[i].revents;
+ n_ready++;
+ }
+ }
+
+#ifdef G_ENABLE_DEBUG
+ if (_gdk_debug_flags & GDK_DEBUG_EVENTLOOP)
+ {
+ g_message ("EventLoop: Found ready file descriptors after waiting");
+ dump_poll_result (ufds, nfds);
+ }
+#endif
+ }
+
+ SELECT_THREAD_UNLOCK ();
+
+ return n_ready;
+}
+
+/************************************************************
+ ********* Main Loop Source *********
+ ************************************************************/
+
+typedef struct _GdkMacosEventSource
+{
+ GSource source;
+ GdkDisplay *display;
+} GdkMacosEventSource;
+
+gboolean
+_gdk_macos_event_source_check_pending (void)
+{
+ return current_events && current_events->head;
+}
+
+NSEvent *
+_gdk_macos_event_source_get_pending (void)
+{
+ NSEvent *event = NULL;
+
+ if (current_events)
+ event = g_queue_pop_tail (current_events);
+
+ return event;
+}
+
+static gboolean
+gdk_macos_event_source_prepare (GSource *source,
+ gint *timeout)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ gboolean retval;
+
+ /* The prepare stage is the stage before the main loop starts polling
+ * and dispatching events. The autorelease poll is drained here for
+ * the preceding main loop iteration or, in case of the first iteration,
+ * for the operations carried out between event loop initialization and
+ * this first iteration.
+ *
+ * The autorelease poll must only be drained when the following conditions
+ * apply:
+ * - We are at the base CFRunLoop level (indicated by current_loop_level),
+ * - We are at the base g_main_loop level (indicated by
+ * g_main_depth())
+ * - We are at the base poll_func level (indicated by getting events).
+ *
+ * Messing with the autorelease pool at any level of nesting can cause access
+ * to deallocated memory because autorelease_pool is static and releasing a
+ * pool will cause all pools allocated inside of it to be released as well.
+ */
+ if (current_loop_level == 0 && g_main_depth() == 0 && getting_events == 0)
+ {
+ if (autorelease_pool)
+ [autorelease_pool drain];
+
+ autorelease_pool = [[NSAutoreleasePool alloc] init];
+ }
+
+ *timeout = -1;
+
+ if (event_source->display->event_pause_count > 0)
+ retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+ else
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
+
+ return retval;
+}
+
+static gboolean
+gdk_macos_event_source_check (GSource *source)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ gboolean retval;
+
+ if (event_source->display->event_pause_count > 0)
+ retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+ else
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
+
+ return retval;
+}
+
+static gboolean
+gdk_macos_event_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ GdkEvent *event;
+
+ _gdk_macos_display_queue_events (GDK_MACOS_DISPLAY (event_source->display));
+
+ event = _gdk_event_unqueue (event_source->display);
+
+ if (event)
+ {
+ _gdk_event_emit (event);
+
+ gdk_event_unref (event);
+ }
+
+ return TRUE;
+}
+
+static void
+gdk_macos_event_source_finalize (GSource *source)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+
+ g_clear_object (&event_source->display);
+}
+
+static GSourceFuncs event_funcs = {
+ gdk_macos_event_source_prepare,
+ gdk_macos_event_source_check,
+ gdk_macos_event_source_dispatch,
+ gdk_macos_event_source_finalize,
+};
+
+/************************************************************
+ ********* Our Poll Function *********
+ ************************************************************/
+
+static gint
+poll_func (GPollFD *ufds,
+ guint nfds,
+ gint timeout_)
+{
+ NSEvent *event;
+ NSDate *limit_date;
+ gint n_ready;
+
+ static GPollFD *last_ufds;
+
+ last_ufds = ufds;
+
+ n_ready = select_thread_start_poll (ufds, nfds, timeout_);
+ if (n_ready > 0)
+ timeout_ = 0;
+
+ if (timeout_ == -1)
+ limit_date = [NSDate distantFuture];
+ else if (timeout_ == 0)
+ limit_date = [NSDate distantPast];
+ else
+ limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+
+ getting_events++;
+ event = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: limit_date
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ getting_events--;
+
+ /* We check if last_ufds did not change since the time this function was
+ * called. It is possible that a recursive main loop (and thus recursive
+ * invocation of this poll function) is triggered while in
+ * nextEventMatchingMask:. If during that time new fds are added,
+ * the cached fds array might be replaced in g_main_context_iterate().
+ * So, we should avoid accessing the old fd array (still pointed at by
+ * ufds) here in that case, since it might have been freed. We avoid this
+ * by not calling the collect stage.
+ */
+ if (last_ufds == ufds && n_ready < 0)
+ n_ready = select_thread_collect_poll (ufds, nfds);
+
+ if (event &&
+ [event type] == NSEventTypeApplicationDefined &&
+ [event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP)
+ {
+ /* Just used to wake us up; if an event and a FD arrived at the same
+ * time; could have come from a previous iteration in some cases,
+ * but the spurious wake up is harmless if a little inefficient.
+ */
+ event = NULL;
+ }
+
+ if (event)
+ {
+ if (!current_events)
+ current_events = g_queue_new ();
+ g_queue_push_head (current_events, [event retain]);
+ }
+
+ return n_ready;
+}
+
+/************************************************************
+ ********* Running the main loop out of CFRunLoop *********
+ ************************************************************/
+
+/* Wrapper around g_main_context_query() that handles reallocating
+ * run_loop_pollfds up to the proper size
+ */
+static gint
+query_main_context (GMainContext *context,
+ int max_priority,
+ int *timeout)
+{
+ gint nfds;
+
+ if (!run_loop_pollfds)
+ {
+ run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
+ run_loop_pollfds = g_new (GPollFD, run_loop_pollfds_size);
+ }
+
+ while ((nfds = g_main_context_query (context, max_priority, timeout,
+ run_loop_pollfds,
+ run_loop_pollfds_size)) > run_loop_pollfds_size)
+ {
+ g_free (run_loop_pollfds);
+ run_loop_pollfds_size = nfds;
+ run_loop_pollfds = g_new (GPollFD, nfds);
+ }
+
+ return nfds;
+}
+
+static void
+run_loop_entry (void)
+{
+ if (acquired_loop_level == -1)
+ {
+ if (g_main_context_acquire (NULL))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Beginning tracking run loop activity"));
+ acquired_loop_level = current_loop_level;
+ }
+ else
+ {
+ /* If we fail to acquire the main context, that means someone is iterating
+ * the main context in a different thread; we simply wait until this loop
+ * exits and then try again at next entry. In general, iterating the loop
+ * from a different thread is rare: it is only possible when GDK threading
+ * is initialized and is not frequently used even then. So, we hope that
+ * having GLib main loop iteration blocked in the combination of that and
+ * a native modal operation is a minimal problem. We could imagine using a
+ * thread that does g_main_context_wait() and then wakes us back up, but
+ * the gain doesn't seem worth the complexity.
+ */
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Can't acquire main loop; skipping tracking run loop
activity"));
+ }
+ }
+}
+
+static void
+run_loop_before_timers (void)
+{
+}
+
+static void
+run_loop_before_sources (void)
+{
+ GMainContext *context = g_main_context_default ();
+ gint max_priority;
+ gint nfds;
+
+ /* Before we let the CFRunLoop process sources, we want to check if there
+ * are any pending GLib main loop sources more urgent than
+ * G_PRIORITY_DEFAULT that need to be dispatched. (We consider all activity
+ * from the CFRunLoop to have a priority of G_PRIORITY_DEFAULT.) If no
+ * sources are processed by the CFRunLoop, then processing will continue
+ * on to the BeforeWaiting stage where we check for lower priority sources.
+ */
+
+ g_main_context_prepare (context, &max_priority);
+ max_priority = MIN (max_priority, G_PRIORITY_DEFAULT);
+
+ /* We ignore the timeout that query_main_context () returns since we'll
+ * always query again before waiting.
+ */
+ nfds = query_main_context (context, max_priority, NULL);
+
+ if (nfds)
+ old_poll_func (run_loop_pollfds, nfds, 0);
+
+ if (g_main_context_check (context, max_priority, run_loop_pollfds, nfds))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching high priority sources"));
+ g_main_context_dispatch (context);
+ }
+}
+
+static void
+dummy_timer_callback (CFRunLoopTimerRef timer,
+ void *info)
+{
+ /* Nothing; won't normally even be called */
+}
+
+static void
+run_loop_before_waiting (void)
+{
+ GMainContext *context = g_main_context_default ();
+ gint timeout;
+ gint n_ready;
+
+ /* At this point, the CFRunLoop is ready to wait. We start a GMain loop
+ * iteration by calling the check() and query() stages. We start a
+ * poll, and if it doesn't complete immediately we let the run loop
+ * go ahead and sleep. Before doing that, if there was a timeout from
+ * GLib, we set up a CFRunLoopTimer to wake us up.
+ */
+
+ g_main_context_prepare (context, &run_loop_max_priority);
+
+ run_loop_n_pollfds = query_main_context (context, run_loop_max_priority, &timeout);
+
+ n_ready = select_thread_start_poll (run_loop_pollfds, run_loop_n_pollfds, timeout);
+
+ if (n_ready > 0 || timeout == 0)
+ {
+ /* We have stuff to do, no sleeping allowed! */
+ CFRunLoopWakeUp (main_thread_run_loop);
+ }
+ else if (timeout > 0)
+ {
+ /* We need to get the run loop to break out of its wait when our timeout
+ * expires. We do this by adding a dummy timer that we'll remove immediately
+ * after the wait wakes up.
+ */
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Adding timer to wake us up in %d milliseconds", timeout));
+
+ run_loop_timer = CFRunLoopTimerCreate (NULL, /* allocator */
+ CFAbsoluteTimeGetCurrent () + timeout / 1000.,
+ 0, /* interval (0=does not repeat) */
+ 0, /* flags */
+ 0, /* order (priority) */
+ dummy_timer_callback,
+ NULL);
+
+ CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+ }
+
+ run_loop_polling_async = n_ready < 0;
+}
+
+static void
+run_loop_after_waiting (void)
+{
+ GMainContext *context = g_main_context_default ();
+
+ /* After sleeping, we finish of the GMain loop iteratin started in before_waiting()
+ * by doing the check() and dispatch() stages.
+ */
+
+ if (run_loop_timer)
+ {
+ CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+ CFRelease (run_loop_timer);
+ run_loop_timer = NULL;
+ }
+
+ if (run_loop_polling_async)
+ {
+ select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds);
+ run_loop_polling_async = FALSE;
+ }
+
+ if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching after waiting"));
+ g_main_context_dispatch (context);
+ }
+}
+
+static void
+run_loop_exit (void)
+{
+ /* + 1 because we decrement current_loop_level separately in observer_callback() */
+ if ((current_loop_level + 1) == acquired_loop_level)
+ {
+ g_main_context_release (NULL);
+ acquired_loop_level = -1;
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Ended tracking run loop activity"));
+ }
+}
+
+static void
+run_loop_observer_callback (CFRunLoopObserverRef observer,
+ CFRunLoopActivity activity,
+ void *info)
+{
+ switch (activity)
+ {
+ case kCFRunLoopEntry:
+ current_loop_level++;
+ break;
+ case kCFRunLoopExit:
+ g_return_if_fail (current_loop_level > 0);
+ current_loop_level--;
+ break;
+ case kCFRunLoopBeforeTimers:
+ case kCFRunLoopBeforeSources:
+ case kCFRunLoopBeforeWaiting:
+ case kCFRunLoopAfterWaiting:
+ case kCFRunLoopAllActivities:
+ default:
+ break;
+ }
+
+ if (getting_events > 0) /* Activity we triggered */
+ return;
+
+ switch (activity)
+ {
+ case kCFRunLoopEntry:
+ run_loop_entry ();
+ break;
+ case kCFRunLoopBeforeTimers:
+ run_loop_before_timers ();
+ break;
+ case kCFRunLoopBeforeSources:
+ run_loop_before_sources ();
+ break;
+ case kCFRunLoopBeforeWaiting:
+ run_loop_before_waiting ();
+ break;
+ case kCFRunLoopAfterWaiting:
+ run_loop_after_waiting ();
+ break;
+ case kCFRunLoopExit:
+ run_loop_exit ();
+ break;
+ case kCFRunLoopAllActivities:
+ /* TODO: Do most of the above? */
+ default:
+ break;
+ }
+}
+
+/************************************************************/
+
+GSource *
+_gdk_macos_event_source_new (GdkMacosDisplay *display)
+{
+ CFRunLoopObserverRef observer;
+ GdkMacosEventSource *event_source;
+ GSource *source;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ /* Hook into the GLib main loop */
+
+ event_poll_fd.events = G_IO_IN;
+ event_poll_fd.fd = -1;
+
+ source = g_source_new (&event_funcs, sizeof (GdkMacosEventSource));
+ g_source_set_name (source, "GDK Quartz event source");
+ g_source_add_poll (source, &event_poll_fd);
+ g_source_set_priority (source, GDK_PRIORITY_EVENTS);
+ g_source_set_can_recurse (source, TRUE);
+
+ old_poll_func = g_main_context_get_poll_func (NULL);
+ g_main_context_set_poll_func (NULL, poll_func);
+
+ event_source = (GdkMacosEventSource *)source;
+ event_source->display = g_object_ref (GDK_DISPLAY (display));
+
+ /* Hook into the the CFRunLoop for the main thread */
+
+ main_thread_run_loop = CFRunLoopGetCurrent ();
+
+ observer = CFRunLoopObserverCreate (NULL, /* default allocator */
+ kCFRunLoopAllActivities,
+ true, /* repeats: not one-shot */
+ 0, /* order (priority) */
+ run_loop_observer_callback,
+ NULL);
+
+ CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
+
+ /* Initialize our autorelease pool */
+ autorelease_pool = [[NSAutoreleasePool alloc] init];
+
+ return source;
+}
diff --git a/gdk/macos/gdkmacoskeymap-private.h b/gdk/macos/gdkmacoskeymap-private.h
new file mode 100644
index 0000000000..34ff21b225
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_KEYMAP_PRIVATE_H__
+#define __GDK_MACOS_KEYMAP_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacoskeymap.h"
+
+G_BEGIN_DECLS
+
+GdkMacosKeymap *_gdk_macos_keymap_new (GdkMacosDisplay *display);
+GdkEventType _gdk_macos_keymap_get_event_type (NSEvent *event);
+gboolean _gdk_macos_keymap_is_modifier (guint keycode);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_KEYMAP_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoskeymap.c b/gdk/macos/gdkmacoskeymap.c
new file mode 100644
index 0000000000..c638c75179
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap.c
@@ -0,0 +1,698 @@
+/*
+ * Copyright © 2000-2020 Red Hat, Inc.
+ * Copyright © 2005 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+/* Some parts of this code come from quartzKeyboard.c,
+ * from the Apple X11 Server.
+ *
+ * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
+ * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name(s) of the above
+ * copyright holders shall not be used in advertising or otherwise to
+ * promote the sale, use or other dealings in this Software without
+ * prior written authorization.
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+#include <Carbon/Carbon.h>
+#include <gdk/gdk.h>
+
+#include "gdkkeysprivate.h"
+#include "gdkkeysyms.h"
+#include "gdkmacoskeymap-private.h"
+
+struct _GdkMacosKeymap
+{
+ GdkKeymap parent_instance;
+};
+
+struct _GdkMacosKeymapClass
+{
+ GdkKeymapClass parent_instance;
+};
+
+G_DEFINE_TYPE (GdkMacosKeymap, gdk_macos_keymap, GDK_TYPE_KEYMAP)
+
+/* This is a table of all keyvals. Each keycode gets KEYVALS_PER_KEYCODE entries.
+ * TThere is 1 keyval per modifier (Nothing, Shift, Alt, Shift+Alt);
+ */
+static guint *keyval_array = NULL;
+
+#define NUM_KEYCODES 128
+#define KEYVALS_PER_KEYCODE 4
+#define GET_KEYVAL(keycode, group, level) \
+ (keyval_array[(keycode * KEYVALS_PER_KEYCODE + group * 2 + level)])
+
+const static struct {
+ guint keycode;
+ guint keyval;
+ unsigned int modmask; /* So we can tell when a mod key is pressed/released */
+} modifier_keys[] = {
+ { 54, GDK_KEY_Meta_R, NSEventModifierFlagCommand },
+ { 55, GDK_KEY_Meta_L, NSEventModifierFlagCommand },
+ { 56, GDK_KEY_Shift_L, NSEventModifierFlagShift },
+ { 57, GDK_KEY_Caps_Lock, NSEventModifierFlagCapsLock },
+ { 58, GDK_KEY_Alt_L, NSEventModifierFlagOption },
+ { 59, GDK_KEY_Control_L, NSEventModifierFlagControl },
+ { 60, GDK_KEY_Shift_R, NSEventModifierFlagShift },
+ { 61, GDK_KEY_Alt_R, NSEventModifierFlagOption },
+ { 62, GDK_KEY_Control_R, NSEventModifierFlagControl }
+};
+
+const static struct {
+ guint keycode;
+ guint keyval;
+} function_keys[] = {
+ { 122, GDK_KEY_F1 },
+ { 120, GDK_KEY_F2 },
+ { 99, GDK_KEY_F3 },
+ { 118, GDK_KEY_F4 },
+ { 96, GDK_KEY_F5 },
+ { 97, GDK_KEY_F6 },
+ { 98, GDK_KEY_F7 },
+ { 100, GDK_KEY_F8 },
+ { 101, GDK_KEY_F9 },
+ { 109, GDK_KEY_F10 },
+ { 103, GDK_KEY_F11 },
+ { 111, GDK_KEY_F12 },
+ { 105, GDK_KEY_F13 },
+ { 107, GDK_KEY_F14 },
+ { 113, GDK_KEY_F15 },
+ { 106, GDK_KEY_F16 }
+};
+
+const static struct {
+ guint keycode;
+ guint normal_keyval, keypad_keyval;
+} known_numeric_keys[] = {
+ { 65, GDK_KEY_period, GDK_KEY_KP_Decimal },
+ { 67, GDK_KEY_asterisk, GDK_KEY_KP_Multiply },
+ { 69, GDK_KEY_plus, GDK_KEY_KP_Add },
+ { 75, GDK_KEY_slash, GDK_KEY_KP_Divide },
+ { 76, GDK_KEY_Return, GDK_KEY_KP_Enter },
+ { 78, GDK_KEY_minus, GDK_KEY_KP_Subtract },
+ { 81, GDK_KEY_equal, GDK_KEY_KP_Equal },
+ { 82, GDK_KEY_0, GDK_KEY_KP_0 },
+ { 83, GDK_KEY_1, GDK_KEY_KP_1 },
+ { 84, GDK_KEY_2, GDK_KEY_KP_2 },
+ { 85, GDK_KEY_3, GDK_KEY_KP_3 },
+ { 86, GDK_KEY_4, GDK_KEY_KP_4 },
+ { 87, GDK_KEY_5, GDK_KEY_KP_5 },
+ { 88, GDK_KEY_6, GDK_KEY_KP_6 },
+ { 89, GDK_KEY_7, GDK_KEY_KP_7 },
+ { 91, GDK_KEY_8, GDK_KEY_KP_8 },
+ { 92, GDK_KEY_9, GDK_KEY_KP_9 }
+};
+
+/* These values aren't covered by gdk_unicode_to_keyval */
+const static struct {
+ gunichar ucs_value;
+ guint keyval;
+} special_ucs_table [] = {
+ { 0x0001, GDK_KEY_Home },
+ { 0x0003, GDK_KEY_Return },
+ { 0x0004, GDK_KEY_End },
+ { 0x0008, GDK_KEY_BackSpace },
+ { 0x0009, GDK_KEY_Tab },
+ { 0x000b, GDK_KEY_Page_Up },
+ { 0x000c, GDK_KEY_Page_Down },
+ { 0x000d, GDK_KEY_Return },
+ { 0x001b, GDK_KEY_Escape },
+ { 0x001c, GDK_KEY_Left },
+ { 0x001d, GDK_KEY_Right },
+ { 0x001e, GDK_KEY_Up },
+ { 0x001f, GDK_KEY_Down },
+ { 0x007f, GDK_KEY_Delete },
+ { 0xf027, GDK_KEY_dead_acute },
+ { 0xf060, GDK_KEY_dead_grave },
+ { 0xf300, GDK_KEY_dead_grave },
+ { 0xf0b4, GDK_KEY_dead_acute },
+ { 0xf301, GDK_KEY_dead_acute },
+ { 0xf385, GDK_KEY_dead_acute },
+ { 0xf05e, GDK_KEY_dead_circumflex },
+ { 0xf2c6, GDK_KEY_dead_circumflex },
+ { 0xf302, GDK_KEY_dead_circumflex },
+ { 0xf07e, GDK_KEY_dead_tilde },
+ { 0xf2dc, GDK_KEY_dead_tilde },
+ { 0xf303, GDK_KEY_dead_tilde },
+ { 0xf342, GDK_KEY_dead_perispomeni },
+ { 0xf0af, GDK_KEY_dead_macron },
+ { 0xf304, GDK_KEY_dead_macron },
+ { 0xf2d8, GDK_KEY_dead_breve },
+ { 0xf306, GDK_KEY_dead_breve },
+ { 0xf2d9, GDK_KEY_dead_abovedot },
+ { 0xf307, GDK_KEY_dead_abovedot },
+ { 0xf0a8, GDK_KEY_dead_diaeresis },
+ { 0xf308, GDK_KEY_dead_diaeresis },
+ { 0xf2da, GDK_KEY_dead_abovering },
+ { 0xf30A, GDK_KEY_dead_abovering },
+ { 0xf022, GDK_KEY_dead_doubleacute },
+ { 0xf2dd, GDK_KEY_dead_doubleacute },
+ { 0xf30B, GDK_KEY_dead_doubleacute },
+ { 0xf2c7, GDK_KEY_dead_caron },
+ { 0xf30C, GDK_KEY_dead_caron },
+ { 0xf0be, GDK_KEY_dead_cedilla },
+ { 0xf327, GDK_KEY_dead_cedilla },
+ { 0xf2db, GDK_KEY_dead_ogonek },
+ { 0xf328, GDK_KEY_dead_ogonek },
+ { 0xfe5d, GDK_KEY_dead_iota },
+ { 0xf323, GDK_KEY_dead_belowdot },
+ { 0xf309, GDK_KEY_dead_hook },
+ { 0xf31B, GDK_KEY_dead_horn },
+ { 0xf02d, GDK_KEY_dead_stroke },
+ { 0xf335, GDK_KEY_dead_stroke },
+ { 0xf336, GDK_KEY_dead_stroke },
+ { 0xf313, GDK_KEY_dead_abovecomma },
+ /* { 0xf313, GDK_KEY_dead_psili }, */
+ { 0xf314, GDK_KEY_dead_abovereversedcomma },
+ /* { 0xf314, GDK_KEY_dead_dasia }, */
+ { 0xf30F, GDK_KEY_dead_doublegrave },
+ { 0xf325, GDK_KEY_dead_belowring },
+ { 0xf2cd, GDK_KEY_dead_belowmacron },
+ { 0xf331, GDK_KEY_dead_belowmacron },
+ { 0xf32D, GDK_KEY_dead_belowcircumflex },
+ { 0xf330, GDK_KEY_dead_belowtilde },
+ { 0xf32E, GDK_KEY_dead_belowbreve },
+ { 0xf324, GDK_KEY_dead_belowdiaeresis },
+ { 0xf311, GDK_KEY_dead_invertedbreve },
+ { 0xf02c, GDK_KEY_dead_belowcomma },
+ { 0xf326, GDK_KEY_dead_belowcomma }
+};
+
+static void
+gdk_macos_keymap_update (GdkMacosKeymap *self)
+{
+ const void *chr_data = NULL;
+ guint *p;
+ int i;
+
+ TISInputSourceRef new_layout = TISCopyCurrentKeyboardLayoutInputSource ();
+ CFDataRef layout_data_ref;
+
+ g_free (keyval_array);
+ keyval_array = g_new0 (guint, NUM_KEYCODES * KEYVALS_PER_KEYCODE);
+
+ layout_data_ref = (CFDataRef)TISGetInputSourceProperty (new_layout, kTISPropertyUnicodeKeyLayoutData);
+
+ if (layout_data_ref)
+ chr_data = CFDataGetBytePtr (layout_data_ref);
+
+ if (chr_data == NULL)
+ {
+ g_error ("cannot get keyboard layout data");
+ return;
+ }
+
+ for (i = 0; i < NUM_KEYCODES; i++)
+ {
+ int j;
+ UInt32 modifiers[] = {0, shiftKey, optionKey, shiftKey | optionKey};
+ UniChar chars[4];
+ UniCharCount nChars;
+
+ p = keyval_array + i * KEYVALS_PER_KEYCODE;
+
+ for (j = 0; j < KEYVALS_PER_KEYCODE; j++)
+ {
+ UInt32 state = 0;
+ OSStatus err;
+ UInt16 key_code;
+ UniChar uc;
+
+ key_code = modifiers[j] | i;
+ err = UCKeyTranslate (chr_data, i, kUCKeyActionDisplay,
+ (modifiers[j] >> 8) & 0xFF,
+ LMGetKbdType(),
+ 0,
+ &state, 4, &nChars, chars);
+
+ /* FIXME: Theoretically, we can get multiple UTF-16
+ * values; we should convert them to proper unicode and
+ * figure out whether there are really keyboard layouts
+ * that give us more than one character for one
+ * keypress.
+ */
+ if (err == noErr && nChars == 1)
+ {
+ int k;
+ gboolean found = FALSE;
+
+ /* A few <Shift><Option>keys return two characters,
+ * the first of which is U+00a0, which isn't
+ * interesting; so we return the second. More
+ * sophisticated handling is the job of a
+ * GtkIMContext.
+ *
+ * If state isn't zero, it means that it's a dead
+ * key of some sort. Some of those are enumerated in
+ * the special_ucs_table with the high nibble set to
+ * f to push it into the private use range. Here we
+ * do the same.
+ */
+ if (state != 0)
+ chars[nChars - 1] |= 0xf000;
+ uc = chars[nChars - 1];
+
+ for (k = 0; k < G_N_ELEMENTS (special_ucs_table); k++)
+ {
+ if (special_ucs_table[k].ucs_value == uc)
+ {
+ p[j] = special_ucs_table[k].keyval;
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* Special-case shift-tab since GTK+ expects
+ * GDK_KEY_ISO_Left_Tab for that.
+ */
+ if (found && p[j] == GDK_KEY_Tab && modifiers[j] == shiftKey)
+ p[j] = GDK_KEY_ISO_Left_Tab;
+
+ if (!found)
+ p[j] = gdk_unicode_to_keyval (uc);
+ }
+ }
+
+ if (p[3] == p[2])
+ p[3] = 0;
+ if (p[2] == p[1])
+ p[2] = 0;
+ if (p[1] == p[0])
+ p[1] = 0;
+ if (p[0] == p[2] &&
+ p[1] == p[3])
+ p[2] = p[3] = 0;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+ {
+ p = keyval_array + modifier_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+ if (p[0] == 0 && p[1] == 0 &&
+ p[2] == 0 && p[3] == 0)
+ p[0] = modifier_keys[i].keyval;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (function_keys); i++)
+ {
+ p = keyval_array + function_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+ p[0] = function_keys[i].keyval;
+ p[1] = p[2] = p[3] = 0;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (known_numeric_keys); i++)
+ {
+ p = keyval_array + known_numeric_keys[i].keycode * KEYVALS_PER_KEYCODE;
+
+ if (p[0] == known_numeric_keys[i].normal_keyval)
+ p[0] = known_numeric_keys[i].keypad_keyval;
+ }
+
+ g_signal_emit_by_name (self, "keys-changed");
+}
+
+static PangoDirection
+gdk_macos_keymap_get_direction (GdkKeymap *keymap)
+{
+ return PANGO_DIRECTION_NEUTRAL;
+}
+
+static gboolean
+gdk_macos_keymap_have_bidi_layouts (GdkKeymap *keymap)
+{
+ return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_caps_lock_state (GdkKeymap *keymap)
+{
+ return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_num_lock_state (GdkKeymap *keymap)
+{
+ return FALSE;
+}
+
+static gboolean
+gdk_macos_keymap_get_scroll_lock_state (GdkKeymap *keymap)
+{
+ return FALSE;
+}
+
+static guint
+gdk_macos_keymap_lookup_key (GdkKeymap *keymap,
+ const GdkKeymapKey *key)
+{
+ GdkMacosKeymap *self = (GdkMacosKeymap *)keymap;
+
+ g_assert (GDK_IS_MACOS_KEYMAP (self));
+ g_assert (key != NULL);
+
+ return GET_KEYVAL (key->keycode, key->group, key->level);
+}
+
+static guint
+translate_keysym (guint hardware_keycode,
+ gint group,
+ GdkModifierType state,
+ gint *effective_group,
+ gint *effective_level)
+{
+ gint level;
+ guint tmp_keyval;
+
+ level = (state & GDK_SHIFT_MASK) ? 1 : 0;
+
+ if (!(GET_KEYVAL (hardware_keycode, group, 0) || GET_KEYVAL (hardware_keycode, group, 1)) &&
+ (GET_KEYVAL (hardware_keycode, 0, 0) || GET_KEYVAL (hardware_keycode, 0, 1)))
+ group = 0;
+
+ if (!GET_KEYVAL (hardware_keycode, group, level) &&
+ GET_KEYVAL (hardware_keycode, group, 0))
+ level = 0;
+
+ tmp_keyval = GET_KEYVAL (hardware_keycode, group, level);
+
+ if (state & GDK_LOCK_MASK)
+ {
+ guint upper = gdk_keyval_to_upper (tmp_keyval);
+ if (upper != tmp_keyval)
+ tmp_keyval = upper;
+ }
+
+ if (effective_group)
+ *effective_group = group;
+ if (effective_level)
+ *effective_level = level;
+
+ return tmp_keyval;
+}
+
+static gboolean
+gdk_macos_keymap_get_entries_for_keyval (GdkKeymap *keymap,
+ guint keyval,
+ GArray *keys)
+{
+ gboolean ret = FALSE;
+
+ g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+ g_assert (keys != NULL);
+
+ for (guint i = 0; i < NUM_KEYCODES * KEYVALS_PER_KEYCODE; i++)
+ {
+ GdkKeymapKey key;
+
+ if (keyval_array[i] != keyval)
+ continue;
+
+ key.keycode = i / KEYVALS_PER_KEYCODE;
+ key.group = (i % KEYVALS_PER_KEYCODE) >= 2;
+ key.level = i % 2;
+
+ g_array_append_val (keys, key);
+
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+static gboolean
+gdk_macos_keymap_get_entries_for_keycode (GdkKeymap *keymap,
+ guint hardware_keycode,
+ GdkKeymapKey **keys,
+ guint **keyvals,
+ gint *n_entries)
+{
+ GArray *keys_array;
+ GArray *keyvals_array;
+ guint *p;
+ guint i;
+
+ g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+ g_assert (keyvals != NULL);
+ g_assert (n_entries != NULL);
+
+ *keyvals = NULL;
+ *n_entries = 0;
+
+ if (hardware_keycode > NUM_KEYCODES)
+ return FALSE;
+
+ if (keys)
+ keys_array = g_array_new (FALSE, FALSE, sizeof (GdkKeymapKey));
+ else
+ keys_array = NULL;
+
+ if (keyvals)
+ keyvals_array = g_array_new (FALSE, FALSE, sizeof (guint));
+ else
+ keyvals_array = NULL;
+
+ p = keyval_array + hardware_keycode * KEYVALS_PER_KEYCODE;
+
+ for (i = 0; i < KEYVALS_PER_KEYCODE; i++)
+ {
+ if (!p[i])
+ continue;
+
+ (*n_entries)++;
+
+ if (keyvals_array)
+ g_array_append_val (keyvals_array, p[i]);
+
+ if (keys_array)
+ {
+ GdkKeymapKey key;
+
+ key.keycode = hardware_keycode;
+ key.group = i >= 2;
+ key.level = i % 2;
+
+ g_array_append_val (keys_array, key);
+ }
+ }
+
+ if (keys)
+ *keys = (GdkKeymapKey *)(gpointer)g_array_free (keys_array, FALSE);
+
+ if (keyvals)
+ *keyvals = (guint *)(gpointer)g_array_free (keyvals_array, FALSE);
+
+ return *n_entries > 0;
+}
+
+static gboolean
+gdk_macos_keymap_translate_keyboard_state (GdkKeymap *keymap,
+ guint hardware_keycode,
+ GdkModifierType state,
+ gint group,
+ guint *keyval,
+ gint *effective_group,
+ gint *level,
+ GdkModifierType *consumed_modifiers)
+{
+ guint tmp_keyval;
+ GdkModifierType bit;
+
+ g_assert (GDK_IS_MACOS_KEYMAP (keymap));
+
+ if (keyval)
+ *keyval = 0;
+ if (effective_group)
+ *effective_group = 0;
+ if (level)
+ *level = 0;
+ if (consumed_modifiers)
+ *consumed_modifiers = 0;
+
+ if (hardware_keycode < 0 || hardware_keycode >= NUM_KEYCODES)
+ return FALSE;
+
+ tmp_keyval = translate_keysym (hardware_keycode, group, state, level, effective_group);
+
+ /* Check if modifiers modify the keyval */
+ if (consumed_modifiers)
+ {
+ guint tmp_modifiers = (state & GDK_MODIFIER_MASK);
+
+ for (bit = 1; bit <= tmp_modifiers; bit <<= 1)
+ {
+ if ((bit & tmp_modifiers) &&
+ translate_keysym (hardware_keycode, group, state & ~bit,
+ NULL, NULL) == tmp_keyval)
+ tmp_modifiers &= ~bit;
+ }
+
+ *consumed_modifiers = tmp_modifiers;
+ }
+
+ if (keyval)
+ *keyval = tmp_keyval;
+
+ return TRUE;
+}
+
+static void
+input_sources_changed_notification (CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ GdkMacosKeymap *self = observer;
+
+ g_assert (GDK_IS_MACOS_KEYMAP (self));
+
+ gdk_macos_keymap_update (self);
+}
+
+static void
+gdk_macos_keymap_finalize (GObject *object)
+{
+ CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+ object,
+ CFSTR ("AppleSelectedInputSourcesChangedNotification"),
+ NULL);
+
+ G_OBJECT_CLASS (gdk_macos_keymap_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_keymap_class_init (GdkMacosKeymapClass *klass)
+{
+ GdkKeymapClass *keymap_class = GDK_KEYMAP_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gdk_macos_keymap_finalize;
+
+ keymap_class->get_caps_lock_state = gdk_macos_keymap_get_caps_lock_state;
+ keymap_class->get_direction = gdk_macos_keymap_get_direction;
+ keymap_class->get_entries_for_keycode = gdk_macos_keymap_get_entries_for_keycode;
+ keymap_class->get_entries_for_keyval = gdk_macos_keymap_get_entries_for_keyval;
+ keymap_class->get_num_lock_state = gdk_macos_keymap_get_num_lock_state;
+ keymap_class->get_scroll_lock_state = gdk_macos_keymap_get_scroll_lock_state;
+ keymap_class->have_bidi_layouts = gdk_macos_keymap_have_bidi_layouts;
+ keymap_class->lookup_key = gdk_macos_keymap_lookup_key;
+ keymap_class->translate_keyboard_state = gdk_macos_keymap_translate_keyboard_state;
+}
+
+static void
+gdk_macos_keymap_init (GdkMacosKeymap *self)
+{
+ CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ input_sources_changed_notification,
+ CFSTR ("AppleSelectedInputSourcesChangedNotification"),
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ gdk_macos_keymap_update (self);
+}
+
+GdkMacosKeymap *
+_gdk_macos_keymap_new (GdkMacosDisplay *display)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ return g_object_new (GDK_TYPE_MACOS_KEYMAP,
+ "display", display,
+ NULL);
+}
+
+/* What sort of key event is this? Returns one of
+ * GDK_KEY_PRESS, GDK_KEY_RELEASE, GDK_NOTHING (should be ignored)
+ */
+GdkEventType
+_gdk_macos_keymap_get_event_type (NSEvent *event)
+{
+ unsigned short keycode;
+ unsigned int flags;
+
+ switch ((int)[event type])
+ {
+ case NSEventTypeKeyDown:
+ return GDK_KEY_PRESS;
+ case NSEventTypeKeyUp:
+ return GDK_KEY_RELEASE;
+ case NSEventTypeFlagsChanged:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* For flags-changed events, we have to find the special key that caused the
+ * event, and see if it's in the modifier mask. */
+ keycode = [event keyCode];
+ flags = [event modifierFlags];
+
+ for (guint i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+ {
+ if (modifier_keys[i].keycode == keycode)
+ {
+ if (flags & modifier_keys[i].modmask)
+ return GDK_KEY_PRESS;
+ else
+ return GDK_KEY_RELEASE;
+ }
+ }
+
+ /* Some keypresses (eg: Expose' activations) seem to trigger flags-changed
+ * events for no good reason. Ignore them! */
+ return 0;
+}
+
+gboolean
+_gdk_macos_keymap_is_modifier (guint keycode)
+{
+ for (guint i = 0; i < G_N_ELEMENTS (modifier_keys); i++)
+ {
+ if (modifier_keys[i].modmask == 0)
+ break;
+
+ if (modifier_keys[i].keycode == keycode)
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/gdk/macos/gdkmacoskeymap.h b/gdk/macos/gdkmacoskeymap.h
new file mode 100644
index 0000000000..6a8a7e288f
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_KEYMAP_H__
+#define __GDK_MACOS_KEYMAP_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosKeymap GdkMacosKeymap;
+typedef struct _GdkMacosKeymapClass GdkMacosKeymapClass;
+
+#define GDK_TYPE_MACOS_KEYMAP (gdk_macos_keymap_get_type())
+#define GDK_MACOS_KEYMAP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_KEYMAP,
GdkMacosKeymap))
+#define GDK_IS_MACOS_KEYMAP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_KEYMAP))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_keymap_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_KEYMAP_H__ */
diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h
new file mode 100644
index 0000000000..3c6f058bd0
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor-private.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_MONITOR_PRIVATE_H__
+#define __GDK_MACOS_MONITOR_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacosmonitor.h"
+
+#include "gdkmonitorprivate.h"
+
+G_BEGIN_DECLS
+
+GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display,
+ CGDirectDisplayID screen_id);
+CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self);
+gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_MONITOR_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c
new file mode 100644
index 0000000000..4bb3c195c2
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdk.h>
+#include <math.h>
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosMonitor
+{
+ GdkMonitor parent_instance;
+ CGDirectDisplayID screen_id;
+ guint has_opengl : 1;
+};
+
+struct _GdkMacosMonitorClass
+{
+ GdkMonitorClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosMonitor, gdk_macos_monitor, GDK_TYPE_MONITOR)
+
+static void
+gdk_macos_monitor_get_workarea (GdkMonitor *monitor,
+ GdkRectangle *geometry)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosMonitor *self = (GdkMacosMonitor *)monitor;
+
+ g_assert (GDK_IS_MACOS_MONITOR (self));
+ g_assert (geometry != NULL);
+
+ *geometry = monitor->geometry;
+
+ for (id obj in [NSScreen screens])
+ {
+ CGDirectDisplayID screen_id;
+
+ screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+
+ if (screen_id == self->screen_id)
+ {
+ NSScreen *screen = (NSScreen *)obj;
+ NSRect visibleFrame = [screen visibleFrame];
+ int x = visibleFrame.origin.x;
+ int y = visibleFrame.origin.y + visibleFrame.size.height;
+
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (monitor->display),
+ x, y, &x, &y);
+
+ geometry->x = x;
+ geometry->y = y;
+ geometry->width = visibleFrame.size.width;
+ geometry->height = visibleFrame.size.height;
+
+ break;
+ }
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_monitor_class_init (GdkMacosMonitorClass *klass)
+{
+ GdkMonitorClass *monitor_class = GDK_MONITOR_CLASS (klass);
+
+ monitor_class->get_workarea = gdk_macos_monitor_get_workarea;
+}
+
+static void
+gdk_macos_monitor_init (GdkMacosMonitor *self)
+{
+}
+
+static GdkSubpixelLayout
+GetSubpixelLayout (CGDirectDisplayID screen_id)
+{
+#if 0
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkSubpixelLayout subpixel_layout = GDK_SUBPIXEL_LAYOUT_UNKNOWN;
+ io_service_t iosvc;
+ NSDictionary *dict;
+ guint layout;
+ gboolean rotation;
+
+ rotation = CGDisplayRotation (screen_id);
+ iosvc = CGDisplayIOServicePort (screen_id);
+ dict = (NSDictionary *)IODisplayCreateInfoDictionary (iosvc, kIODisplayOnlyPreferredName);
+ layout = [[dict objectForKey:@kDisplaySubPixelLayout] unsignedIntValue];
+
+ switch (layout)
+ {
+ case kDisplaySubPixelLayoutRGB:
+ if (rotation == 0.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_RGB;
+ else if (rotation == 90.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_RGB;
+ else if (rotation == 180.0 || rotation == -180.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_BGR;
+ else if (rotation == -90.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_BGR;
+ break;
+
+ case kDisplaySubPixelLayoutBGR:
+ if (rotation == 0.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_BGR;
+ else if (rotation == 90.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_BGR;
+ else if (rotation == 180.0 || rotation == -180.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_HORIZONTAL_RGB;
+ else if (rotation == -90.0 || rotation == 270.0)
+ subpixel_layout = GDK_SUBPIXEL_LAYOUT_VERTICAL_RGB;
+ break;
+
+ default:
+ break;
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return subpixel_layout;
+#endif
+
+ return GDK_SUBPIXEL_LAYOUT_UNKNOWN;
+}
+
+static char *
+GetLocalizedName (CGDirectDisplayID screen_id)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ char *name = NULL;
+
+ for (id obj in [NSScreen screens])
+ {
+ CGDirectDisplayID this_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"]
unsignedIntValue];
+
+ if (screen_id == this_id)
+ {
+ NSString *str = [(NSScreen *)obj localizedName];
+ name = g_strdup ([str UTF8String]);
+ break;
+ }
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return g_steal_pointer (&name);
+}
+
+static gchar *
+GetConnectorName (CGDirectDisplayID screen_id)
+{
+ guint unit = CGDisplayUnitNumber (screen_id);
+ return g_strdup_printf ("unit-%u", unit);
+}
+
+gboolean
+_gdk_macos_monitor_reconfigure (GdkMacosMonitor *self)
+{
+ GdkSubpixelLayout subpixel_layout;
+ CGDisplayModeRef mode;
+ GdkMacosDisplay *display;
+ GdkRectangle geom;
+ gboolean has_opengl;
+ CGSize size;
+ CGRect bounds;
+ CGRect main_bounds;
+ size_t width;
+ size_t pixel_width;
+ gchar *connector;
+ gchar *name;
+ int refresh_rate;
+ int scale_factor = 1;
+ int width_mm;
+ int height_mm;
+
+ g_return_val_if_fail (GDK_IS_MACOS_MONITOR (self), FALSE);
+
+ if (!(mode = CGDisplayCopyDisplayMode (self->screen_id)))
+ return FALSE;
+
+ size = CGDisplayScreenSize (self->screen_id);
+ bounds = CGDisplayBounds (self->screen_id);
+ main_bounds = CGDisplayBounds (CGMainDisplayID ());
+ width = CGDisplayModeGetWidth (mode);
+ pixel_width = CGDisplayModeGetPixelWidth (mode);
+ refresh_rate = CGDisplayModeGetRefreshRate (mode);
+ has_opengl = CGDisplayUsesOpenGLAcceleration (self->screen_id);
+ subpixel_layout = GetSubpixelLayout (self->screen_id);
+ name = GetLocalizedName (self->screen_id);
+ connector = GetConnectorName (self->screen_id);
+
+ if (width != 0 && pixel_width != 0)
+ scale_factor = MAX (1, pixel_width / width);
+
+ width_mm = size.width;
+ height_mm = size.height;
+
+ CGDisplayModeRelease (mode);
+
+ /* This requires that the display bounds have been
+ * updated before the monitor is reconfigured.
+ */
+ display = GDK_MACOS_DISPLAY (GDK_MONITOR (self)->display);
+ _gdk_macos_display_from_display_coords (display,
+ bounds.origin.x,
+ bounds.origin.y,
+ &geom.x, &geom.y);
+ geom.width = bounds.size.width;
+ geom.height = bounds.size.height;
+
+ gdk_monitor_set_connector (GDK_MONITOR (self), connector);
+ gdk_monitor_set_model (GDK_MONITOR (self), name);
+ gdk_monitor_set_position (GDK_MONITOR (self), geom.x, geom.y);
+ gdk_monitor_set_size (GDK_MONITOR (self), geom.width, geom.height);
+ gdk_monitor_set_physical_size (GDK_MONITOR (self), width_mm, height_mm);
+ gdk_monitor_set_scale_factor (GDK_MONITOR (self), scale_factor);
+ gdk_monitor_set_refresh_rate (GDK_MONITOR (self), refresh_rate);
+ gdk_monitor_set_subpixel_layout (GDK_MONITOR (self), GDK_SUBPIXEL_LAYOUT_UNKNOWN);
+
+ /* We might be able to use this at some point to change which GSK renderer
+ * we use for surfaces on this monitor. For example, it might be better
+ * to use cairo if we cannot use OpenGL (or it would be software) anyway.
+ * Presumably that is more common in cases where macOS is running under
+ * an emulator such as QEMU.
+ */
+ self->has_opengl = !!has_opengl;
+
+ g_free (name);
+ g_free (connector);
+
+ return TRUE;
+}
+
+GdkMacosMonitor *
+_gdk_macos_monitor_new (GdkMacosDisplay *display,
+ CGDirectDisplayID screen_id)
+{
+ GdkMacosMonitor *self;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ self = g_object_new (GDK_TYPE_MACOS_MONITOR,
+ "display", display,
+ NULL);
+
+ self->screen_id = screen_id;
+
+ _gdk_macos_monitor_reconfigure (self);
+
+ return g_steal_pointer (&self);
+}
+
+CGDirectDisplayID
+_gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_MONITOR (self), 0);
+
+ return self->screen_id;
+}
diff --git a/gdk/macos/gdkmacosmonitor.h b/gdk/macos/gdkmacosmonitor.h
new file mode 100644
index 0000000000..64b91f887e
--- /dev/null
+++ b/gdk/macos/gdkmacosmonitor.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_MONITOR_H__
+#define __GDK_MACOS_MONITOR_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosMonitor GdkMacosMonitor;
+typedef struct _GdkMacosMonitorClass GdkMacosMonitorClass;
+
+#define GDK_TYPE_MACOS_MONITOR (gdk_macos_monitor_get_type())
+#define GDK_MACOS_MONITOR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_MONITOR,
GdkMacosMonitor))
+#define GDK_IS_MACOS_MONITOR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_MONITOR))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_monitor_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_MONITOR_H__ */
diff --git a/gdk/macos/gdkmacospopupsurface-private.h b/gdk/macos/gdkmacospopupsurface-private.h
new file mode 100644
index 0000000000..c04c137cf7
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface-private.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_POPUP_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_POPUP_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosPopupSurface GdkMacosPopupSurface;
+typedef struct _GdkMacosPopupSurfaceClass GdkMacosPopupSurfaceClass;
+
+#define GDK_TYPE_MACOS_POPUP_SURFACE (_gdk_macos_popup_surface_get_type())
+#define GDK_MACOS_POPUP_SURFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object),
GDK_TYPE_MACOS_POPUP_SURFACE, GdkMacosPopupSurface))
+#define GDK_IS_MACOS_POPUP_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object),
GDK_TYPE_MACOS_POPUP_SURFACE))
+
+GType _gdk_macos_popup_surface_get_type (void);
+GdkMacosSurface *_gdk_macos_popup_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height);
+void _gdk_macos_popup_surface_attach_to_parent (GdkMacosPopupSurface *self);
+void _gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_POPUP_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c
new file mode 100644
index 0000000000..9da37fb09a
--- /dev/null
+++ b/gdk/macos/gdkmacospopupsurface.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+#include "gdkpopupprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosPopupSurface
+{
+ GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosPopupSurfaceClass
+{
+ GdkMacosSurfaceClass parent_class;
+};
+
+static void
+gdk_macos_popup_surface_layout (GdkMacosPopupSurface *self,
+ int width,
+ int height,
+ GdkPopupLayout *layout)
+{
+ GdkRectangle final_rect;
+ int x, y;
+
+ g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
+ g_assert (layout != NULL);
+ g_assert (GDK_SURFACE (self)->parent);
+
+ gdk_surface_layout_popup_helper (GDK_SURFACE (self),
+ width,
+ height,
+ layout,
+ &final_rect);
+
+ gdk_surface_get_origin (GDK_SURFACE (self)->parent, &x, &y);
+
+ x += final_rect.x;
+ y += final_rect.y;
+
+ if (final_rect.width != GDK_SURFACE (self)->width ||
+ final_rect.height != GDK_SURFACE (self)->height)
+ _gdk_macos_surface_move_resize (GDK_MACOS_SURFACE (self),
+ x,
+ y,
+ final_rect.width,
+ final_rect.height);
+ else
+ _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y);
+
+}
+
+static void
+show_popup (GdkMacosPopupSurface *self)
+{
+ NSWindow *nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ [(GdkMacosWindow *)nswindow showAndMakeKey:NO];
+
+ _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
+}
+
+static void
+show_grabbing_popup (GdkSeat *seat,
+ GdkSurface *surface,
+ gpointer user_data)
+{
+ show_popup (GDK_MACOS_POPUP_SURFACE (surface));
+}
+
+static gboolean
+gdk_macos_popup_surface_present (GdkPopup *popup,
+ int width,
+ int height,
+ GdkPopupLayout *layout)
+{
+ GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)popup;
+
+ g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+ gdk_macos_popup_surface_layout (self, width, height, layout);
+
+ if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)))
+ return TRUE;
+
+ if (GDK_SURFACE (self)->autohide)
+ {
+ GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (popup));
+ GdkSeat *seat = gdk_display_get_default_seat (display);
+
+ gdk_seat_grab (seat,
+ GDK_SURFACE (self),
+ GDK_SEAT_CAPABILITY_ALL,
+ TRUE,
+ NULL, NULL,
+ show_grabbing_popup,
+ NULL);
+ }
+ else
+ {
+ show_popup (GDK_MACOS_POPUP_SURFACE (self));
+ }
+
+ return GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
+}
+
+static GdkGravity
+gdk_macos_popup_surface_get_surface_anchor (GdkPopup *popup)
+{
+ return GDK_SURFACE (popup)->popup.surface_anchor;
+}
+
+static GdkGravity
+gdk_macos_popup_surface_get_rect_anchor (GdkPopup *popup)
+{
+ return GDK_SURFACE (popup)->popup.rect_anchor;
+}
+
+static int
+gdk_macos_popup_surface_get_position_x (GdkPopup *popup)
+{
+ return GDK_SURFACE (popup)->x;
+}
+
+static int
+gdk_macos_popup_surface_get_position_y (GdkPopup *popup)
+{
+ return GDK_SURFACE (popup)->y;
+}
+
+static void
+popup_interface_init (GdkPopupInterface *iface)
+{
+ iface->present = gdk_macos_popup_surface_present;
+ iface->get_surface_anchor = gdk_macos_popup_surface_get_surface_anchor;
+ iface->get_rect_anchor = gdk_macos_popup_surface_get_rect_anchor;
+ iface->get_position_x = gdk_macos_popup_surface_get_position_x;
+ iface->get_position_y = gdk_macos_popup_surface_get_position_y;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosPopupSurface, _gdk_macos_popup_surface, GDK_TYPE_MACOS_SURFACE,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP, popup_interface_init))
+
+enum {
+ PROP_0,
+ LAST_PROP,
+};
+
+static void
+_gdk_macos_popup_surface_finalize (GObject *object)
+{
+ GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)object;
+
+ g_clear_object (&GDK_SURFACE (self)->parent);
+
+ G_OBJECT_CLASS (_gdk_macos_popup_surface_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_popup_surface_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkSurface *surface = GDK_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case LAST_PROP + GDK_POPUP_PROP_PARENT:
+ g_value_set_object (value, surface->parent);
+ break;
+
+ case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+ g_value_set_boolean (value, surface->autohide);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+_gdk_macos_popup_surface_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkSurface *surface = GDK_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case LAST_PROP + GDK_POPUP_PROP_PARENT:
+ surface->parent = g_value_dup_object (value);
+ if (surface->parent)
+ surface->parent->children = g_list_prepend (surface->parent->children, surface);
+ break;
+
+ case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+ surface->autohide = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+_gdk_macos_popup_surface_class_init (GdkMacosPopupSurfaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = _gdk_macos_popup_surface_finalize;
+ object_class->get_property = _gdk_macos_popup_surface_get_property;
+ object_class->set_property = _gdk_macos_popup_surface_set_property;
+
+ gdk_popup_install_properties (object_class, 1);
+}
+
+static void
+_gdk_macos_popup_surface_init (GdkMacosPopupSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_popup_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosWindow *window;
+ GdkMacosSurface *self;
+ NSScreen *screen;
+ NSUInteger style_mask;
+ NSRect content_rect;
+ NSRect screen_rect;
+ int nx;
+ int ny;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+ g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+ g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
+
+ style_mask = NSWindowStyleMaskBorderless;
+
+ _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+ screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+ screen_rect = [screen frame];
+ nx -= screen_rect.origin.x;
+ ny -= screen_rect.origin.y;
+ content_rect = NSMakeRect (nx, ny - height, width, height);
+
+ window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+ styleMask:style_mask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:screen];
+
+ [window setOpaque:NO];
+ [window setBackgroundColor:[NSColor clearColor]];
+
+#if 0
+ /* NOTE: We could set these to be popup level, but then
+ * [NSApp orderedWindows] would not give us the windows
+ * back with the stacking order applied.
+ */
+ [window setLevel:NSPopUpMenuWindowLevel];
+#endif
+
+ self = g_object_new (GDK_TYPE_MACOS_POPUP_SURFACE,
+ "display", display,
+ "frame-clock", frame_clock,
+ "native", window,
+ "parent", parent,
+ NULL);
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_popup_surface_attach_to_parent (GdkMacosPopupSurface *self)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
+ {
+ NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ [parent addChildWindow:window ordered:NSWindowAbove];
+ }
+}
+
+void
+_gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
+ {
+ NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ [parent removeChildWindow:window];
+ }
+}
diff --git a/gdk/macos/gdkmacosseat-private.h b/gdk/macos/gdkmacosseat-private.h
new file mode 100644
index 0000000000..57e5605018
--- /dev/null
+++ b/gdk/macos/gdkmacosseat-private.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_SEAT_PRIVATE_H__
+#define __GDK_MACOS_SEAT_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+
+#include "gdkmacosdisplay.h"
+
+#include "gdkseatprivate.h"
+
+G_BEGIN_DECLS
+
+GdkSeat *_gdk_macos_seat_new (GdkMacosDisplay *display);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SEAT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosseat.c b/gdk/macos/gdkmacosseat.c
new file mode 100644
index 0000000000..b396ec565d
--- /dev/null
+++ b/gdk/macos/gdkmacosseat.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdk.h>
+
+#include "gdkdeviceprivate.h"
+#include "gdkseatdefaultprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosseat-private.h"
+
+GdkSeat *
+_gdk_macos_seat_new (GdkMacosDisplay *display)
+{
+ GdkDevice *core_keyboard;
+ GdkDevice *core_pointer;
+ GdkSeat *seat;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ core_pointer = g_object_new (GDK_TYPE_MACOS_DEVICE,
+ "name", "Core Pointer",
+ "type", GDK_DEVICE_TYPE_MASTER,
+ "source", GDK_SOURCE_MOUSE,
+ "has-cursor", TRUE,
+ "display", display,
+ NULL);
+ core_keyboard = g_object_new (GDK_TYPE_MACOS_DEVICE,
+ "name", "Core Keyboard",
+ "type", GDK_DEVICE_TYPE_MASTER,
+ "source", GDK_SOURCE_KEYBOARD,
+ "has-cursor", FALSE,
+ "display", display,
+ NULL);
+
+ _gdk_device_set_associated_device (GDK_DEVICE (core_pointer),
+ GDK_DEVICE (core_keyboard));
+ _gdk_device_set_associated_device (GDK_DEVICE (core_keyboard),
+ GDK_DEVICE (core_pointer));
+
+ seat = gdk_seat_default_new_for_master_pair (core_pointer, core_keyboard);
+
+ g_object_unref (core_pointer);
+ g_object_unref (core_keyboard);
+
+ return g_steal_pointer (&seat);
+}
diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h
new file mode 100644
index 0000000000..44b90152f2
--- /dev/null
+++ b/gdk/macos/gdkmacossurface-private.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_SURFACE_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <cairo.h>
+
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+
+#import "GdkMacosWindow.h"
+
+G_BEGIN_DECLS
+
+#define GDK_MACOS_SURFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_MACOS_SURFACE,
GdkMacosSurfaceClass))
+#define GDK_IS_MACOS_SURFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_MACOS_SURFACE))
+#define GDK_MACOS_SURFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_MACOS_SURFACE,
GdkMacosSurfaceClass))
+
+struct _GdkMacosSurface
+{
+ GdkSurface parent_instance;
+
+ GList main;
+ GList sorted;
+ GList frame;
+
+ GdkMacosWindow *window;
+ GPtrArray *monitors;
+ cairo_region_t *input_region;
+ char *title;
+
+ int shadow_top;
+ int shadow_right;
+ int shadow_bottom;
+ int shadow_left;
+
+ gint64 pending_frame_counter;
+};
+
+struct _GdkMacosSurfaceClass
+{
+ GdkSurfaceClass parent_class;
+};
+
+GdkMacosSurface *_gdk_macos_surface_new (GdkMacosDisplay *display,
+ GdkSurfaceType surface_type,
+ GdkSurface *parent,
+ int x,
+ int y,
+ int width,
+ int height);
+NSWindow *_gdk_macos_surface_get_native (GdkMacosSurface *self);
+CGDirectDisplayID _gdk_macos_surface_get_screen_id (GdkMacosSurface *self);
+const char *_gdk_macos_surface_get_title (GdkMacosSurface *self);
+void _gdk_macos_surface_set_title (GdkMacosSurface *self,
+ const gchar *title);
+void _gdk_macos_surface_get_shadow (GdkMacosSurface *self,
+ gint *top,
+ gint *right,
+ gint *bottom,
+ gint *left);
+gboolean _gdk_macos_surface_get_modal_hint (GdkMacosSurface *self);
+void _gdk_macos_surface_set_modal_hint (GdkMacosSurface *self,
+ gboolean modal_hint);
+void _gdk_macos_surface_set_geometry_hints (GdkMacosSurface *self,
+ const GdkGeometry *geometry,
+ GdkSurfaceHints geom_mask);
+void _gdk_macos_surface_resize (GdkMacosSurface *self,
+ int width,
+ int height);
+void _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self);
+void _gdk_macos_surface_update_position (GdkMacosSurface *self);
+void _gdk_macos_surface_damage_cairo (GdkMacosSurface *self,
+ cairo_surface_t *surface,
+ cairo_region_t *painted);
+void _gdk_macos_surface_show (GdkMacosSurface *self);
+void _gdk_macos_surface_thaw (GdkMacosSurface *self,
+ gint64
predicted_presentation_time,
+ gint64 refresh_interval);
+CGContextRef _gdk_macos_surface_acquire_context (GdkMacosSurface *self,
+ gboolean clear_scale,
+ gboolean antialias);
+void _gdk_macos_surface_release_context (GdkMacosSurface *self,
+ CGContextRef cg_context);
+void _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self);
+void _gdk_macos_surface_move (GdkMacosSurface *self,
+ int x,
+ int y);
+void _gdk_macos_surface_move_resize (GdkMacosSurface *self,
+ int x,
+ int y,
+ int width,
+ int height);
+gboolean _gdk_macos_surface_is_tracking (GdkMacosSurface *self,
+ NSTrackingArea *area);
+void _gdk_macos_surface_monitor_changed (GdkMacosSurface *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c
new file mode 100644
index 0000000000..214d2411c4
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.c
@@ -0,0 +1,887 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <AppKit/AppKit.h>
+#include <float.h>
+#include <gdk/gdk.h>
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkframeclockidleprivate.h"
+#include "gdkinternals.h"
+#include "gdksurfaceprivate.h"
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+G_DEFINE_TYPE (GdkMacosSurface, gdk_macos_surface, GDK_TYPE_SURFACE)
+
+enum {
+ PROP_0,
+ PROP_NATIVE,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+window_is_fullscreen (GdkMacosSurface *self)
+{
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0;
+}
+
+static void
+gdk_macos_surface_set_input_region (GdkSurface *surface,
+ cairo_region_t *region)
+{
+}
+
+static void
+gdk_macos_surface_set_opaque_region (GdkSurface *surface,
+ cairo_region_t *region)
+{
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ /* TODO: */
+}
+
+static void
+gdk_macos_surface_hide (GdkSurface *surface)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+ GdkSeat *seat;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ seat = gdk_display_get_default_seat (surface->display);
+ gdk_seat_ungrab (seat);
+
+ [self->window hide];
+
+ _gdk_surface_clear_update_area (surface);
+
+ _gdk_macos_display_stacking_changed (GDK_MACOS_DISPLAY (surface->display));
+}
+
+static gint
+gdk_macos_surface_get_scale_factor (GdkSurface *surface)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ return [self->window backingScaleFactor];
+}
+
+static void
+gdk_macos_surface_set_shadow_width (GdkSurface *surface,
+ int left,
+ int right,
+ int top,
+ int bottom)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ self->shadow_top = top;
+ self->shadow_right = right;
+ self->shadow_bottom = bottom;
+ self->shadow_left = left;
+
+ if (top || right || bottom || left)
+ [self->window setHasShadow:NO];
+}
+
+static void
+gdk_macos_surface_begin_resize_drag (GdkSurface *surface,
+ GdkSurfaceEdge edge,
+ GdkDevice *device,
+ int button,
+ int root_x,
+ int root_y,
+ guint32 timestamp)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ [self->window beginManualResize:edge];
+}
+
+static void
+gdk_macos_surface_begin_move_drag (GdkSurface *surface,
+ GdkDevice *device,
+ int button,
+ int root_x,
+ int root_y,
+ guint32 timestamp)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ [self->window beginManualMove];
+}
+
+static void
+gdk_macos_surface_begin_frame (GdkMacosSurface *self)
+{
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+}
+
+static void
+gdk_macos_surface_end_frame (GdkMacosSurface *self)
+{
+ GdkFrameTimings *timings;
+ GdkFrameClock *frame_clock;
+ GdkDisplay *display;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ display = gdk_surface_get_display (GDK_SURFACE (self));
+ frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self));
+
+ if ((timings = gdk_frame_clock_get_current_timings (frame_clock)))
+ self->pending_frame_counter = timings->frame_counter;
+
+ _gdk_macos_display_add_frame_callback (GDK_MACOS_DISPLAY (display), self);
+
+ gdk_surface_freeze_updates (GDK_SURFACE (self));
+}
+
+static void
+gdk_macos_surface_before_paint (GdkMacosSurface *self,
+ GdkFrameClock *frame_clock)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+
+ if (surface->update_freeze_count > 0)
+ return;
+
+ gdk_macos_surface_begin_frame (self);
+}
+
+static void
+gdk_macos_surface_after_paint (GdkMacosSurface *self,
+ GdkFrameClock *frame_clock)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+
+ if (surface->update_freeze_count > 0)
+ return;
+
+ gdk_macos_surface_end_frame (self);
+}
+
+static void
+gdk_macos_surface_get_root_coords (GdkSurface *surface,
+ int x,
+ int y,
+ int *root_x,
+ int *root_y)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+ GdkDisplay *display;
+ NSRect content_rect;
+ int tmp_x = 0;
+ int tmp_y = 0;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ {
+ if (root_x)
+ *root_x = 0;
+ if (root_y)
+ *root_y = 0;
+
+ return;
+ }
+
+ content_rect = [self->window contentRectForFrameRect:[self->window frame]];
+
+ display = gdk_surface_get_display (surface);
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+ content_rect.origin.x,
+ content_rect.origin.y + content_rect.size.height,
+ &tmp_x, &tmp_y);
+
+ tmp_x += x;
+ tmp_y += y;
+
+ if (root_x)
+ *root_x = tmp_x;
+
+ if (root_y)
+ *root_y = tmp_y;
+}
+
+static gboolean
+gdk_macos_surface_get_device_state (GdkSurface *surface,
+ GdkDevice *device,
+ gdouble *x,
+ gdouble *y,
+ GdkModifierType *mask)
+{
+ GdkDisplay *display;
+ NSWindow *nswindow;
+ NSPoint point;
+ int x_tmp;
+ int y_tmp;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (x != NULL);
+ g_assert (y != NULL);
+ g_assert (mask != NULL);
+
+ display = gdk_surface_get_display (surface);
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+ point = [nswindow mouseLocationOutsideOfEventStream];
+
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+ point.x, point.y,
+ &x_tmp, &y_tmp);
+
+ *x = x_tmp;
+ *y = x_tmp;
+
+ return TRUE;
+}
+
+static void
+gdk_macos_surface_destroy (GdkSurface *surface,
+ gboolean foreign_destroy)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosSurface *self = (GdkMacosSurface *)surface;
+ GdkMacosWindow *window = g_steal_pointer (&self->window);
+
+ g_clear_pointer (&self->title, g_free);
+
+ if (window != NULL)
+ [window close];
+
+ _gdk_macos_display_surface_removed (GDK_MACOS_DISPLAY (surface->display), self);
+
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+
+ g_assert (self->sorted.prev == NULL);
+ g_assert (self->sorted.next == NULL);
+ g_assert (self->frame.prev == NULL);
+ g_assert (self->frame.next == NULL);
+ g_assert (self->main.prev == NULL);
+ g_assert (self->main.next == NULL);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_surface_constructed (GObject *object)
+{
+ GdkMacosSurface *self = (GdkMacosSurface *)object;
+ GdkFrameClock *frame_clock;
+
+ g_assert (GDK_IS_MACOS_SURFACE (self));
+
+ G_OBJECT_CLASS (gdk_macos_surface_parent_class)->constructed (object);
+
+ if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
+ {
+ g_signal_connect_object (frame_clock,
+ "before-paint",
+ G_CALLBACK (gdk_macos_surface_before_paint),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (frame_clock,
+ "after-paint",
+ G_CALLBACK (gdk_macos_surface_after_paint),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+}
+
+static void
+gdk_macos_surface_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NATIVE:
+ g_value_set_pointer (value, self->window);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gdk_macos_surface_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NATIVE:
+ self->window = g_value_get_pointer (value);
+ [self->window setGdkSurface:self];
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gdk_macos_surface_class_init (GdkMacosSurfaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass);
+
+ object_class->constructed = gdk_macos_surface_constructed;
+ object_class->get_property = gdk_macos_surface_get_property;
+ object_class->set_property = gdk_macos_surface_set_property;
+
+ surface_class->begin_move_drag = gdk_macos_surface_begin_move_drag;
+ surface_class->begin_resize_drag = gdk_macos_surface_begin_resize_drag;
+ surface_class->destroy = gdk_macos_surface_destroy;
+ surface_class->get_device_state = gdk_macos_surface_get_device_state;
+ surface_class->get_root_coords = gdk_macos_surface_get_root_coords;
+ surface_class->get_scale_factor = gdk_macos_surface_get_scale_factor;
+ surface_class->hide = gdk_macos_surface_hide;
+ surface_class->set_input_region = gdk_macos_surface_set_input_region;
+ surface_class->set_opaque_region = gdk_macos_surface_set_opaque_region;
+ surface_class->set_shadow_width = gdk_macos_surface_set_shadow_width;
+
+ properties [PROP_NATIVE] =
+ g_param_spec_pointer ("native",
+ "Native",
+ "The native NSWindow",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gdk_macos_surface_init (GdkMacosSurface *self)
+{
+ self->frame.data = self;
+ self->main.data = self;
+ self->sorted.data = self;
+ self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+GdkMacosSurface *
+_gdk_macos_surface_new (GdkMacosDisplay *display,
+ GdkSurfaceType surface_type,
+ GdkSurface *parent,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkFrameClock *frame_clock;
+ GdkMacosSurface *ret;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ if (parent != NULL)
+ frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent));
+ else
+ frame_clock = _gdk_frame_clock_idle_new ();
+
+ switch (surface_type)
+ {
+ case GDK_SURFACE_TOPLEVEL:
+ ret = _gdk_macos_toplevel_surface_new (display, parent, frame_clock, x, y, width, height);
+ break;
+
+ case GDK_SURFACE_POPUP:
+ ret = _gdk_macos_popup_surface_new (display, parent, frame_clock, x, y, width, height);
+ break;
+
+ case GDK_SURFACE_TEMP:
+ ret = _gdk_macos_drag_surface_new (display, parent, frame_clock, x, y, width, height);
+ break;
+
+ default:
+ g_warn_if_reached ();
+ ret = NULL;
+ }
+
+ g_object_unref (frame_clock);
+
+ return g_steal_pointer (&ret);
+}
+
+void
+_gdk_macos_surface_get_shadow (GdkMacosSurface *self,
+ gint *top,
+ gint *right,
+ gint *bottom,
+ gint *left)
+{
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ if (top)
+ *top = self->shadow_top;
+
+ if (left)
+ *left = self->shadow_left;
+
+ if (bottom)
+ *bottom = self->shadow_bottom;
+
+ if (right)
+ *right = self->shadow_right;
+}
+
+const char *
+_gdk_macos_surface_get_title (GdkMacosSurface *self)
+{
+
+ return self->title;
+}
+
+void
+_gdk_macos_surface_set_title (GdkMacosSurface *self,
+ const gchar *title)
+{
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ if (title == NULL)
+ title = "";
+
+ if (g_strcmp0 (self->title, title) != 0)
+ {
+ g_free (self->title);
+ self->title = g_strdup (title);
+
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+ [self->window setTitle:[NSString stringWithUTF8String:title]];
+ GDK_END_MACOS_ALLOC_POOL;
+ }
+}
+
+CGDirectDisplayID
+_gdk_macos_surface_get_screen_id (GdkMacosSurface *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), (CGDirectDisplayID)-1);
+
+ if (self->window != NULL)
+ {
+ NSScreen *screen = [self->window screen];
+ return [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+ }
+
+ return (CGDirectDisplayID)-1;
+}
+
+NSWindow *
+_gdk_macos_surface_get_native (GdkMacosSurface *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+ return (NSWindow *)self->window;
+}
+
+void
+_gdk_macos_surface_set_geometry_hints (GdkMacosSurface *self,
+ const GdkGeometry *geometry,
+ GdkSurfaceHints geom_mask)
+{
+ NSSize max_size;
+ NSSize min_size;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+ g_return_if_fail (geometry != NULL);
+ g_return_if_fail (self->window != NULL);
+
+ if (geom_mask & GDK_HINT_MAX_SIZE)
+ max_size = NSMakeSize (geometry->max_width, geometry->max_height);
+ else
+ max_size = NSMakeSize (FLT_MAX, FLT_MAX);
+
+ if (geom_mask & GDK_HINT_MIN_SIZE)
+ min_size = NSMakeSize (geometry->min_width, geometry->min_height);
+ else
+ min_size = NSMakeSize (0, 0);
+
+ [self->window setMaxSize:max_size];
+ [self->window setMinSize:min_size];
+}
+
+void
+_gdk_macos_surface_resize (GdkMacosSurface *self,
+ int width,
+ int height)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+ GdkDisplay *display;
+ NSRect content_rect;
+ NSRect frame_rect;
+ int gx;
+ int gy;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ display = gdk_surface_get_display (surface);
+ _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
+ surface->x,
+ surface->y + surface->height,
+ &gx,
+ &gy);
+ content_rect = NSMakeRect (gx, gy, width, height);
+ frame_rect = [self->window frameRectForContentRect:content_rect];
+ [self->window setFrame:frame_rect display:YES];
+}
+
+void
+_gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self)
+{
+ GdkSurfaceState state;
+ gboolean is_fullscreen;
+ gboolean was_fullscreen;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ state = GDK_SURFACE (self)->state;
+ is_fullscreen = window_is_fullscreen (self);
+ was_fullscreen = (state & GDK_SURFACE_STATE_FULLSCREEN) != 0;
+
+ if (is_fullscreen != was_fullscreen)
+ {
+ if (is_fullscreen)
+ gdk_synthesize_surface_state (GDK_SURFACE (self), 0, GDK_SURFACE_STATE_FULLSCREEN);
+ else
+ gdk_synthesize_surface_state (GDK_SURFACE (self), GDK_SURFACE_STATE_FULLSCREEN, 0);
+ }
+}
+
+void
+_gdk_macos_surface_update_position (GdkMacosSurface *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkSurface *surface = GDK_SURFACE (self);
+ GdkDisplay *display = gdk_surface_get_display (surface);
+ NSRect frame_rect = [self->window frame];
+ NSRect content_rect = [self->window contentRectForFrameRect:frame_rect];
+
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+ content_rect.origin.x,
+ content_rect.origin.y + content_rect.size.height,
+ &surface->x, &surface->y);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+void
+_gdk_macos_surface_damage_cairo (GdkMacosSurface *self,
+ cairo_surface_t *surface,
+ cairo_region_t *painted)
+{
+ NSView *view;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+ g_return_if_fail (surface != NULL);
+
+ view = [self->window contentView];
+
+ if (GDK_IS_MACOS_CAIRO_VIEW (view))
+ [(GdkMacosCairoView *)view setCairoSurfaceWithRegion:surface
+ cairoRegion:painted];
+}
+
+void
+_gdk_macos_surface_thaw (GdkMacosSurface *self,
+ gint64 presentation_time,
+ gint64 refresh_interval)
+{
+ GdkFrameTimings *timings;
+ GdkFrameClock *frame_clock;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ gdk_surface_thaw_updates (GDK_SURFACE (self));
+
+ frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self));
+
+ if (self->pending_frame_counter)
+ {
+ timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter);
+
+ if (timings != NULL)
+ timings->presentation_time = presentation_time - refresh_interval;
+
+ self->pending_frame_counter = 0;
+ }
+
+ timings = gdk_frame_clock_get_current_timings (frame_clock);
+
+ if (timings != NULL)
+ {
+ timings->refresh_interval = refresh_interval;
+ timings->predicted_presentation_time = presentation_time;
+ }
+}
+
+void
+_gdk_macos_surface_show (GdkMacosSurface *self)
+{
+ GdkMacosDisplay *display;
+ gboolean was_mapped;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (self))
+ return;
+
+ was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
+
+ if (!was_mapped)
+ gdk_synthesize_surface_state (GDK_SURFACE (self), GDK_SURFACE_STATE_WITHDRAWN, 0);
+
+ _gdk_surface_update_viewable (GDK_SURFACE (self));
+
+ [self->window showAndMakeKey:GDK_IS_TOPLEVEL (self)];
+
+ if (!was_mapped)
+ {
+ if (gdk_surface_is_viewable (GDK_SURFACE (self)))
+ gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+ }
+
+ display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+ _gdk_macos_display_stacking_changed (display);
+}
+
+CGContextRef
+_gdk_macos_surface_acquire_context (GdkMacosSurface *self,
+ gboolean clear_scale,
+ gboolean antialias)
+{
+ CGContextRef cg_context;
+
+ g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
+
+ if (GDK_SURFACE_DESTROYED (self))
+ return NULL;
+
+ if (!(cg_context = [[NSGraphicsContext currentContext] CGContext]))
+ return NULL;
+
+ CGContextSaveGState (cg_context);
+
+ if (!antialias)
+ CGContextSetAllowsAntialiasing (cg_context, antialias);
+
+ if (clear_scale)
+ {
+ CGSize scale;
+
+ scale = CGSizeMake (1.0, 1.0);
+ scale = CGContextConvertSizeToDeviceSpace (cg_context, scale);
+
+ CGContextScaleCTM (cg_context, 1.0 / scale.width, 1.0 / scale.height);
+ }
+
+ return cg_context;
+}
+
+void
+_gdk_macos_surface_release_context (GdkMacosSurface *self,
+ CGContextRef cg_context)
+{
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ CGContextRestoreGState (cg_context);
+ CGContextSetAllowsAntialiasing (cg_context, TRUE);
+}
+
+void
+_gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self)
+{
+ GdkTranslatedKey translated = {0};
+ GdkTranslatedKey no_lock = {0};
+ GdkDisplay *display;
+ GdkEvent *event;
+ GdkSeat *seat;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ translated.keyval = GDK_KEY_VoidSymbol;
+ no_lock.keyval = GDK_KEY_VoidSymbol;
+
+ display = gdk_surface_get_display (GDK_SURFACE (self));
+ seat = gdk_display_get_default_seat (display);
+ event = gdk_key_event_new (GDK_KEY_PRESS,
+ GDK_SURFACE (self),
+ gdk_seat_get_keyboard (seat),
+ NULL,
+ GDK_CURRENT_TIME,
+ 0,
+ 0,
+ FALSE,
+ &translated,
+ &no_lock);
+ _gdk_event_queue_append (display, event);
+}
+
+void
+_gdk_macos_surface_move (GdkMacosSurface *self,
+ int x,
+ int y)
+{
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ _gdk_macos_surface_move_resize (self, x, y, -1, -1);
+}
+
+void
+_gdk_macos_surface_move_resize (GdkMacosSurface *self,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+ GdkDisplay *display;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ if ((x == -1 || (x == surface->x)) &&
+ (y == -1 || (y == surface->y)) &&
+ (width == -1 || (width == surface->width)) &&
+ (height == -1 || (height == surface->height)))
+ return;
+
+ display = gdk_surface_get_display (surface);
+
+ if (width == -1)
+ width = surface->width;
+
+ if (height == -1)
+ height = surface->height;
+
+ _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
+ x, y, &x, &y);
+
+ [self->window setFrame:NSMakeRect(x, y - height, width, height)
+ display:YES];
+}
+
+gboolean
+_gdk_macos_surface_is_tracking (GdkMacosSurface *self,
+ NSTrackingArea *area)
+{
+ GdkMacosBaseView *view;
+
+ g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE);
+
+ if (self->window == NULL)
+ return FALSE;
+
+ view = (GdkMacosBaseView *)[self->window contentView];
+ if (view == NULL)
+ return FALSE;
+
+ return [view trackingArea] == area;
+}
+
+void
+_gdk_macos_surface_monitor_changed (GdkMacosSurface *self)
+{
+ GdkRectangle rect;
+ GdkRectangle intersect;
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+ guint n_monitors;
+
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+ rect.x = GDK_SURFACE (self)->x;
+ rect.y = GDK_SURFACE (self)->y;
+ rect.width = GDK_SURFACE (self)->width;
+ rect.height = GDK_SURFACE (self)->height;
+
+ for (guint i = self->monitors->len; i > 0; i--)
+ {
+ monitor = g_ptr_array_index (self->monitors, i-1);
+
+ if (!gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
+ {
+ g_object_ref (monitor);
+ g_ptr_array_remove_index (self->monitors, i-1);
+ gdk_surface_leave_monitor (GDK_SURFACE (self), monitor);
+ g_object_unref (monitor);
+ }
+ }
+
+ display = gdk_surface_get_display (GDK_SURFACE (self));
+ n_monitors = gdk_display_get_n_monitors (display);
+
+ for (guint i = 0; i < n_monitors; i++)
+ {
+ monitor = gdk_display_get_monitor (display, i);
+
+ if (g_ptr_array_find (self->monitors, monitor, NULL))
+ continue;
+
+ if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
+ {
+ g_ptr_array_add (self->monitors, g_object_ref (monitor));
+ gdk_surface_enter_monitor (GDK_SURFACE (self), monitor);
+ }
+ }
+}
diff --git a/gdk/macos/gdkmacossurface.h b/gdk/macos/gdkmacossurface.h
new file mode 100644
index 0000000000..470287202e
--- /dev/null
+++ b/gdk/macos/gdkmacossurface.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_SURFACE_H__
+#define __GDK_MACOS_SURFACE_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosSurface GdkMacosSurface;
+typedef struct _GdkMacosSurfaceClass GdkMacosSurfaceClass;
+
+#define GDK_TYPE_MACOS_SURFACE (gdk_macos_surface_get_type())
+#define GDK_MACOS_SURFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_SURFACE,
GdkMacosSurface))
+#define GDK_IS_MACOS_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_SURFACE))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_surface_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_SURFACE_H__ */
diff --git a/gdk/macos/gdkmacostoplevelsurface-private.h b/gdk/macos/gdkmacostoplevelsurface-private.h
new file mode 100644
index 0000000000..d7866601e5
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface-private.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_TOPLEVEL_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_TOPLEVEL_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosToplevelSurface GdkMacosToplevelSurface;
+typedef struct _GdkMacosToplevelSurfaceClass GdkMacosToplevelSurfaceClass;
+
+#define GDK_TYPE_MACOS_TOPLEVEL_SURFACE (_gdk_macos_toplevel_surface_get_type())
+#define GDK_MACOS_TOPLEVEL_SURFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object),
GDK_TYPE_MACOS_TOPLEVEL_SURFACE, GdkMacosToplevelSurface))
+#define GDK_IS_MACOS_TOPLEVEL_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object),
GDK_TYPE_MACOS_TOPLEVEL_SURFACE))
+
+GType _gdk_macos_toplevel_surface_get_type (void);
+GdkMacosSurface *_gdk_macos_toplevel_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height);
+void _gdk_macos_toplevel_surface_attach_to_parent (GdkMacosToplevelSurface *self);
+void _gdk_macos_toplevel_surface_detach_from_parent (GdkMacosToplevelSurface *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_TOPLEVEL_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c
new file mode 100644
index 0000000000..92f7df62b6
--- /dev/null
+++ b/gdk/macos/gdkmacostoplevelsurface.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+#include "gdktoplevelprivate.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosToplevelSurface
+{
+ GdkMacosSurface parent_instance;
+ guint decorated : 1;
+};
+
+struct _GdkMacosToplevelSurfaceClass
+{
+ GdkMacosSurfaceClass parent_instance;
+};
+
+static void
+_gdk_macos_toplevel_surface_fullscreen (GdkMacosToplevelSurface *self)
+{
+ NSWindow *window;
+
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ if (([window styleMask] & NSWindowStyleMaskFullScreen) == 0)
+ [window toggleFullScreen:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_unfullscreen (GdkMacosToplevelSurface *self)
+{
+ NSWindow *window;
+
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ if (([window styleMask] & NSWindowStyleMaskFullScreen) != 0)
+ [window toggleFullScreen:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_maximize (GdkMacosToplevelSurface *self)
+{
+ NSWindow *window;
+
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ if (![window isZoomed])
+ [window zoom:window];
+}
+
+static void
+_gdk_macos_toplevel_surface_unmaximize (GdkMacosToplevelSurface *self)
+{
+ NSWindow *window;
+
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ if ([window isZoomed])
+ [window zoom:window];
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_present (GdkToplevel *toplevel,
+ int width,
+ int height,
+ GdkToplevelLayout *layout)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+ GdkGeometry geometry;
+ GdkSurfaceHints mask;
+
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+ g_assert (window != NULL);
+
+ if (gdk_toplevel_layout_get_resizable (layout))
+ {
+ geometry.min_width = gdk_toplevel_layout_get_min_width (layout);
+ geometry.min_height = gdk_toplevel_layout_get_min_height (layout);
+ mask = GDK_HINT_MIN_SIZE;
+ }
+ else
+ {
+ geometry.max_width = geometry.min_width = width;
+ geometry.max_height = geometry.min_height = height;
+ mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
+ }
+
+ _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask);
+ gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height);
+ _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height);
+
+ /* Maximized state */
+ if (gdk_toplevel_layout_get_maximized (layout))
+ _gdk_macos_toplevel_surface_maximize (self);
+ else
+ _gdk_macos_toplevel_surface_unmaximize (self);
+
+ /* Fullscreen state */
+ if (gdk_toplevel_layout_get_fullscreen (layout))
+ _gdk_macos_toplevel_surface_fullscreen (self);
+ else
+ _gdk_macos_toplevel_surface_unfullscreen (self);
+
+ /* Now present the window */
+ [(GdkMacosWindow *)window showAndMakeKey:YES];
+
+ _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
+
+ return TRUE;
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_minimize (GdkToplevel *toplevel)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+ [window miniaturize:window];
+ return TRUE;
+}
+
+static gboolean
+_gdk_macos_toplevel_surface_lower (GdkToplevel *toplevel)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+ [window orderBack:window];
+ return TRUE;
+}
+
+static void
+_gdk_macos_toplevel_surface_focus (GdkToplevel *toplevel,
+ guint32 timestamp)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)toplevel;
+ NSWindow *nswindow;
+
+ if (GDK_SURFACE_DESTROYED (self))
+ return;
+
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+ [nswindow makeKeyAndOrderFront:nswindow];
+}
+
+static void
+toplevel_iface_init (GdkToplevelInterface *iface)
+{
+ iface->present = _gdk_macos_toplevel_surface_present;
+ iface->minimize = _gdk_macos_toplevel_surface_minimize;
+ iface->lower = _gdk_macos_toplevel_surface_lower;
+ iface->focus = _gdk_macos_toplevel_surface_focus;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosToplevelSurface, _gdk_macos_toplevel_surface, GDK_TYPE_MACOS_SURFACE,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_TOPLEVEL, toplevel_iface_init))
+
+enum {
+ PROP_0,
+ LAST_PROP
+};
+
+static void
+_gdk_macos_toplevel_surface_set_transient_for (GdkMacosToplevelSurface *self,
+ GdkMacosSurface *parent)
+{
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+ g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+ _gdk_macos_toplevel_surface_detach_from_parent (self);
+ g_clear_object (&GDK_SURFACE (self)->transient_for);
+
+ if (g_set_object (&GDK_SURFACE (self)->transient_for, GDK_SURFACE (parent)))
+ _gdk_macos_toplevel_surface_attach_to_parent (self);
+}
+
+static void
+_gdk_macos_toplevel_surface_set_decorated (GdkMacosToplevelSurface *self,
+ gboolean decorated)
+{
+ g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ decorated = !!decorated;
+
+ if (decorated != self->decorated)
+ {
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+ self->decorated = decorated;
+ [(GdkMacosWindow *)window setDecorated:(BOOL)decorated];
+ }
+}
+
+static void
+_gdk_macos_toplevel_surface_hide (GdkSurface *surface)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+
+ _gdk_macos_toplevel_surface_detach_from_parent (self);
+
+ GDK_SURFACE_CLASS (_gdk_macos_toplevel_surface_parent_class)->hide (surface);
+}
+
+static void
+_gdk_macos_toplevel_surface_destroy (GdkSurface *surface,
+ gboolean foreign_destroy)
+{
+ GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+
+ g_clear_object (&GDK_SURFACE (self)->transient_for);
+
+ GDK_SURFACE_CLASS (_gdk_macos_toplevel_surface_parent_class)->destroy (surface, foreign_destroy);
+}
+
+static void
+_gdk_macos_toplevel_surface_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkSurface *surface = GDK_SURFACE (object);
+ GdkMacosSurface *base = GDK_MACOS_SURFACE (surface);
+ GdkMacosToplevelSurface *toplevel = GDK_MACOS_TOPLEVEL_SURFACE (base);
+
+ switch (prop_id)
+ {
+ case LAST_PROP + GDK_TOPLEVEL_PROP_STATE:
+ g_value_set_flags (value, surface->state);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+ g_value_set_string (value, _gdk_macos_surface_get_title (base));
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+ g_value_set_string (value, "");
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+ g_value_set_object (value, GDK_SURFACE (toplevel)->transient_for);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+ g_value_set_boolean (value, GDK_SURFACE (toplevel)->modal_hint);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+ g_value_set_pointer (value, NULL);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
+ g_value_set_boolean (value, toplevel->decorated);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+ g_value_set_enum (value, surface->fullscreen_mode);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+ g_value_set_boolean (value, surface->shortcuts_inhibited);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_gdk_macos_toplevel_surface_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkSurface *surface = GDK_SURFACE (object);
+ GdkMacosSurface *base = GDK_MACOS_SURFACE (surface);
+ GdkMacosToplevelSurface *toplevel = GDK_MACOS_TOPLEVEL_SURFACE (base);
+
+ switch (prop_id)
+ {
+ case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+ _gdk_macos_surface_set_title (base, g_value_get_string (value));
+ g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+ _gdk_macos_toplevel_surface_set_transient_for (toplevel, g_value_get_object (value));
+ g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+ GDK_SURFACE (surface)->modal_hint = g_value_get_boolean (value);
+ g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
+ _gdk_macos_toplevel_surface_set_decorated (toplevel, g_value_get_boolean (value));
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+ surface->fullscreen_mode = g_value_get_enum (value);
+ g_object_notify_by_pspec (G_OBJECT (surface), pspec);
+ break;
+
+ case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_gdk_macos_toplevel_surface_class_init (GdkMacosToplevelSurfaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass);
+
+ object_class->get_property = _gdk_macos_toplevel_surface_get_property;
+ object_class->set_property = _gdk_macos_toplevel_surface_set_property;
+
+ surface_class->destroy = _gdk_macos_toplevel_surface_destroy;
+ surface_class->hide = _gdk_macos_toplevel_surface_hide;
+
+ gdk_toplevel_install_properties (object_class, LAST_PROP);
+}
+
+static void
+_gdk_macos_toplevel_surface_init (GdkMacosToplevelSurface *self)
+{
+ self->decorated = TRUE;
+}
+
+GdkMacosSurface *
+_gdk_macos_toplevel_surface_new (GdkMacosDisplay *display,
+ GdkSurface *parent,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosWindow *window;
+ GdkMacosSurface *self;
+ NSScreen *screen;
+ NSUInteger style_mask;
+ NSRect content_rect;
+ NSRect screen_rect;
+ int nx;
+ int ny;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+ g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+ g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
+
+ style_mask = (NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskResizable);
+
+ _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+ screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+ screen_rect = [screen frame];
+ nx -= screen_rect.origin.x;
+ ny -= screen_rect.origin.y;
+ content_rect = NSMakeRect (nx, ny - height, width, height);
+
+ window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+ styleMask:style_mask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:screen];
+
+ self = g_object_new (GDK_TYPE_MACOS_TOPLEVEL_SURFACE,
+ "display", display,
+ "frame-clock", frame_clock,
+ "native", window,
+ NULL);
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_toplevel_surface_attach_to_parent (GdkMacosToplevelSurface *self)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_return_if_fail (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ if (surface->transient_for != NULL &&
+ !GDK_SURFACE_DESTROYED (surface->transient_for))
+ {
+ NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->transient_for));
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ [parent addChildWindow:window ordered:NSWindowAbove];
+
+ if (GDK_SURFACE (self)->modal_hint)
+ [window setLevel:NSModalPanelWindowLevel];
+ }
+}
+
+void
+_gdk_macos_toplevel_surface_detach_from_parent (GdkMacosToplevelSurface *self)
+{
+ GdkSurface *surface = (GdkSurface *)self;
+
+ g_return_if_fail (GDK_IS_MACOS_TOPLEVEL_SURFACE (self));
+
+ if (GDK_SURFACE_DESTROYED (surface))
+ return;
+
+ if (surface->transient_for != NULL &&
+ !GDK_SURFACE_DESTROYED (surface->transient_for))
+ {
+ NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->transient_for));
+ NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
+
+ [parent removeChildWindow:window];
+ [window setLevel:NSNormalWindowLevel];
+ }
+}
diff --git a/gdk/macos/gdkmacosutils-private.h b/gdk/macos/gdkmacosutils-private.h
new file mode 100644
index 0000000000..678982ba97
--- /dev/null
+++ b/gdk/macos/gdkmacosutils-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_UTILS_PRIVATE_H__
+#define __GDK_MACOS_UTILS_PRIVATE_H__
+
+#include <AppKit/AppKit.h>
+#include <gdk/gdk.h>
+
+#define GDK_BEGIN_MACOS_ALLOC_POOL NSAutoreleasePool *_pool = [[NSAutoreleasePool alloc] init]
+#define GDK_END_MACOS_ALLOC_POOL [_pool release]
+
+static inline gboolean
+queue_contains (GQueue *queue,
+ GList *link_)
+{
+ return queue->head == link_ || link_->prev || link_->next;
+}
+
+#endif /* __GDK_MACOS_UTILS_PRIVATE_H__ */
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
new file mode 100644
index 0000000000..c10a3bb5b8
--- /dev/null
+++ b/gdk/macos/meson.build
@@ -0,0 +1,53 @@
+gdk_macos_sources = files([
+ 'gdkdisplaylinksource.c',
+ 'GdkMacosWindow.c',
+ 'gdkmacoscairocontext.c',
+ 'gdkmacoscursor.c',
+ 'gdkmacosdevice.c',
+ 'gdkmacosdisplay.c',
+ 'gdkmacosdisplay-settings.c',
+ 'gdkmacosdisplay-translate.c',
+ 'gdkmacosdragsurface.c',
+ 'gdkmacoseventsource.c',
+ 'gdkmacoskeymap.c',
+ 'gdkmacosmonitor.c',
+ 'gdkmacospopupsurface.c',
+ 'gdkmacosseat.c',
+ 'gdkmacossurface.c',
+ 'gdkmacostoplevelsurface.c',
+
+ 'GdkMacosBaseView.c',
+ 'GdkMacosCairoView.c',
+ # 'GdkMacosGLView.c',
+])
+
+gdk_macos_public_headers = files([
+ 'gdkmacosdevice.h',
+ 'gdkmacosdisplay.h',
+ 'gdkmacoskeymap.h',
+ 'gdkmacosmonitor.h',
+ 'gdkmacossurface.h',
+])
+
+install_headers(gdk_macos_public_headers, 'gdkmacos.h', subdir: 'gtk-4.0/gdk/macos/')
+
+gdk_macos_frameworks = [
+ 'AppKit',
+ 'Carbon',
+ 'CoreVideo',
+ 'CoreServices',
+]
+
+gdk_macos_deps = [
+ dependency('appleframeworks', modules: gdk_macos_frameworks)
+]
+
+libgdk_c_args += ['-xobjective-c']
+
+libgdk_macos = static_library('gdk-macos',
+ gdk_macos_sources, gdkconfig, gdkenum_h,
+ include_directories: [ confinc, gdkinc, ],
+ c_args: libgdk_c_args + common_cflags,
+ link_args: common_ldflags,
+ link_with: [],
+ dependencies: gdk_deps + gdk_macos_deps)
diff --git a/gdk/meson.build b/gdk/meson.build
index ac14c3b6fe..eebc9b2a97 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -143,6 +143,7 @@ gdkconfig_cdata.set('GDK_WINDOWING_X11', x11_enabled)
gdkconfig_cdata.set('GDK_WINDOWING_WAYLAND', wayland_enabled)
gdkconfig_cdata.set('GDK_WINDOWING_WIN32', win32_enabled)
gdkconfig_cdata.set('GDK_WINDOWING_BROADWAY', broadway_enabled)
+gdkconfig_cdata.set('GDK_WINDOWING_MACOS', macos_enabled)
gdkconfig_cdata.set('GDK_RENDERING_VULKAN', have_vulkan)
gdkconfig = configure_file(
@@ -216,9 +217,14 @@ if wayland_enabled or broadway_enabled
endif
endif
+libgdk_c_args = [
+ '-DGTK_COMPILATION',
+ '-DG_LOG_DOMAIN="Gdk"',
+]
+
gdk_backends = []
gdk_backends_gen_headers = [] # non-public generated headers
-foreach backend : ['broadway', 'quartz', 'wayland', 'win32', 'x11']
+foreach backend : ['broadway', 'quartz', 'wayland', 'win32', 'x11', 'macos']
if get_variable('@0@_enabled'.format(backend))
subdir(backend)
gdk_deps += get_variable('gdk_@0@_deps'.format(backend))
@@ -235,16 +241,12 @@ if gdk_backends.length() == 0
error('No backends enabled')
endif
-# FIXME: might have to add '-xobjective-c' to c_args for quartz backend?
libgdk = static_library('gdk',
sources: [gdk_sources, gdk_backends_gen_headers, gdkconfig],
dependencies: gdk_deps + [libgtk_css_dep],
link_with: [libgtk_css, ],
include_directories: [confinc, gdkx11_inc, wlinc],
- c_args: [
- '-DGTK_COMPILATION',
- '-DG_LOG_DOMAIN="Gdk"',
- ] + common_cflags,
+ c_args: libgdk_c_args + common_cflags,
link_whole: gdk_backends,
link_args: common_ldflags)
diff --git a/gtk/meson.build b/gtk/meson.build
index ecccb3f642..56daeb356a 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -983,6 +983,12 @@ if cc.has_header('langinfo.h')
endforeach
endif
+# Maintain compatibility with autotools
+gtk_ldflags = []
+if os_darwin
+ gtk_ldflags += [ '-compatibility_version 1', '-current_version 1.0', ]
+endif
+
# Library
libgtk = library('gtk-4',
soversion: gtk_soversion,
@@ -992,7 +998,7 @@ libgtk = library('gtk-4',
include_directories: [confinc, gdkinc, gskinc, gtkinc],
dependencies: gtk_deps + [libgtk_css_dep, libgdk_dep, libgsk_dep],
link_with: [libgtk_css, libgdk, libgsk, ],
- link_args: common_ldflags,
+ link_args: common_ldflags + gtk_ldflags,
install: true)
gtk_dep_sources = [gtkversion, gtktypebuiltins_h]
diff --git a/meson.build b/meson.build
index fcd3fe2052..5eead7f5e8 100644
--- a/meson.build
+++ b/meson.build
@@ -93,6 +93,7 @@ gtk_api_version = '4.0'
x11_enabled = get_option('x11-backend')
wayland_enabled = get_option('wayland-backend')
broadway_enabled = get_option('broadway-backend')
+macos_enabled = get_option('macos-backend')
quartz_enabled = get_option('quartz-backend')
win32_enabled = get_option('win32-backend')
@@ -118,6 +119,7 @@ if os_darwin
wayland_enabled = false
else
quartz_enabled = false
+ macos_enabled = false
endif
if os_win32
@@ -242,6 +244,8 @@ if cc.get_id() == 'msvc'
elif cc.get_id() == 'gcc' or cc.get_id() == 'clang'
test_cflags = [
'-fno-strict-aliasing',
+ '-Wno-c++11-extensions',
+ '-Wno-missing-include-dirs',
'-Wno-typedef-redefinition',
'-Wcast-align',
'-Wduplicated-branches',
@@ -317,11 +321,6 @@ if os_unix and not os_darwin
endforeach
endif
-# Maintain compatibility with autotools
-if os_darwin
- common_ldflags += [ '-compatibility_version 1', '-current_version 1.0', ]
-endif
-
confinc = include_directories('.')
gdkinc = include_directories('gdk')
gskinc = include_directories('gsk')
@@ -368,8 +367,10 @@ pangocairo_dep = dependency('pangocairo', version: cairo_req,
pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req,
fallback : ['gdk-pixbuf', 'gdkpixbuf_dep'])
epoxy_dep = dependency('epoxy', version: epoxy_req,
- fallback: ['libepoxy', 'libepoxy_dep'])
-atk_dep = dependency('atk', version: atk_req)
+ fallback: ['libepoxy', 'libepoxy_dep'],
+ static: false)
+atk_dep = dependency('atk', version: atk_req,
+ fallback : ['atk', 'libatk_dep'])
harfbuzz_dep = dependency('harfbuzz', version: '>= 0.9', required: false)
xkbdep = dependency('xkbcommon', version: xkbcommon_req, required: wayland_enabled)
graphene_dep = dependency('graphene-gobject-1.0', version: graphene_req,
@@ -775,7 +776,7 @@ pkg_install_dir = join_paths(get_option('libdir'), 'pkgconfig')
pkgs = [ 'gtk4.pc' ]
pkg_targets = ''
-foreach backend: [ 'broadway', 'quartz', 'wayland', 'win32', 'x11', ]
+foreach backend: [ 'broadway', 'quartz', 'macos', 'wayland', 'win32', 'x11', ]
if get_variable('@0@_enabled'.format(backend))
pkgs += ['gtk4-@0@.pc'.format(backend)]
pkg_targets += ' ' + backend
diff --git a/meson_options.txt b/meson_options.txt
index 73f26da7da..61c8930ab3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -9,6 +9,8 @@ option('win32-backend', type: 'boolean', value: true,
description : 'Enable the Windows gdk backend (only when building on Windows)')
option('quartz-backend', type: 'boolean', value: true,
description : 'Enable the macOS gdk backend (only when building on macOS)')
+option('macos-backend', type: 'boolean', value: true,
+ description : 'Enable the macOS gdk backend (only when building on macOS)')
# Media backends
option('media', type: 'string', value: 'gstreamer',
diff --git a/subprojects/atk.wrap b/subprojects/atk.wrap
new file mode 100644
index 0000000000..f6765c5bfa
--- /dev/null
+++ b/subprojects/atk.wrap
@@ -0,0 +1,5 @@
+[wrap-git]
+directory=atk
+url=https://gitlab.gnome.org/GNOME/atk.git
+push-url=ssh://git gitlab gnome org:GNOME/atk.git
+revision=master
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]