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..6a56f0187f 100644
--- a/gdk/gdkcontentdeserializer.c
+++ b/gdk/gdkcontentdeserializer.c
@@ -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..d9151527ff 100644
--- a/gdk/gdkcontentserializer.c
+++ b/gdk/gdkcontentserializer.c
@@ -907,7 +907,7 @@ init (void)
g_slist_free (formats);
-#ifdef G_OS_UNIX
+#if defined(G_OS_UNIX) && !defined(__APPLE__)
file_transfer_portal_register ();
#endif
diff --git a/gdk/gdkdisplaymanager.c b/gdk/gdkdisplaymanager.c
index 94ced0387e..a17305fcbe 100644
--- a/gdk/gdkdisplaymanager.c
+++ b/gdk/gdkdisplaymanager.c
@@ -50,6 +50,10 @@
#include "broadway/gdkprivate-broadway.h"
#endif
+#ifdef GDK_WINDOWING_MACOS
+#include "macos/gdkmacosdisplay-private.h"
+#endif
+
#ifdef GDK_WINDOWING_WIN32
#include "win32/gdkwin32.h"
#include "win32/gdkprivate-win32.h"
@@ -262,6 +266,9 @@ static GdkBackend gdk_backends[] = {
#ifdef GDK_WINDOWING_QUARTZ
{ "quartz", _gdk_quartz_display_open },
#endif
+#ifdef GDK_WINDOWING_MACOS
+ { "macos", _gdk_macos_display_open },
+#endif
#ifdef GDK_WINDOWING_WIN32
{ "win32", _gdk_win32_display_open },
#endif
diff --git a/gdk/macos/GdkMacosBaseView.c b/gdk/macos/GdkMacosBaseView.c
new file mode 100644
index 0000000000..c6750dfdb9
--- /dev/null
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -0,0 +1,718 @@
+/* GdkMacosBaseView.c
+ *
+ * Copyright 2005-2007 Imendio AB
+ * Copyright 2011 Hiroyuki Yamamoto
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkinternals.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+/* Text Input Client */
+#define TIC_MARKED_TEXT "tic-marked-text"
+#define TIC_SELECTED_POS "tic-selected-pos"
+#define TIC_SELECTED_LEN "tic-selected-len"
+#define TIC_INSERT_TEXT "tic-insert-text"
+#define TIC_IN_KEY_DOWN "tic-in-key-down"
+
+/* GtkIMContext */
+#define GIC_CURSOR_RECT "gic-cursor-rect"
+#define GIC_FILTER_KEY "gic-filter-key"
+#define GIC_FILTER_PASSTHRU 0
+#define GIC_FILTER_FILTERED 1
+
+@implementation GdkMacosBaseView
+
+-(id)initWithFrame:(NSRect)frameRect
+{
+ if ((self = [super initWithFrame: frameRect]))
+ {
+ NSRect rect = NSMakeRect (0, 0, 0, 0);
+ NSTrackingAreaOptions options;
+
+ markedRange = NSMakeRange (NSNotFound, 0);
+ selectedRange = NSMakeRange (0, 0);
+ [self setValue: @(YES) forKey: @"postsFrameChangedNotifications"];
+
+ options = (NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingInVisibleRect |
+ NSTrackingActiveAlways);
+ trackingArea = [[NSTrackingArea alloc] initWithRect:rect
+ options:options
+ owner:(id)self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+ }
+
+ return self;
+}
+
+-(void)setNeedsDisplay:(BOOL)needsDisplay
+{
+ for (id child in [self subviews])
+ [child setNeedsDisplay:needsDisplay];
+}
+
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+ /* Do nothing */
+}
+
+-(BOOL)acceptsFirstMouse
+{
+ return YES;
+}
+
+-(BOOL)mouseDownCanMoveWindow
+{
+ return NO;
+}
+
+-(BOOL)acceptsFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("acceptsFirstResponder"));
+ return YES;
+}
+
+-(BOOL)becomeFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("becomeFirstResponder"));
+ return YES;
+}
+
+-(BOOL)resignFirstResponder
+{
+ GDK_NOTE (EVENTS, g_message ("resignFirstResponder"));
+ return YES;
+}
+
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate
+{
+ needsInvalidateShadow = invalidate;
+}
+
+-(NSTrackingArea *)trackingArea
+{
+ return trackingArea;
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+ return [(GdkMacosWindow *)[self window] gdkSurface];
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+ GdkMacosSurface *surface = [self gdkSurface];
+ GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (surface));
+
+ return GDK_MACOS_DISPLAY (display);
+}
+
+-(void)keyDown:(NSEvent *)theEvent
+{
+ /* NOTE: When user press Cmd+A, interpretKeyEvents: will call noop:
+ * method. When user press and hold A to show the accented char window,
+ * it consumed repeating key down events for key 'A' do NOT call
+ * any other method. We use this behavior to determine if this key
+ * down event is filtered by interpretKeyEvents.
+ */
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+ GDK_NOTE (EVENTS, g_message ("keyDown"));
+ [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
+}
+
+-(void)flagsChanged: (NSEvent *)theEvent
+{
+}
+
+-(NSUInteger)characterIndexForPoint:(NSPoint)aPoint
+{
+ GDK_NOTE (EVENTS, g_message ("characterIndexForPoint"));
+ return 0;
+}
+
+-(NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange: (NSRangePointer)actualRange
+{
+ GdkRectangle *rect;
+
+ GDK_NOTE (EVENTS, g_message ("firstRectForCharacterRange"));
+
+ if ((rect = g_object_get_data (G_OBJECT ([self gdkSurface]), GIC_CURSOR_RECT)))
+ {
+ GdkMacosDisplay *display = [self gdkDisplay];
+ int ns_x, ns_y;
+
+ _gdk_macos_display_to_display_coords (display,
+ rect->x, rect->y + rect->height,
+ &ns_x, &ns_y);
+
+ return NSMakeRect (ns_x, ns_y, rect->width, rect->height);
+ }
+
+ return NSMakeRect (0, 0, 0, 0);
+}
+
+-(NSArray *)validAttributesForMarkedText
+{
+ GDK_NOTE (EVENTS, g_message ("validAttributesForMarkedText"));
+ return [NSArray arrayWithObjects: NSUnderlineStyleAttributeName, nil];
+}
+
+-(NSAttributedString *)attributedSubstringForProposedRange: (NSRange)aRange actualRange: (NSRangePointer)actualRange
+{
+ GDK_NOTE (EVENTS, g_message ("attributedSubstringForProposedRange"));
+ return nil;
+}
+
+-(BOOL)hasMarkedText
+{
+ GDK_NOTE (EVENTS, g_message ("hasMarkedText"));
+ return markedRange.location != NSNotFound && markedRange.length != 0;
+}
+
+-(NSRange)markedRange
+{
+ GDK_NOTE (EVENTS, g_message ("markedRange"));
+ return markedRange;
+}
+
+-(NSRange)selectedRange
+{
+ GDK_NOTE (EVENTS, g_message ("selectedRange"));
+ return selectedRange;
+}
+
+-(void)unmarkText
+{
+ GDK_NOTE (EVENTS, g_message ("unmarkText"));
+
+ selectedRange = NSMakeRange (0, 0);
+ markedRange = NSMakeRange (NSNotFound, 0);
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, NULL, g_free);
+}
+
+-(void)setMarkedText:(id)aString selectedRange: (NSRange)newSelection replacementRange: (NSRange)replacementRange
+{
+ const char *str;
+
+ GDK_NOTE (EVENTS, g_message ("setMarkedText"));
+
+ if (replacementRange.location == NSNotFound)
+ {
+ markedRange = NSMakeRange (newSelection.location, [aString length]);
+ selectedRange = NSMakeRange (newSelection.location, newSelection.length);
+ }
+ else
+ {
+ markedRange = NSMakeRange (replacementRange.location, [aString length]);
+ selectedRange = NSMakeRange (replacementRange.location + newSelection.location, newSelection.length);
+ }
+
+ if ([aString isKindOfClass: [NSAttributedString class]])
+ str = [[aString string] UTF8String];
+ else
+ str = [aString UTF8String];
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_MARKED_TEXT, g_strdup (str), g_free);
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ TIC_SELECTED_POS,
+ GUINT_TO_POINTER (selectedRange.location));
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ TIC_SELECTED_LEN,
+ GUINT_TO_POINTER (selectedRange.length));
+
+ GDK_NOTE (EVENTS, g_message ("setMarkedText: set %s (%p, nsview %p): %s",
+ TIC_MARKED_TEXT, [self gdkSurface], self,
+ str ? str : "(empty)"));
+
+ /* handle text input changes by mouse events */
+ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+ _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)doCommandBySelector:(SEL)aSelector
+{
+ GDK_NOTE (EVENTS, g_message ("doCommandBySelector"));
+
+ if ([self respondsToSelector: aSelector])
+ [self performSelector: aSelector];
+}
+
+-(void)insertText:(id)aString replacementRange: (NSRange)replacementRange
+{
+ const char *str;
+ NSString *string;
+
+ GDK_NOTE (EVENTS, g_message ("insertText"));
+
+ if ([self hasMarkedText])
+ [self unmarkText];
+
+ if ([aString isKindOfClass: [NSAttributedString class]])
+ string = [aString string];
+ else
+ string = aString;
+
+ NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
+ NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ if ([string rangeOfCharacterFromSet:ctrlChars].length &&
+ [string rangeOfCharacterFromSet:wsnlChars].length == 0)
+ {
+ /* discard invalid text input with Chinese input methods */
+ str = "";
+ [self unmarkText];
+ [[NSTextInputContext currentInputContext] discardMarkedText];
+ }
+ else
+ {
+ str = [string UTF8String];
+ }
+
+ g_object_set_data_full (G_OBJECT ([self gdkSurface]), TIC_INSERT_TEXT, g_strdup (str), g_free);
+ GDK_NOTE (EVENTS, g_message ("insertText: set %s (%p, nsview %p): %s",
+ TIC_INSERT_TEXT, [self gdkSurface], self,
+ str ? str : "(empty)"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_FILTERED));
+
+ /* handle text input changes by mouse events */
+ if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT ([self gdkSurface]), TIC_IN_KEY_DOWN)))
+ _gdk_macos_surface_synthesize_null_key ([self gdkSurface]);
+}
+
+-(void)deleteBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToBeginningOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteToBeginningOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteToEndOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteToEndOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteWordBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)deleteWordForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("deleteWordForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertBacktab:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertBacktab"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertNewline:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertNewline"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)insertTab:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("insertTab"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveBackwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveBackwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDown:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveDown"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveDownAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveDownAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveForwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveForwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeft:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveLeft"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveLeftAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveLeftAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRight:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveRight"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveRightAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveRightAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocument:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocument"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfDocumentAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfDocumentAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToBeginningOfLineAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToBeginningOfLineAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocument:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfDocument"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfDocumentAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfDocumentAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveToEndOfLineAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveToEndOfLineAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUp:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveUp"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveUpAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveUpAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordBackward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordBackwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordBackwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForward:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordForward"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordForwardAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordForwardAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeft:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordLeft"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordLeftAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordLeftAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRight:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordRight"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)moveWordRightAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("moveWordRightAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDown:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageDown"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageDownAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageDownAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUp:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageUp"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)pageUpAndModifySelection:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("pageUpAndModifySelection"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectAll:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectAll"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectLine:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectLine"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)selectWord:(id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("selectWord"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+-(void)noop: (id)sender
+{
+ GDK_NOTE (EVENTS, g_message ("noop"));
+
+ g_object_set_data (G_OBJECT ([self gdkSurface]),
+ GIC_FILTER_KEY,
+ GUINT_TO_POINTER (GIC_FILTER_PASSTHRU));
+}
+
+@end
diff --git a/gdk/quartz/GdkQuartzView.h b/gdk/macos/GdkMacosBaseView.h
similarity index 51%
rename from gdk/quartz/GdkQuartzView.h
rename to gdk/macos/GdkMacosBaseView.h
index 24ce2fd7c3..7fcfc7e43b 100644
--- a/gdk/quartz/GdkQuartzView.h
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -1,6 +1,7 @@
-/* GdkQuartzView.h
+/* GdkMacosBaseView.h
*
- * Copyright (C) 2005 Imendio AB
+ * 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
@@ -14,36 +15,32 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
#import
-#include "gdk/gdk.h"
+#import
-/* 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"
+#include
-/* 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
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
-@interface GdkQuartzView : NSView
+#define GDK_IS_MACOS_BASE_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosBaseView class]])
+
+@interface GdkMacosBaseView : NSView
{
- GdkSurface *gdk_surface;
- NSTrackingRectTag trackingRect;
+ NSTrackingArea *trackingArea;
BOOL needsInvalidateShadow;
NSRange markedRange;
NSRange selectedRange;
}
-- (void)setGdkSurface: (GdkSurface *)window;
-- (GdkSurface *)gdkSurface;
-- (NSTrackingRectTag)trackingRect;
-- (void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(GdkMacosSurface *)gdkSurface;
+-(GdkMacosDisplay *)gdkDisplay;
+-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
+-(NSTrackingArea *)trackingArea;
+-(void)setOpaqueRegion:(cairo_region_t *)region;
@end
diff --git a/gdk/macos/GdkMacosCairoSubview.c b/gdk/macos/GdkMacosCairoSubview.c
new file mode 100644
index 0000000000..425b52ac78
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.c
@@ -0,0 +1,171 @@
+/* GdkMacosCairoSubview.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoSubview.h"
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoSubview
+
+-(BOOL)isOpaque
+{
+ return _isOpaque;
+}
+
+-(BOOL)isFlipped
+{
+ return YES;
+}
+
+-(GdkSurface *)gdkSurface
+{
+ return GDK_SURFACE ([(GdkMacosBaseView *)[self superview] gdkSurface]);
+}
+
+-(void)drawRect:(NSRect)rect
+{
+ CGContextRef cgContext;
+ GdkSurface *gdk_surface;
+ cairo_surface_t *dest;
+ const NSRect *rects = NULL;
+ NSView *root_view;
+ NSInteger n_rects = 0;
+ NSRect abs_bounds;
+ cairo_t *cr;
+ CGSize scale;
+ int scale_factor;
+
+ if (self->cairoSurface == NULL)
+ return;
+
+ /* Acquire everything we need to do translations, drawing, etc */
+ gdk_surface = [self gdkSurface];
+ scale_factor = gdk_surface_get_scale_factor (gdk_surface);
+ root_view = [[self window] contentView];
+ cgContext = [[NSGraphicsContext currentContext] CGContext];
+ abs_bounds = [self convertRect:[self bounds] toView:root_view];
+
+ CGContextSaveGState (cgContext);
+
+ /* Translate scaling to remove HiDPI scaling from CGContext as
+ * cairo will be doing that for us already.
+ */
+ scale = CGSizeMake (1.0, 1.0);
+ scale = CGContextConvertSizeToDeviceSpace (cgContext, scale);
+ CGContextScaleCTM (cgContext, 1.0 / scale.width, 1.0 / scale.height);
+
+ /* Create the cairo surface to draw to the CGContext and translate
+ * coordinates so we can pretend we are in the same coordinate system
+ * as the GDK surface.
+ */
+ dest = cairo_quartz_surface_create_for_cg_context (cgContext,
+ gdk_surface->width * scale_factor,
+ gdk_surface->height * scale_factor);
+ cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+
+ /* Create cairo context and translate things into the origin of
+ * the topmost contentView so that we just draw at 0,0 with a
+ * clip region to paint the surface.
+ */
+ cr = cairo_create (dest);
+ cairo_translate (cr, -abs_bounds.origin.x, -abs_bounds.origin.y);
+
+ /* Clip the cairo context based on the rectangles to be drawn
+ * within the bounding box :rect.
+ */
+ [self getRectsBeingDrawn:&rects count:&n_rects];
+ for (NSInteger i = 0; i < n_rects; i++)
+ {
+ NSRect area = [self convertRect:rects[i] toView:root_view];
+ cairo_rectangle (cr,
+ area.origin.x, area.origin.y,
+ area.size.width, area.size.height);
+ }
+ cairo_clip (cr);
+
+ /* Now paint the surface (without blending) as we do not need
+ * any compositing here. The transparent regions (like shadows)
+ * are already on non-opaque layers.
+ */
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface (cr, self->cairoSurface, 0, 0);
+ cairo_paint (cr);
+
+ /* Cleanup state, flush the surface to the backing layer, and
+ * restore GState for future use.
+ */
+ cairo_destroy (cr);
+ cairo_surface_flush (dest);
+ cairo_surface_destroy (dest);
+ CGContextRestoreGState (cgContext);
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)surface
+ withDamage:(cairo_region_t *)region
+{
+ if (surface != self->cairoSurface)
+ {
+ g_clear_pointer (&self->cairoSurface, cairo_surface_destroy);
+ if (surface != NULL)
+ self->cairoSurface = cairo_surface_reference (surface);
+ }
+
+ if (region != NULL)
+ {
+ NSView *root_view = [[self window] contentView];
+ NSRect abs_bounds = [self convertRect:[self bounds] toView:root_view];
+ guint n_rects = cairo_region_num_rectangles (region);
+
+ for (guint i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+ NSRect nsrect;
+
+ cairo_region_get_rectangle (region, i, &rect);
+ nsrect = NSMakeRect (rect.x, rect.y, rect.width, rect.height);
+
+ if (NSIntersectsRect (abs_bounds, nsrect))
+ {
+ nsrect.origin.x -= abs_bounds.origin.x;
+ nsrect.origin.y -= abs_bounds.origin.y;
+ [self setNeedsDisplayInRect:nsrect];
+ }
+ }
+ }
+
+ for (id view in [self subviews])
+ [(GdkMacosCairoSubview *)view setCairoSurface:surface
+ withDamage:region];
+}
+
+-(void)setOpaque:(BOOL)opaque
+{
+ self->_isOpaque = opaque;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoSubview.h b/gdk/macos/GdkMacosCairoSubview.h
new file mode 100644
index 0000000000..9255347566
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.h
@@ -0,0 +1,35 @@
+/* GdkMacosCairoSubview.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include
+
+#define GDK_IS_MACOS_CAIRO_SUBVIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoSubview class]])
+
+@interface GdkMacosCairoSubview : NSView
+{
+ BOOL _isOpaque;
+ cairo_surface_t *cairoSurface;
+}
+
+-(void)setOpaque:(BOOL)opaque;
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+ withDamage:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
new file mode 100644
index 0000000000..e6a31178b4
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -0,0 +1,170 @@
+/* GdkMacosCairoView.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoView.h"
+#import "GdkMacosCairoSubview.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoView
+
+-(void)dealloc
+{
+ g_clear_pointer (&self->opaque, g_ptr_array_unref);
+ self->transparent = NULL;
+
+ [super dealloc];
+}
+
+-(BOOL)isOpaque
+{
+ if ([self window])
+ return [[self window] isOpaque];
+ return YES;
+}
+
+-(BOOL)isFlipped
+{
+ return YES;
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+ withDamage:(cairo_region_t *)cairoRegion
+{
+ for (id view in [self subviews])
+ [(GdkMacosCairoSubview *)view setCairoSurface:cairoSurface
+ withDamage:cairoRegion];
+}
+
+-(void)removeOpaqueChildren
+{
+ [[self->transparent subviews]
+ makeObjectsPerformSelector:@selector(removeFromSuperview)];
+
+ if (self->opaque->len)
+ g_ptr_array_remove_range (self->opaque, 0, self->opaque->len);
+}
+
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+ NSRect abs_bounds;
+ guint n_rects;
+
+ if (region == NULL)
+ return;
+
+ abs_bounds = [self convertRect:[self bounds] toView:nil];
+ n_rects = cairo_region_num_rectangles (region);
+
+ /* The common case (at least for opaque windows and CSD) is that we will
+ * have either one or two opaque rectangles. If we detect that the same
+ * number of them are available as the previous, we can just resize the
+ * previous ones to avoid adding/removing views at a fast rate while
+ * resizing.
+ */
+ if (n_rects == self->opaque->len)
+ {
+ for (guint i = 0; i < n_rects; i++)
+ {
+ GdkMacosCairoSubview *child;
+ cairo_rectangle_int_t rect;
+
+ child = g_ptr_array_index (self->opaque, i);
+ cairo_region_get_rectangle (region, i, &rect);
+
+ [child setFrame:NSMakeRect (rect.x - abs_bounds.origin.x,
+ rect.y - abs_bounds.origin.y,
+ rect.width,
+ rect.height)];
+ }
+
+ return;
+ }
+
+ [self removeOpaqueChildren];
+ for (guint i = 0; i < n_rects; i++)
+ {
+ GdkMacosCairoSubview *child;
+ cairo_rectangle_int_t rect;
+ NSRect nsrect;
+
+ cairo_region_get_rectangle (region, i, &rect);
+ nsrect = NSMakeRect (rect.x - abs_bounds.origin.x,
+ rect.y - abs_bounds.origin.y,
+ rect.width,
+ rect.height);
+
+ child = [[GdkMacosCairoSubview alloc] initWithFrame:nsrect];
+ [child setOpaque:YES];
+ [child setWantsLayer:YES];
+ [self->transparent addSubview:child];
+ g_ptr_array_add (self->opaque, child);
+ }
+}
+
+-(NSView *)initWithFrame:(NSRect)frame
+{
+ if ((self = [super initWithFrame:frame]))
+ {
+ /* An array to track all the opaque children placed into
+ * the child self->transparent. This allows us to reuse them
+ * when we receive a new opaque area instead of discarding
+ * them on each draw.
+ */
+ self->opaque = g_ptr_array_new ();
+
+ /* Setup our primary subview which will render all content that is not
+ * within an opaque region (such as shadows for CSD windows). For opaque
+ * windows, this will all be obscurred by other views, so it doesn't
+ * matter much to have it here.
+ */
+ self->transparent = [[GdkMacosCairoSubview alloc] initWithFrame:frame];
+ [self addSubview:self->transparent];
+
+ }
+
+ return self;
+}
+
+-(void)setFrame:(NSRect)rect
+{
+ [super setFrame:rect];
+ [self->transparent setFrame:NSMakeRect (0, 0, rect.size.width, rect.size.height)];
+}
+
+-(BOOL)acceptsFirstMouse
+{
+ return YES;
+}
+
+-(BOOL)mouseDownCanMoveWindow
+{
+ return NO;
+}
+
+@end
diff --git a/gdk/quartz/gdkdevicemanager-core-quartz.h b/gdk/macos/GdkMacosCairoView.h
similarity index 54%
rename from gdk/quartz/gdkdevicemanager-core-quartz.h
rename to gdk/macos/GdkMacosCairoView.h
index 178e97c77d..1c28d83b39 100644
--- a/gdk/quartz/gdkdevicemanager-core-quartz.h
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -1,7 +1,7 @@
-/* gdkdevicemanager-quartz.h
+/* GdkMacosCairoView.h
*
- * Copyright (C) 2009 Carlos Garnacho
- * Copyright (C) 2010 Kristian Rietveld
+ * 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
@@ -15,28 +15,23 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
-#ifndef __GDK_QUARTZ_DEVICE_MANAGER_CORE__
-#define __GDK_QUARTZ_DEVICE_MANAGER_CORE__
+#include
-#include
-#include "gdkquartzdevicemanager-core.h"
+#import "GdkMacosBaseView.h"
-G_BEGIN_DECLS
+#define GDK_IS_MACOS_CAIRO_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoView class]])
-struct _GdkQuartzDeviceManagerCore
+@interface GdkMacosCairoView : GdkMacosBaseView
{
- GObject parent_object;
- GdkDevice *core_pointer;
- GdkDevice *core_keyboard;
-};
+ NSView *transparent;
+ GPtrArray *opaque;
+}
-struct _GdkQuartzDeviceManagerCoreClass
-{
- GObjectClass parent_class;
-};
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+ withDamage:(cairo_region_t *)region;
-G_END_DECLS
-
-#endif /* __GDK_QUARTZ_DEVICE_MANAGER__ */
+@end
diff --git a/gdk/macos/GdkMacosGLLayer.c b/gdk/macos/GdkMacosGLLayer.c
new file mode 100644
index 0000000000..a1ab55a4dc
--- /dev/null
+++ b/gdk/macos/GdkMacosGLLayer.c
@@ -0,0 +1,157 @@
+/* GdkMacosGLLayer.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/* Based on Chromium image_transport_surface_calayer_mac.mm
+ * See the BSD-style license above.
+ */
+
+#include "config.h"
+
+#include
+
+#import "GdkMacosGLLayer.h"
+
+@implementation GdkMacosGLLayer
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+-(id)initWithContext:(NSOpenGLContext *)shared
+{
+ [super init];
+ _shared = [shared retain];
+ return self;
+}
+
+-(void)dealloc
+{
+ [_shared release];
+ _shared = nil;
+
+ [super dealloc];
+}
+
+-(void)setContentsRect:(NSRect)bounds
+{
+ _pixelSize = bounds.size;
+ [super setContentsRect:bounds];
+}
+
+-(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
+{
+ return CGLRetainPixelFormat ([[_shared pixelFormat] CGLPixelFormatObj]);
+}
+
+-(CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
+{
+ CGLContextObj context = NULL;
+ CGLCreateContext (pixelFormat, [_shared CGLContextObj], &context);
+ return context;
+}
+
+-(BOOL)canDrawInCGLContext:(CGLContextObj)glContext
+ pixelFormat:(CGLPixelFormatObj)pixelFormat
+ forLayerTime:(CFTimeInterval)timeInterval
+ displayTime:(const CVTimeStamp*)timeStamp
+{
+ return YES;
+}
+
+-(void)drawInCGLContext:(CGLContextObj)glContext
+ pixelFormat:(CGLPixelFormatObj)pixelFormat
+ forLayerTime:(CFTimeInterval)timeInterval
+ displayTime:(const CVTimeStamp*)timeStamp
+{
+ if (_texture == 0)
+ return;
+
+ glClearColor (1, 0, 1, 1);
+ glClear (GL_COLOR_BUFFER_BIT);
+ GLint viewport[4] = {0, 0, 0, 0};
+ glGetIntegerv (GL_VIEWPORT, viewport);
+ NSSize viewportSize = NSMakeSize (viewport[2], viewport[3]);
+
+ /* Set the coordinate system to be one-to-one with pixels. */
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity ();
+ glOrtho (0, viewportSize.width, 0, viewportSize.height, -1, 1);
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity ();
+
+ /* Draw a fullscreen quad. */
+ glColor4f (1, 1, 1, 1);
+ glEnable (GL_TEXTURE_RECTANGLE_ARB);
+ glBindTexture (GL_TEXTURE_RECTANGLE_ARB, _texture);
+ glBegin (GL_QUADS);
+ {
+ glTexCoord2f (0, 0);
+ glVertex2f (0, 0);
+ glTexCoord2f (0, _pixelSize.height);
+ glVertex2f (0, _pixelSize.height);
+ glTexCoord2f (_pixelSize.width, _pixelSize.height);
+ glVertex2f (_pixelSize.width, _pixelSize.height);
+ glTexCoord2f (_pixelSize.width, 0);
+ glVertex2f (_pixelSize.width, 0);
+ }
+ glEnd ();
+ glBindTexture (0, _texture);
+ glDisable (GL_TEXTURE_RECTANGLE_ARB);
+ [super drawInCGLContext:glContext
+ pixelFormat:pixelFormat
+ forLayerTime:timeInterval
+ displayTime:timeStamp];
+}
+
+-(void)setTexture:(GLuint)texture
+{
+ _texture = texture;
+ [self setNeedsDisplay];
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+@end
diff --git a/gdk/quartz/gdkscreen-quartz.h b/gdk/macos/GdkMacosGLLayer.h
similarity index 56%
rename from gdk/quartz/gdkscreen-quartz.h
rename to gdk/macos/GdkMacosGLLayer.h
index 174139353b..dd90c1ca08 100644
--- a/gdk/quartz/gdkscreen-quartz.h
+++ b/gdk/macos/GdkMacosGLLayer.h
@@ -1,6 +1,6 @@
-/* gdkscreen-quartz.h
+/* GdkMacosGLLayer.h
*
- * Copyright (C) 2009,2010 Kristian Rietveld
+ * 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
@@ -14,36 +14,27 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
-#ifndef __GDK_QUARTZ_SCREEN__
-#define __GDK_QUARTZ_SCREEN__
+#include
+#include
-G_BEGIN_DECLS
+#define GDK_IS_MACOS_GL_LAYER(obj) ((obj) && [obj isKindOfClass:[GdkMacosGLLayer class]])
-struct _GdkQuartzScreen
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+@interface GdkMacosGLLayer : CAOpenGLLayer
{
- GObject parent_instance;
+ NSOpenGLContext *_shared;
+ GLuint _texture;
+ NSSize _pixelSize;
+}
- GdkDisplay *display;
+-(id)initWithContext:(NSOpenGLContext *)shared;
+-(void)setTexture:(GLuint)texture;
- /* Origin of "root window" in Cocoa coordinates */
- gint min_x;
- gint min_y;
+@end
- gint width;
- gint height;
-
- guint screen_changed_id;
-
- guint emit_monitors_changed : 1;
-};
-
-struct _GdkQuartzScreenClass
-{
- GObjectClass parent_class;
-};
-
-G_END_DECLS
-
-#endif /* __GDK_QUARTZ_SCREEN__ */
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c
new file mode 100644
index 0000000000..6ba8780bc8
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.c
@@ -0,0 +1,732 @@
+/* GdkMacosWindow.m
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+
+#import "GdkMacosBaseView.h"
+#import "GdkMacosCairoView.h"
+#import "GdkMacosWindow.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacospopupsurface-private.h"
+#include "gdkmacostoplevelsurface-private.h"
+
+#include "gdkmonitorprivate.h"
+#include "gdksurfaceprivate.h"
+
+@implementation GdkMacosWindow
+
+-(BOOL)windowShouldClose:(id)sender
+{
+ GdkDisplay *display;
+ GdkEvent *event;
+ GList *node;
+
+ display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+ event = gdk_delete_event_new (GDK_SURFACE (gdk_surface));
+ node = _gdk_event_queue_append (display, event);
+ _gdk_windowing_got_event (display, node, event,
+ _gdk_display_get_next_serial (display));
+
+ return NO;
+}
+
+-(void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+ if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+ _gdk_macos_toplevel_surface_detach_from_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+ else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+ _gdk_macos_popup_surface_detach_from_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+}
+
+-(void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MINIMIZED);
+}
+
+-(void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+ if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
+ _gdk_macos_toplevel_surface_attach_to_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
+ else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
+ _gdk_macos_popup_surface_attach_to_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
+
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MINIMIZED, 0);
+}
+
+-(void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_FOCUSED);
+ _gdk_macos_display_surface_became_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignKey:(NSNotification *)aNotification
+{
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_FOCUSED, 0);
+ _gdk_macos_display_surface_resigned_key ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+ if (![self isVisible])
+ {
+ /* Note: This is a hack needed because for unknown reasons, hidden
+ * windows get shown when clicking the dock icon when the application
+ * is not already active.
+ */
+ [self orderOut:nil];
+ return;
+ }
+
+ _gdk_macos_display_surface_became_main ([self gdkDisplay], gdk_surface);
+}
+
+-(void)windowDidResignMain:(NSNotification *)aNotification
+{
+ _gdk_macos_display_surface_resigned_main ([self gdkDisplay], gdk_surface);
+}
+
+/* Used in combination with NSLeftMouseUp in sendEvent to keep track
+ * of when the window is being moved with the mouse.
+ */
+-(void)windowWillMove:(NSNotification *)aNotification
+{
+ inMove = YES;
+}
+
+-(void)sendEvent:(NSEvent *)event
+{
+ NSEventType event_type = [event type];
+
+ switch ((int)event_type)
+ {
+ case NSEventTypeLeftMouseUp: {
+ GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
+ double time = ((double)[event timestamp]) * 1000.0;
+
+ _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time);
+
+ inManualMove = NO;
+ inManualResize = NO;
+ inMove = NO;
+
+ /* We need to deliver the event to the proper drag gestures or we
+ * will leave the window in inconsistent state that requires clicking
+ * in the window to cancel the gesture.
+ *
+ * TODO: Can we improve grab breaking to fix this?
+ */
+ _gdk_macos_display_send_button_event ([self gdkDisplay], event);
+
+ break;
+ }
+
+ case NSEventTypeLeftMouseDragged:
+ if ([self trackManualMove] || [self trackManualResize])
+ return;
+ break;
+
+ default:
+ break;
+ }
+
+ [super sendEvent:event];
+}
+
+-(BOOL)isInMove
+{
+ return inMove;
+}
+
+-(void)checkSendEnterNotify
+{
+ /* When a new window has been created, and the mouse is in the window
+ * area, we will not receive an NSEventTypeMouseEntered event.
+ * Therefore, we synthesize an enter notify event manually.
+ */
+ if (!initialPositionKnown)
+ {
+ initialPositionKnown = YES;
+
+ if (NSPointInRect ([NSEvent mouseLocation], [self frame]))
+ {
+ GdkMacosBaseView *view = (GdkMacosBaseView *)[self contentView];
+ NSEvent *event;
+
+ event = [NSEvent enterExitEventWithType: NSEventTypeMouseEntered
+ location: [self mouseLocationOutsideOfEventStream]
+ modifierFlags: 0
+ timestamp: [[NSApp currentEvent] timestamp]
+ windowNumber: [self windowNumber]
+ context: NULL
+ eventNumber: 0
+ trackingNumber: (NSInteger)[view trackingArea]
+ userData: nil];
+
+ [NSApp postEvent:event atStart:NO];
+ }
+ }
+}
+
+-(void)windowDidUnmaximize
+{
+ NSWindowStyleMask style_mask = [self styleMask];
+
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_SURFACE_STATE_MAXIMIZED, 0);
+
+ /* If we are using CSD, then we transitioned to an opaque
+ * window while we were maximized. Now we need to drop that
+ * as we are leaving maximized state.
+ */
+ if ((style_mask & NSWindowStyleMaskTitled) == 0 && [self isOpaque])
+ [self setOpaque:NO];
+}
+
+-(void)windowDidMove:(NSNotification *)aNotification
+{
+ GdkSurface *surface = GDK_SURFACE (gdk_surface);
+ gboolean maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+
+ /* In case the window is changed when maximized remove the maximized state */
+ if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+ [self windowDidUnmaximize];
+
+ _gdk_macos_surface_update_position (gdk_surface);
+ _gdk_macos_surface_reposition_children (gdk_surface);
+
+ [self checkSendEnterNotify];
+}
+
+-(void)windowDidResize:(NSNotification *)aNotification
+{
+ NSRect content_rect;
+ GdkSurface *surface;
+ GdkDisplay *display;
+ GdkEvent *event;
+ gboolean maximized;
+ GList *node;
+
+ surface = GDK_SURFACE (gdk_surface);
+ display = gdk_surface_get_display (surface);
+
+ content_rect = [self contentRectForFrameRect:[self frame]];
+ maximized = (surface->state & GDK_SURFACE_STATE_MAXIMIZED) != 0;
+
+ /* see same in windowDidMove */
+ if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
+ [self windowDidUnmaximize];
+
+ surface->width = content_rect.size.width;
+ surface->height = content_rect.size.height;
+
+ /* Certain resize operations (e.g. going fullscreen), also move the
+ * origin of the window.
+ */
+ _gdk_macos_surface_update_position (GDK_MACOS_SURFACE (surface));
+
+ [[self contentView] setFrame:NSMakeRect (0, 0, surface->width, surface->height)];
+
+ _gdk_surface_update_size (surface);
+
+ /* Synthesize a configure event */
+ event = gdk_configure_event_new (surface,
+ content_rect.size.width,
+ content_rect.size.height);
+ node = _gdk_event_queue_append (display, event);
+ _gdk_windowing_got_event (display, node, event,
+ _gdk_display_get_next_serial (display));
+
+ _gdk_macos_surface_reposition_children (gdk_surface);
+
+ [self checkSendEnterNotify];
+}
+
+-(id)initWithContentRect:(NSRect)contentRect
+ styleMask:(NSWindowStyleMask)styleMask
+ backing:(NSBackingStoreType)backingType
+ defer:(BOOL)flag
+ screen:(NSScreen *)screen
+{
+ GdkMacosCairoView *view;
+
+ self = [super initWithContentRect:contentRect
+ styleMask:styleMask
+ backing:backingType
+ defer:flag
+ screen:screen];
+
+ [self setAcceptsMouseMovedEvents:YES];
+ [self setDelegate:(id)self];
+ [self setReleasedWhenClosed:YES];
+
+ view = [[GdkMacosCairoView alloc] initWithFrame:contentRect];
+ [self setContentView:view];
+ [view release];
+
+ return self;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+ return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+ return GDK_IS_TOPLEVEL (gdk_surface);
+}
+
+-(void)showAndMakeKey:(BOOL)makeKey
+{
+ inShowOrHide = YES;
+
+ if (makeKey && [self canBecomeKeyWindow])
+ [self makeKeyAndOrderFront:nil];
+ else
+ [self orderFront:nil];
+
+ inShowOrHide = NO;
+
+ [self checkSendEnterNotify];
+}
+
+-(void)hide
+{
+ inShowOrHide = YES;
+ [self orderOut:nil];
+ inShowOrHide = NO;
+
+ initialPositionKnown = NO;
+}
+
+-(BOOL)trackManualMove
+{
+ NSRect windowFrame;
+ NSPoint currentLocation;
+ GdkMonitor *monitor;
+ GdkRectangle geometry;
+ GdkRectangle workarea;
+ int shadow_top = 0;
+ int shadow_left = 0;
+ int shadow_right = 0;
+ int shadow_bottom = 0;
+ GdkRectangle window_gdk;
+ GdkPoint pointer_position;
+ GdkPoint new_origin;
+
+ if (!inManualMove)
+ return NO;
+
+ /* Get our shadow so we can adjust the window position sans-shadow */
+ _gdk_macos_surface_get_shadow (gdk_surface,
+ &shadow_top,
+ &shadow_right,
+ &shadow_bottom,
+ &shadow_left);
+
+ windowFrame = [self frame];
+ currentLocation = [NSEvent mouseLocation];
+
+ /* Update the snapping geometry to match the current monitor */
+ monitor = _gdk_macos_display_get_monitor_at_display_coords ([self gdkDisplay],
+ currentLocation.x,
+ currentLocation.y);
+ gdk_monitor_get_geometry (monitor, &geometry);
+ gdk_monitor_get_workarea (monitor, &workarea);
+ _edge_snapping_set_monitor (&self->snapping, &geometry, &workarea);
+
+ /* Convert origins to GDK coordinates */
+ _gdk_macos_display_from_display_coords ([self gdkDisplay],
+ currentLocation.x,
+ currentLocation.y,
+ &pointer_position.x,
+ &pointer_position.y);
+ _gdk_macos_display_from_display_coords ([self gdkDisplay],
+ windowFrame.origin.x,
+ windowFrame.origin.y + windowFrame.size.height,
+ &window_gdk.x,
+ &window_gdk.y);
+ window_gdk.width = windowFrame.size.width;
+ window_gdk.height = windowFrame.size.height;
+
+ /* Subtract our shadowin from the window */
+ window_gdk.x += shadow_left;
+ window_gdk.y += shadow_top;
+ window_gdk.width = window_gdk.width - shadow_left - shadow_right;
+ window_gdk.height = window_gdk.height - shadow_top - shadow_bottom;
+
+ /* Now place things on the monitor */
+ _edge_snapping_motion (&self->snapping, &pointer_position, &window_gdk);
+
+ /* And add our shadow back to the frame */
+ window_gdk.x -= shadow_left;
+ window_gdk.y -= shadow_top;
+ window_gdk.width += shadow_left + shadow_right;
+ window_gdk.height += shadow_top + shadow_bottom;
+
+ /* Convert to quartz coordiantes */
+ _gdk_macos_display_to_display_coords ([self gdkDisplay],
+ window_gdk.x,
+ window_gdk.y + window_gdk.height,
+ &new_origin.x, &new_origin.y);
+ windowFrame.origin.x = new_origin.x;
+ windowFrame.origin.y = new_origin.y;
+
+ /* And now apply the frame to the window */
+ [self setFrameOrigin:NSMakePoint(new_origin.x, new_origin.y)];
+
+ return YES;
+}
+
+/* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler
+ * above will see the event or if it will be subjected to standard processing
+ * by GDK.
+*/
+-(BOOL)isInManualResizeOrMove
+{
+ return inManualResize || inManualMove;
+}
+
+-(void)beginManualMove
+{
+ NSPoint initialMoveLocation;
+ GdkPoint point;
+ GdkMonitor *monitor;
+ GdkRectangle geometry;
+ GdkRectangle area;
+ GdkRectangle workarea;
+
+ if (inMove || inManualMove || inManualResize)
+ return;
+
+ inManualMove = YES;
+
+ monitor = _gdk_macos_surface_get_best_monitor ([self gdkSurface]);
+ gdk_monitor_get_geometry (monitor, &geometry);
+ gdk_monitor_get_workarea (monitor, &workarea);
+
+ initialMoveLocation = [NSEvent mouseLocation];
+
+ _gdk_macos_display_from_display_coords ([self gdkDisplay],
+ initialMoveLocation.x,
+ initialMoveLocation.y,
+ &point.x,
+ &point.y);
+
+ area.x = gdk_surface->root_x;
+ area.y = gdk_surface->root_y;
+ area.width = GDK_SURFACE (gdk_surface)->width;
+ area.height = GDK_SURFACE (gdk_surface)->height;
+
+ _edge_snapping_init (&self->snapping,
+ &geometry,
+ &workarea,
+ &point,
+ &area);
+}
+
+-(BOOL)trackManualResize
+{
+ NSPoint mouse_location;
+ NSRect new_frame;
+ float mdx, mdy, dw, dh, dx, dy;
+ NSSize min_size;
+
+ if (!inManualResize || inTrackManualResize)
+ return NO;
+
+ inTrackManualResize = YES;
+
+ mouse_location = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+ mdx = initialResizeLocation.x - mouse_location.x;
+ mdy = initialResizeLocation.y - mouse_location.y;
+
+ /* Set how a mouse location delta translates to changes in width,
+ * height and position.
+ */
+ dw = dh = dx = dy = 0.0;
+ if (resizeEdge == GDK_SURFACE_EDGE_EAST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+ {
+ dw = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_NORTH ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST)
+ {
+ dh = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_SOUTH ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
+ {
+ dh = 1.0;
+ dy = -1.0;
+ }
+ if (resizeEdge == GDK_SURFACE_EDGE_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
+ resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST)
+ {
+ dw = 1.0;
+ dx = -1.0;
+ }
+
+ /* Apply changes to the frame captured when we started resizing */
+ new_frame = initialResizeFrame;
+ new_frame.origin.x += mdx * dx;
+ new_frame.origin.y += mdy * dy;
+ new_frame.size.width += mdx * dw;
+ new_frame.size.height += mdy * dh;
+
+ /* In case the resulting window would be too small reduce the
+ * change to both size and position.
+ */
+ min_size = [self contentMinSize];
+
+ if (new_frame.size.width < min_size.width)
+ {
+ if (dx)
+ new_frame.origin.x -= min_size.width - new_frame.size.width;
+ new_frame.size.width = min_size.width;
+ }
+
+ if (new_frame.size.height < min_size.height)
+ {
+ if (dy)
+ new_frame.origin.y -= min_size.height - new_frame.size.height;
+ new_frame.size.height = min_size.height;
+ }
+
+ /* We could also apply aspect ratio:
+ new_frame.size.height = new_frame.size.width / [self aspectRatio].width * [self aspectRatio].height;
+ */
+
+ [self setFrame:new_frame display:YES];
+
+ /* Let the resizing be handled by GTK+. */
+ if (g_main_context_pending (NULL))
+ g_main_context_iteration (NULL, FALSE);
+
+ inTrackManualResize = NO;
+
+ return YES;
+}
+
+-(void)beginManualResize:(GdkSurfaceEdge)edge
+{
+ if (inMove || inManualMove || inManualResize)
+ return;
+
+ inManualResize = YES;
+ resizeEdge = edge;
+
+ initialResizeFrame = [self frame];
+ initialResizeLocation = [self convertPointToScreen:[self mouseLocationOutsideOfEventStream]];
+}
+
+-(NSDragOperation)draggingEntered:(id )sender
+{
+ return NSDragOperationNone;
+}
+
+-(void)draggingEnded:(id )sender
+{
+}
+
+-(void)draggingExited:(id )sender
+{
+}
+
+-(NSDragOperation)draggingUpdated:(id )sender
+{
+ return NSDragOperationNone;
+}
+
+-(BOOL)performDragOperation:(id )sender
+{
+ return YES;
+}
+
+-(BOOL)wantsPeriodicDraggingUpdates
+{
+ return NO;
+}
+
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+}
+
+-(void)setStyleMask:(NSWindowStyleMask)styleMask
+{
+ gboolean was_fullscreen;
+ gboolean is_fullscreen;
+ gboolean was_opaque;
+ gboolean is_opaque;
+
+ was_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+ was_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+ [super setStyleMask:styleMask];
+
+ is_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
+ is_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
+
+ if (was_fullscreen != is_fullscreen)
+ _gdk_macos_surface_update_fullscreen_state (gdk_surface);
+
+ if (was_opaque != is_opaque)
+ {
+ [self setOpaque:is_opaque];
+
+ if (!is_opaque)
+ [self setBackgroundColor:[NSColor clearColor]];
+ }
+}
+
+-(NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
+{
+ GdkMacosSurface *surface = gdk_surface;
+ NSRect rect;
+ gint shadow_top;
+
+ /* Allow the window to move up "shadow_top" more than normally allowed
+ * by the default impl. This makes it possible to move windows with
+ * client side shadow right up to the screen's menu bar. */
+ _gdk_macos_surface_get_shadow (surface, &shadow_top, NULL, NULL, NULL);
+ rect = [super constrainFrameRect:frameRect toScreen:screen];
+ if (frameRect.origin.y > rect.origin.y)
+ rect.origin.y = MIN (frameRect.origin.y, rect.origin.y + shadow_top);
+
+ return rect;
+}
+
+-(NSRect)windowWillUseStandardFrame:(NSWindow *)nsWindow
+ defaultFrame:(NSRect)newFrame
+{
+ NSRect screenFrame = [[self screen] visibleFrame];
+ GdkMacosSurface *surface = gdk_surface;
+ gboolean maximized = GDK_SURFACE (surface)->state & GDK_SURFACE_STATE_MAXIMIZED;
+
+ if (!maximized)
+ return screenFrame;
+ else
+ return lastUnmaximizedFrame;
+}
+
+-(BOOL)windowShouldZoom:(NSWindow *)nsWindow
+ toFrame:(NSRect)newFrame
+{
+ GdkMacosSurface *surface = gdk_surface;
+ GdkSurfaceState state = GDK_SURFACE (surface)->state;
+
+ if (state & GDK_SURFACE_STATE_MAXIMIZED)
+ {
+ lastMaximizedFrame = newFrame;
+ [self windowDidUnmaximize];
+ }
+ else
+ {
+ lastUnmaximizedFrame = [nsWindow frame];
+ gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_SURFACE_STATE_MAXIMIZED);
+ }
+
+ inMaximizeTransition = YES;
+
+ return YES;
+}
+
+-(void)windowDidEndLiveResize:(NSNotification *)aNotification
+{
+ gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_SURFACE_STATE_MAXIMIZED;
+
+ inMaximizeTransition = NO;
+
+ /* Even if this is CSD, we want to be opaque while maximized
+ * to speed up compositing by allowing the display server to
+ * avoid costly blends.
+ */
+ if (maximized)
+ [self setOpaque:YES];
+}
+
+-(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
+{
+ return [[window screen] frame].size;
+}
+
+-(void)windowWillEnterFullScreen:(NSNotification *)aNotification
+{
+ lastUnfullscreenFrame = [self frame];
+}
+
+-(void)windowWillExitFullScreen:(NSNotification *)aNotification
+{
+ [self setFrame:lastUnfullscreenFrame display:YES];
+}
+
+-(void)windowDidExitFullScreen:(NSNotification *)aNotification
+{
+}
+
+-(void)windowDidChangeScreen:(NSNotification *)aNotification
+{
+ _gdk_macos_surface_monitor_changed (gdk_surface);
+}
+
+-(void)setGdkSurface:(GdkMacosSurface *)surface
+{
+ self->gdk_surface = surface;
+}
+
+-(void)setDecorated:(BOOL)decorated
+{
+ NSWindowStyleMask style_mask = [self styleMask];
+
+ [self setHasShadow:decorated];
+
+ if (decorated)
+ style_mask |= NSWindowStyleMaskTitled;
+ else
+ style_mask &= ~NSWindowStyleMaskTitled;
+
+ [self setStyleMask:style_mask];
+}
+
+-(GdkMacosSurface *)gdkSurface
+{
+ return self->gdk_surface;
+}
+
+-(GdkMacosDisplay *)gdkDisplay
+{
+ return GDK_MACOS_DISPLAY (GDK_SURFACE (self->gdk_surface)->display);
+}
+
+-(BOOL)movableByWindowBackground
+{
+ return NO;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h
new file mode 100644
index 0000000000..61f546a78b
--- /dev/null
+++ b/gdk/macos/GdkMacosWindow.h
@@ -0,0 +1,70 @@
+/* GdkMacosWindow.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ * Copyright © 2005-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#import
+#import
+
+#include
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacossurface.h"
+#include "edgesnapping.h"
+
+#define GDK_IS_MACOS_WINDOW(obj) ([obj isKindOfClass:[GdkMacosWindow class]])
+
+@interface GdkMacosWindow : NSWindow {
+ GdkMacosSurface *gdk_surface;
+
+ BOOL inMove;
+ BOOL inShowOrHide;
+ BOOL initialPositionKnown;
+
+ /* Manually triggered move/resize (not by the window manager) */
+ BOOL inManualMove;
+ BOOL inManualResize;
+ BOOL inTrackManualResize;
+ NSPoint initialResizeLocation;
+ NSRect initialResizeFrame;
+ GdkSurfaceEdge resizeEdge;
+
+ EdgeSnapping snapping;
+
+ NSRect lastUnmaximizedFrame;
+ NSRect lastMaximizedFrame;
+ NSRect lastUnfullscreenFrame;
+ BOOL inMaximizeTransition;
+}
+
+-(void)beginManualMove;
+-(void)beginManualResize:(GdkSurfaceEdge)edge;
+-(void)hide;
+-(BOOL)isInManualResizeOrMove;
+-(BOOL)isInMove;
+-(GdkMacosDisplay *)gdkDisplay;
+-(GdkMacosSurface *)gdkSurface;
+-(void)setGdkSurface:(GdkMacosSurface *)surface;
+-(void)setStyleMask:(NSWindowStyleMask)styleMask;
+-(void)showAndMakeKey:(BOOL)makeKey;
+-(BOOL)trackManualMove;
+-(BOOL)trackManualResize;
+-(void)setDecorated:(BOOL)decorated;
+
+@end
diff --git a/gdk/macos/edgesnapping.c b/gdk/macos/edgesnapping.c
new file mode 100644
index 0000000000..bf9347265a
--- /dev/null
+++ b/gdk/macos/edgesnapping.c
@@ -0,0 +1,229 @@
+/* edgesnapping.c
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkrectangle.h"
+#include "edgesnapping.h"
+
+#define LEAVE_THRESHOLD 3.0
+#define ENTER_THRESHOLD 2.0
+
+#define X1(r) ((r)->x)
+#define X2(r) ((r)->x + (r)->width)
+#define Y1(r) ((r)->y)
+#define Y2(r) ((r)->y + (r)->height)
+
+void
+_edge_snapping_init (EdgeSnapping *self,
+ const GdkRectangle *geometry,
+ const GdkRectangle *workarea,
+ const GdkPoint *pointer_position,
+ const GdkRectangle *window)
+{
+ g_assert (self != NULL);
+ g_assert (geometry != NULL);
+ g_assert (workarea != NULL);
+ g_assert (pointer_position != NULL);
+
+ self->geometry = *geometry;
+ self->workarea = *workarea;
+ self->last_pointer_position = *pointer_position;
+ self->pointer_offset_in_window.x = pointer_position->x - window->x;
+ self->pointer_offset_in_window.y = pointer_position->y - window->y;
+}
+
+static void
+edge_snapping_constrain_left (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window)
+{
+ if (change < 0)
+ {
+ if (X1 (window) < X1 (geometry) &&
+ X1 (window) > X1 (geometry) - LEAVE_THRESHOLD &&
+ ABS (change) < LEAVE_THRESHOLD)
+ window->x = geometry->x;
+ }
+
+ /* We don't constrain when returning from left edge */
+}
+
+static void
+edge_snapping_constrain_right (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window)
+{
+ if (change > 0)
+ {
+ if (X2 (window) > X2 (geometry) &&
+ X2 (window) < X2 (geometry) + LEAVE_THRESHOLD &&
+ ABS (change) < LEAVE_THRESHOLD)
+ window->x = X2 (geometry) - window->width;
+ }
+
+ /* We don't constrain when returning from right edge */
+}
+
+static void
+edge_snapping_constrain_top (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window)
+{
+ if (change < 0)
+ {
+ if (Y1 (window) < Y1 (geometry))
+ window->y = geometry->y;
+ }
+
+ /* We don't constrain when returning from top edge */
+}
+
+static void
+edge_snapping_constrain_bottom (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window)
+{
+ if (change > 0)
+ {
+ if (Y2 (window) > Y2 (geometry) &&
+ Y2 (window) < Y2 (geometry) + LEAVE_THRESHOLD &&
+ ABS (change) < LEAVE_THRESHOLD)
+ window->y = Y2 (geometry) - window->height;
+ }
+ else if (change < 0)
+ {
+ if (Y2 (window) < Y2 (geometry) &&
+ Y2 (window) > Y2 (geometry) - ENTER_THRESHOLD &&
+ ABS (change) < ENTER_THRESHOLD)
+ window->y = Y2 (geometry) - window->height;
+ }
+
+}
+
+static void
+edge_snapping_constrain_horizontal (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window)
+{
+ g_assert (self != NULL);
+ g_assert (geometry != NULL);
+ g_assert (window != NULL);
+ g_assert (change != 0);
+
+ if (ABS (X1 (geometry) - X1 (window)) < ABS (X2 (geometry)) - ABS (X2 (window)))
+ edge_snapping_constrain_left (self, change, geometry, window);
+ else
+ edge_snapping_constrain_right (self, change, geometry, window);
+}
+
+static void
+edge_snapping_constrain_vertical (EdgeSnapping *self,
+ int change,
+ const GdkRectangle *geometry,
+ GdkRectangle *window,
+ gboolean bottom_only)
+{
+ g_assert (self != NULL);
+ g_assert (geometry != NULL);
+ g_assert (window != NULL);
+ g_assert (change != 0);
+
+ if (!bottom_only &&
+ ABS (Y1 (geometry) - Y1 (window)) < ABS (Y2 (geometry)) - ABS (Y2 (window)))
+ edge_snapping_constrain_top (self, change, geometry, window);
+ else
+ edge_snapping_constrain_bottom (self, change, geometry, window);
+}
+
+void
+_edge_snapping_motion (EdgeSnapping *self,
+ const GdkPoint *pointer_position,
+ GdkRectangle *window)
+{
+ GdkRectangle new_window;
+ GdkRectangle overlap;
+ GdkPoint change;
+
+ g_assert (self != NULL);
+ g_assert (pointer_position != NULL);
+
+ change.x = pointer_position->x - self->last_pointer_position.x;
+ change.y = pointer_position->y - self->last_pointer_position.y;
+
+ self->last_pointer_position = *pointer_position;
+
+ window->x += change.x;
+ window->y += change.y;
+
+ new_window = *window;
+
+ /* First constrain horizontal */
+ if (change.x)
+ {
+ edge_snapping_constrain_horizontal (self, change.x, &self->workarea, &new_window);
+ if (gdk_rectangle_equal (&new_window, window))
+ edge_snapping_constrain_horizontal (self, change.x, &self->geometry, &new_window);
+ }
+
+ /* Now constrain veritcally */
+ if (change.y)
+ {
+ edge_snapping_constrain_vertical (self, change.y, &self->workarea, &new_window, FALSE);
+ if (new_window.y == window->y)
+ edge_snapping_constrain_vertical (self, change.y, &self->geometry, &new_window, TRUE);
+ }
+
+ /* If the window is not placed in the monitor at all, then we need to
+ * just move the window onto the new screen using the original offset
+ * of the pointer within the window.
+ */
+ if (!gdk_rectangle_intersect (&self->geometry, &new_window, &overlap))
+ {
+ new_window.x = pointer_position->x - self->pointer_offset_in_window.x;
+ new_window.y = pointer_position->y - self->pointer_offset_in_window.y;
+ }
+
+ /* And finally make sure we aren't underneath the top bar of the
+ * particular monitor.
+ */
+ if (Y1 (&new_window) < Y1 (&self->workarea))
+ new_window.y = self->workarea.y;
+
+ *window = new_window;
+}
+
+void
+_edge_snapping_set_monitor (EdgeSnapping *self,
+ const GdkRectangle *geometry,
+ const GdkRectangle *workarea)
+{
+ g_assert (self != NULL);
+ g_assert (geometry != NULL);
+ g_assert (workarea != NULL);
+
+ self->geometry = *geometry;
+ self->workarea = *workarea;
+}
diff --git a/gdk/macos/edgesnapping.h b/gdk/macos/edgesnapping.h
new file mode 100644
index 0000000000..8769ea7e47
--- /dev/null
+++ b/gdk/macos/edgesnapping.h
@@ -0,0 +1,50 @@
+/* edgesnapping.h
+ *
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __EDGE_SNAPPING_H__
+#define __EDGE_SNAPPING_H__
+
+#include "gdktypes.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ GdkRectangle geometry;
+ GdkRectangle workarea;
+ GdkPoint last_pointer_position;
+ GdkPoint pointer_offset_in_window;
+} EdgeSnapping;
+
+void _edge_snapping_init (EdgeSnapping *self,
+ const GdkRectangle *geometry,
+ const GdkRectangle *workarea,
+ const GdkPoint *pointer_position,
+ const GdkRectangle *window);
+void _edge_snapping_motion (EdgeSnapping *self,
+ const GdkPoint *pointer_position,
+ GdkRectangle *window);
+void _edge_snapping_set_monitor (EdgeSnapping *self,
+ const GdkRectangle *geometry,
+ const GdkRectangle *workarea);
+
+G_END_DECLS
+
+#endif /* __EDGE_SNAPPING_H__ */
diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c
new file mode 100644
index 0000000000..ba8731de5a
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.c
@@ -0,0 +1,254 @@
+/* gdkdisplaylinksource.c
+ *
+ * Copyright (C) 2015 Christian Hergert
+ *
+ * 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 .
+ *
+ * Authors:
+ * Christian Hergert
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "gdkdisplaylinksource.h"
+
+#include "gdkmacoseventsource-private.h"
+
+static gint64 host_to_frame_clock_time (gint64 host_time);
+
+static gboolean
+gdk_display_link_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ gint64 now;
+
+ now = g_source_get_time (source);
+
+ if (now < impl->presentation_time)
+ *timeout_ = (impl->presentation_time - now) / 1000L;
+ else
+ *timeout_ = -1;
+
+ return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_check (GSource *source)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+ gboolean ret = G_SOURCE_CONTINUE;
+
+ impl->needs_dispatch = FALSE;
+
+ if (callback != NULL)
+ ret = callback (user_data);
+
+ return ret;
+}
+
+static void
+gdk_display_link_source_finalize (GSource *source)
+{
+ GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+
+ CVDisplayLinkStop (impl->display_link);
+ CVDisplayLinkRelease (impl->display_link);
+}
+
+static GSourceFuncs gdk_display_link_source_funcs = {
+ gdk_display_link_source_prepare,
+ gdk_display_link_source_check,
+ gdk_display_link_source_dispatch,
+ gdk_display_link_source_finalize
+};
+
+void
+gdk_display_link_source_pause (GdkDisplayLinkSource *source)
+{
+ CVDisplayLinkStop (source->display_link);
+}
+
+void
+gdk_display_link_source_unpause (GdkDisplayLinkSource *source)
+{
+ CVDisplayLinkStart (source->display_link);
+}
+
+static CVReturn
+gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link,
+ const CVTimeStamp *inNow,
+ const CVTimeStamp *inOutputTime,
+ CVOptionFlags flagsIn,
+ CVOptionFlags *flagsOut,
+ void *user_data)
+{
+ GdkDisplayLinkSource *impl = user_data;
+ gint64 presentation_time;
+ gboolean needs_wakeup;
+
+ needs_wakeup = !g_atomic_int_get (&impl->needs_dispatch);
+
+ presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
+
+ impl->presentation_time = presentation_time;
+ impl->needs_dispatch = TRUE;
+
+ if (needs_wakeup)
+ {
+ NSEvent *event;
+
+ /* Post a message so we'll break out of the message loop.
+ *
+ * We don't use g_main_context_wakeup() here because that
+ * would result in sending a message to the pipe(2) fd in
+ * the select thread which would then send this message as
+ * well. Lots of extra work.
+ */
+ event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: 0
+ context: nil
+ subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+ data1: 0
+ data2: 0];
+
+ [NSApp postEvent:event atStart:YES];
+ }
+
+ return kCVReturnSuccess;
+}
+
+/**
+ * gdk_display_link_source_new:
+ *
+ * Creates a new #GSource that will activate the dispatch function upon
+ * notification from a CVDisplayLink that a new frame should be drawn.
+ *
+ * Effort is made to keep the transition from the high-priority
+ * CVDisplayLink thread into this GSource lightweight. However, this is
+ * somewhat non-ideal since the best case would be to do the drawing
+ * from the high-priority thread.
+ *
+ * Returns: (transfer full): A newly created #GSource.
+ */
+GSource *
+gdk_display_link_source_new (void)
+{
+ GdkDisplayLinkSource *impl;
+ GSource *source;
+ CVReturn ret;
+ double period;
+
+ source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl);
+ impl = (GdkDisplayLinkSource *)source;
+
+ /*
+ * Create our link based on currently connected displays.
+ * If there are multiple displays, this will be something that tries
+ * to work for all of them. In the future, we may want to explore multiple
+ * links based on the connected displays.
+ */
+ ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link);
+ if (ret != kCVReturnSuccess)
+ {
+ g_warning ("Failed to initialize CVDisplayLink!");
+ return source;
+ }
+
+ /*
+ * Determine our nominal period between frames.
+ */
+ period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link);
+ if (period == 0.0)
+ period = 1.0 / 60.0;
+ impl->refresh_interval = period * 1000000L;
+ impl->refresh_rate = 1.0 / period * 1000L;
+
+ /*
+ * Wire up our callback to be executed within the high-priority thread.
+ */
+ CVDisplayLinkSetOutputCallback (impl->display_link,
+ gdk_display_link_source_frame_cb,
+ source);
+
+ g_source_set_name (source, "[gdk] quartz frame clock");
+
+ return source;
+}
+
+static gint64
+host_to_frame_clock_time (gint64 host_time)
+{
+ static mach_timebase_info_data_t timebase_info;
+
+ /*
+ * NOTE:
+ *
+ * This code is taken from GLib to match g_get_monotonic_time().
+ */
+ if (G_UNLIKELY (timebase_info.denom == 0))
+ {
+ /* This is a fraction that we must use to scale
+ * mach_absolute_time() by in order to reach nanoseconds.
+ *
+ * We've only ever observed this to be 1/1, but maybe it could be
+ * 1000/1 if mach time is microseconds already, or 1/1000 if
+ * picoseconds. Try to deal nicely with that.
+ */
+ mach_timebase_info (&timebase_info);
+
+ /* We actually want microseconds... */
+ if (timebase_info.numer % 1000 == 0)
+ timebase_info.numer /= 1000;
+ else
+ timebase_info.denom *= 1000;
+
+ /* We want to make the numer 1 to avoid having to multiply... */
+ if (timebase_info.denom % timebase_info.numer == 0)
+ {
+ timebase_info.denom /= timebase_info.numer;
+ timebase_info.numer = 1;
+ }
+ else
+ {
+ /* We could just multiply by timebase_info.numer below, but why
+ * bother for a case that may never actually exist...
+ *
+ * Plus -- performing the multiplication would risk integer
+ * overflow. If we ever actually end up in this situation, we
+ * should more carefully evaluate the correct course of action.
+ */
+ mach_timebase_info (&timebase_info); /* Get a fresh copy for a better message */
+ g_error ("Got weird mach timebase info of %d/%d. Please file a bug against GLib.",
+ timebase_info.numer, timebase_info.denom);
+ }
+ }
+
+ return host_time / timebase_info.denom;
+}
diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h
new file mode 100644
index 0000000000..ed769b59f8
--- /dev/null
+++ b/gdk/macos/gdkdisplaylinksource.h
@@ -0,0 +1,49 @@
+/* gdkdisplaylinksource.h
+ *
+ * Copyright (C) 2015 Christian Hergert
+ *
+ * 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 .
+ *
+ * Authors:
+ * Christian Hergert
+ */
+
+#ifndef GDK_DISPLAY_LINK_SOURCE_H
+#define GDK_DISPLAY_LINK_SOURCE_H
+
+#include
+
+#include
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ GSource source;
+
+ CVDisplayLinkRef display_link;
+ gint64 refresh_interval;
+ guint refresh_rate;
+
+ volatile gint64 presentation_time;
+ volatile guint needs_dispatch;
+} GdkDisplayLinkSource;
+
+GSource *gdk_display_link_source_new (void);
+void gdk_display_link_source_pause (GdkDisplayLinkSource *source);
+void gdk_display_link_source_unpause (GdkDisplayLinkSource *source);
+
+G_END_DECLS
+
+#endif /* GDK_DISPLAY_LINK_SOURCE_H */
diff --git a/gdk/macos/gdkmacos.h b/gdk/macos/gdkmacos.h
new file mode 100644
index 0000000000..7d7d348752
--- /dev/null
+++ b/gdk/macos/gdkmacos.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_H__
+#define __GDK_MACOS_H__
+
+#include
+
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay.h"
+#include "gdkmacosglcontext.h"
+#include "gdkmacoskeymap.h"
+#include "gdkmacosmonitor.h"
+#include "gdkmacossurface.h"
+
+#endif /* __GDK_MACOS_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext-private.h b/gdk/macos/gdkmacoscairocontext-private.h
new file mode 100644
index 0000000000..1afede8591
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__
+#define __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__
+
+#include "gdkcairocontextprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosCairoContext GdkMacosCairoContext;
+typedef struct _GdkMacosCairoContextClass GdkMacosCairoContextClass;
+
+#define GDK_TYPE_MACOS_CAIRO_CONTEXT (_gdk_macos_cairo_context_get_type())
+#define GDK_MACOS_CAIRO_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_CAIRO_CONTEXT, GdkMacosCairoContext))
+#define GDK_IS_MACOS_CAIRO_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_CAIRO_CONTEXT))
+
+GType _gdk_macos_cairo_context_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CAIRO_CONTEXT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c
new file mode 100644
index 0000000000..e70d2bf00f
--- /dev/null
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright © 2016 Benjamin Otte
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkconfig.h"
+
+#include
+#include
+
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosCairoContext
+{
+ GdkCairoContext parent_instance;
+
+ cairo_surface_t *window_surface;
+};
+
+struct _GdkMacosCairoContextClass
+{
+ GdkCairoContextClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT)
+
+static cairo_surface_t *
+create_cairo_surface_for_surface (GdkSurface *surface)
+{
+ cairo_surface_t *cairo_surface;
+ int scale;
+ int width;
+ int height;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ scale = gdk_surface_get_scale_factor (surface);
+ width = scale * gdk_surface_get_width (surface);
+ height = scale * gdk_surface_get_height (surface);
+
+ cairo_surface = cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+ if (cairo_surface != NULL)
+ cairo_surface_set_device_scale (cairo_surface, scale, scale);
+
+ return cairo_surface;
+}
+
+static cairo_t *
+_gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)cairo_context;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+ return cairo_create (self->window_surface);
+}
+
+static void
+_gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
+ cairo_region_t *region)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+ GdkSurface *surface;
+ NSWindow *nswindow;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+ surface = gdk_draw_context_get_surface (draw_context);
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+ if (self->window_surface == NULL)
+ {
+ self->window_surface = create_cairo_surface_for_surface (surface);
+ }
+ else
+ {
+ if (![nswindow isOpaque])
+ {
+ cairo_t *cr = cairo_create (self->window_surface);
+ gdk_cairo_region (cr, region);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+ }
+ }
+}
+
+static void
+_gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context,
+ cairo_region_t *painted)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+ GdkSurface *surface;
+ NSView *nsview;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+ g_assert (self->window_surface != NULL);
+
+ surface = gdk_draw_context_get_surface (draw_context);
+ nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface));
+
+ if (GDK_IS_MACOS_CAIRO_VIEW (nsview))
+ [(GdkMacosCairoView *)nsview setCairoSurface:self->window_surface
+ withDamage:painted];
+}
+
+static void
+_gdk_macos_cairo_context_surface_resized (GdkDrawContext *draw_context)
+{
+ GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+
+ g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
+
+ g_clear_pointer (&self->window_surface, cairo_surface_destroy);
+}
+
+static void
+_gdk_macos_cairo_context_class_init (GdkMacosCairoContextClass *klass)
+{
+ GdkCairoContextClass *cairo_context_class = GDK_CAIRO_CONTEXT_CLASS (klass);
+ GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+ draw_context_class->begin_frame = _gdk_macos_cairo_context_begin_frame;
+ draw_context_class->end_frame = _gdk_macos_cairo_context_end_frame;
+ draw_context_class->surface_resized = _gdk_macos_cairo_context_surface_resized;
+
+ cairo_context_class->cairo_create = _gdk_macos_cairo_context_cairo_create;
+}
+
+static void
+_gdk_macos_cairo_context_init (GdkMacosCairoContext *self)
+{
+}
diff --git a/gdk/macos/gdkmacosclipboard-private.h b/gdk/macos/gdkmacosclipboard-private.h
new file mode 100644
index 0000000000..ab37d707a5
--- /dev/null
+++ b/gdk/macos/gdkmacosclipboard-private.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_CLIPBOARD_PRIVATE_H__
+#define __GDK_MACOS_CLIPBOARD_PRIVATE_H__
+
+#include
+
+#include "gdkclipboardprivate.h"
+#include "gdkmacosdisplay-private.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_CLIPBOARD (_gdk_macos_clipboard_get_type())
+
+G_DECLARE_FINAL_TYPE (GdkMacosClipboard, _gdk_macos_clipboard, GDK, MACOS_CLIPBOARD, GdkClipboard)
+
+GdkClipboard *_gdk_macos_clipboard_new (GdkMacosDisplay *display);
+void _gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self);
+NSPasteboardType _gdk_macos_clipboard_to_ns_type (const char *mime_type,
+ NSPasteboardType *alternate);
+const char *_gdk_macos_clipboard_from_ns_type (NSPasteboardType ns_type);
+
+@interface GdkMacosClipboardDataProvider : NSObject
+{
+ GCancellable *cancellable;
+ GdkClipboard *clipboard;
+ char **mimeTypes;
+}
+
+-(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
+-(NSArray *)types;
+
+@end
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_CLIPBOARD_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosclipboard.c b/gdk/macos/gdkmacosclipboard.c
new file mode 100644
index 0000000000..7297045b76
--- /dev/null
+++ b/gdk/macos/gdkmacosclipboard.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+
+#include "gdkmacosclipboard-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosClipboard
+{
+ GdkClipboard parent_instance;
+ NSPasteboard *pasteboard;
+ NSInteger last_change_count;
+};
+
+typedef struct
+{
+ GMemoryOutputStream *stream;
+ NSPasteboardItem *item;
+ NSPasteboardType type;
+ GMainContext *main_context;
+ guint done : 1;
+} WriteRequest;
+
+G_DEFINE_TYPE (GdkMacosClipboard, _gdk_macos_clipboard, GDK_TYPE_CLIPBOARD)
+
+static void
+write_request_free (WriteRequest *wr)
+{
+ g_clear_pointer (&wr->main_context, g_main_context_unref);
+ g_clear_object (&wr->stream);
+ [wr->item release];
+ g_slice_free (WriteRequest, wr);
+}
+
+const char *
+_gdk_macos_clipboard_from_ns_type (NSPasteboardType type)
+{
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+
+ if ([type isEqualToString:NSPasteboardTypeString] ||
+ [type isEqualToString:NSStringPboardType])
+ return g_intern_string ("text/plain;charset=utf-8");
+ else if ([type isEqualToString:NSPasteboardTypeURL] ||
+ [type isEqualToString:NSPasteboardTypeFileURL])
+ return g_intern_string ("text/uri-list");
+ else if ([type isEqualToString:NSPasteboardTypeColor])
+ return g_intern_string ("application/x-color");
+ else if ([type isEqualToString:NSPasteboardTypeTIFF])
+ return g_intern_string ("image/tiff");
+ else if ([type isEqualToString:NSPasteboardTypePNG])
+ return g_intern_string ("image/png");
+
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ return NULL;
+}
+
+NSPasteboardType
+_gdk_macos_clipboard_to_ns_type (const char *mime_type,
+ NSPasteboardType *alternate)
+{
+ if (alternate)
+ *alternate = NULL;
+
+ if (g_strcmp0 (mime_type, "text/plain;charset=utf-8") == 0)
+ {
+ return NSPasteboardTypeString;
+ }
+ else if (g_strcmp0 (mime_type, "text/uri-list") == 0)
+ {
+ if (alternate)
+ *alternate = NSPasteboardTypeURL;
+ return NSPasteboardTypeFileURL;
+ }
+ else if (g_strcmp0 (mime_type, "application/x-color") == 0)
+ {
+ return NSPasteboardTypeColor;
+ }
+ else if (g_strcmp0 (mime_type, "image/tiff") == 0)
+ {
+ return NSPasteboardTypeTIFF;
+ }
+ else if (g_strcmp0 (mime_type, "image/png") == 0)
+ {
+ return NSPasteboardTypePNG;
+ }
+
+ return nil;
+}
+
+static void
+populate_content_formats (GdkContentFormatsBuilder *builder,
+ NSPasteboardType type)
+{
+ const char *mime_type;
+
+ g_return_if_fail (builder != NULL);
+ g_return_if_fail (type != NULL);
+
+ mime_type = _gdk_macos_clipboard_from_ns_type (type);
+
+ if (mime_type != NULL)
+ gdk_content_formats_builder_add_mime_type (builder, mime_type);
+}
+
+static GdkContentFormats *
+load_offer_formats (GdkMacosClipboard *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkContentFormatsBuilder *builder;
+ GdkContentFormats *formats;
+
+ g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+
+ builder = gdk_content_formats_builder_new ();
+ for (NSPasteboardType type in [self->pasteboard types])
+ populate_content_formats (builder, type);
+ formats = gdk_content_formats_builder_free_to_formats (builder);
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return g_steal_pointer (&formats);
+}
+
+static void
+_gdk_macos_clipboard_load_contents (GdkMacosClipboard *self)
+{
+ GdkContentFormats *formats;
+ NSInteger change_count;
+
+ g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+
+ change_count = [self->pasteboard changeCount];
+
+ formats = load_offer_formats (self);
+ gdk_clipboard_claim_remote (GDK_CLIPBOARD (self), formats);
+ gdk_content_formats_unref (formats);
+
+ self->last_change_count = change_count;
+}
+
+static GInputStream *
+create_stream_from_nsdata (NSData *data)
+{
+ const guint8 *bytes = [data bytes];
+ gsize len = [data length];
+
+ return g_memory_input_stream_new_from_data (g_memdup (bytes, len), len, g_free);
+}
+
+static void
+_gdk_macos_clipboard_read_async (GdkClipboard *clipboard,
+ GdkContentFormats *formats,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosClipboard *self = (GdkMacosClipboard *)clipboard;
+ GdkContentFormats *offer_formats = NULL;
+ const char *mime_type;
+ GInputStream *stream = NULL;
+ GTask *task = NULL;
+
+ g_assert (GDK_IS_MACOS_CLIPBOARD (self));
+ g_assert (formats != NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, _gdk_macos_clipboard_read_async);
+ g_task_set_priority (task, io_priority);
+
+ offer_formats = load_offer_formats (GDK_MACOS_CLIPBOARD (clipboard));
+ mime_type = gdk_content_formats_match_mime_type (formats, offer_formats);
+
+ if (mime_type == NULL)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "%s",
+ _("No compatible transfer format found"));
+ goto cleanup;
+ }
+
+ if (strcmp (mime_type, "text/plain;charset=utf-8") == 0)
+ {
+ NSString *nsstr = [self->pasteboard stringForType:NSPasteboardTypeString];
+
+ if (nsstr != NULL)
+ {
+ const char *str = [nsstr UTF8String];
+ stream = g_memory_input_stream_new_from_data (g_strdup (str),
+ strlen (str) + 1,
+ g_free);
+ }
+ }
+ else if (strcmp (mime_type, "text/uri-list") == 0)
+ {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+
+ if ([[self->pasteboard types] containsObject:NSPasteboardTypeFileURL])
+ {
+ GString *str = g_string_new (NULL);
+ NSArray *files = [self->pasteboard propertyListForType:NSFilenamesPboardType];
+ gsize n_files = [files count];
+ char *data;
+ guint len;
+
+ for (gsize i = 0; i < n_files; ++i)
+ {
+ NSString* uriString = [files objectAtIndex:i];
+ uriString = [@"file://" stringByAppendingString:uriString];
+ uriString = [uriString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+
+ g_string_append_printf (str,
+ "%s\r\n",
+ [uriString cStringUsingEncoding:NSUTF8StringEncoding]);
+ }
+
+ len = str->len;
+ data = g_string_free (str, FALSE);
+ stream = g_memory_input_stream_new_from_data (data, len, g_free);
+ }
+
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ }
+ else if (strcmp (mime_type, "application/x-color") == 0)
+ {
+ NSColorSpace *colorspace;
+ NSColor *nscolor;
+ guint16 color[4];
+
+ colorspace = [NSColorSpace genericRGBColorSpace];
+ nscolor = [[NSColor colorFromPasteboard:self->pasteboard]
+ colorUsingColorSpace:colorspace];
+
+ color[0] = 0xffff * [nscolor redComponent];
+ color[1] = 0xffff * [nscolor greenComponent];
+ color[2] = 0xffff * [nscolor blueComponent];
+ color[3] = 0xffff * [nscolor alphaComponent];
+
+ stream = g_memory_input_stream_new_from_data (g_memdup (&color, sizeof color),
+ sizeof color,
+ g_free);
+ }
+ else if (strcmp (mime_type, "image/tiff") == 0)
+ {
+ NSData *data = [self->pasteboard dataForType:NSPasteboardTypeTIFF];
+ stream = create_stream_from_nsdata (data);
+ }
+ else if (strcmp (mime_type, "image/png") == 0)
+ {
+ NSData *data = [self->pasteboard dataForType:NSPasteboardTypePNG];
+ stream = create_stream_from_nsdata (data);
+ }
+
+ if (stream != NULL)
+ {
+ g_task_set_task_data (task, g_strdup (mime_type), g_free);
+ g_task_return_pointer (task, g_steal_pointer (&stream), g_object_unref);
+ }
+ else
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Failed to decode contents with mime-type of '%s'"),
+ mime_type);
+ }
+
+cleanup:
+ g_clear_object (&task);
+ g_clear_pointer (&offer_formats, gdk_content_formats_unref);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static GInputStream *
+_gdk_macos_clipboard_read_finish (GdkClipboard *clipboard,
+ GAsyncResult *result,
+ const char **out_mime_type,
+ GError **error)
+{
+ GTask *task = (GTask *)result;
+
+ g_assert (GDK_IS_MACOS_CLIPBOARD (clipboard));
+ g_assert (G_IS_TASK (task));
+
+ if (out_mime_type != NULL)
+ *out_mime_type = g_strdup (g_task_get_task_data (task));
+
+ return g_task_propagate_pointer (task, error);
+}
+
+static void
+_gdk_macos_clipboard_send_to_pasteboard (GdkMacosClipboard *self,
+ GdkContentProvider *content)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosClipboardDataProvider *dataProvider;
+ GdkContentFormats *serializable;
+ NSPasteboardItem *item;
+ const char * const *mime_types;
+ gsize n_mime_types;
+
+ g_return_if_fail (GDK_IS_MACOS_CLIPBOARD (self));
+ g_return_if_fail (GDK_IS_CONTENT_PROVIDER (content));
+
+ serializable = gdk_content_provider_ref_storable_formats (content);
+ serializable = gdk_content_formats_union_serialize_mime_types (serializable);
+ mime_types = gdk_content_formats_get_mime_types (serializable, &n_mime_types);
+
+ dataProvider = [[GdkMacosClipboardDataProvider alloc] initClipboard:GDK_CLIPBOARD (self)
+ mimetypes:mime_types];
+ item = [[NSPasteboardItem alloc] init];
+ [item setDataProvider:dataProvider forTypes:[dataProvider types]];
+
+ [self->pasteboard clearContents];
+ if ([self->pasteboard writeObjects:[NSArray arrayWithObject:item]] == NO)
+ g_warning ("Failed to write object to pasteboard");
+
+ self->last_change_count = [self->pasteboard changeCount];
+
+ g_clear_pointer (&serializable, gdk_content_formats_unref);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static gboolean
+_gdk_macos_clipboard_claim (GdkClipboard *clipboard,
+ GdkContentFormats *formats,
+ gboolean local,
+ GdkContentProvider *provider)
+{
+ GdkMacosClipboard *self = (GdkMacosClipboard *)clipboard;
+ gboolean ret;
+
+ g_assert (GDK_IS_CLIPBOARD (clipboard));
+ g_assert (formats != NULL);
+ g_assert (!provider || GDK_IS_CONTENT_PROVIDER (provider));
+
+ ret = GDK_CLIPBOARD_CLASS (_gdk_macos_clipboard_parent_class)->claim (clipboard, formats, local, provider);
+
+ if (local)
+ _gdk_macos_clipboard_send_to_pasteboard (self, provider);
+
+ return ret;
+}
+
+static void
+_gdk_macos_clipboard_constructed (GObject *object)
+{
+ GdkMacosClipboard *self = (GdkMacosClipboard *)object;
+
+ if (self->pasteboard == nil)
+ self->pasteboard = [[NSPasteboard generalPasteboard] retain];
+
+ G_OBJECT_CLASS (_gdk_macos_clipboard_parent_class)->constructed (object);
+}
+
+static void
+_gdk_macos_clipboard_finalize (GObject *object)
+{
+ GdkMacosClipboard *self = (GdkMacosClipboard *)object;
+
+ if (self->pasteboard != nil)
+ {
+ [self->pasteboard release];
+ self->pasteboard = nil;
+ }
+
+ G_OBJECT_CLASS (_gdk_macos_clipboard_parent_class)->finalize (object);
+}
+
+static void
+_gdk_macos_clipboard_class_init (GdkMacosClipboardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (klass);
+
+ object_class->constructed = _gdk_macos_clipboard_constructed;
+ object_class->finalize = _gdk_macos_clipboard_finalize;
+
+ clipboard_class->claim = _gdk_macos_clipboard_claim;
+ clipboard_class->read_async = _gdk_macos_clipboard_read_async;
+ clipboard_class->read_finish = _gdk_macos_clipboard_read_finish;
+}
+
+static void
+_gdk_macos_clipboard_init (GdkMacosClipboard *self)
+{
+}
+
+GdkClipboard *
+_gdk_macos_clipboard_new (GdkMacosDisplay *display)
+{
+ GdkMacosClipboard *self;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ self = g_object_new (GDK_TYPE_MACOS_CLIPBOARD,
+ "display", display,
+ NULL);
+
+ _gdk_macos_clipboard_load_contents (self);
+
+ return GDK_CLIPBOARD (self);
+}
+
+void
+_gdk_macos_clipboard_check_externally_modified (GdkMacosClipboard *self)
+{
+ g_return_if_fail (GDK_IS_MACOS_CLIPBOARD (self));
+
+ if ([self->pasteboard changeCount] != self->last_change_count)
+ _gdk_macos_clipboard_load_contents (self);
+}
+
+@implementation GdkMacosClipboardDataProvider
+
+-(id)initClipboard:(GdkClipboard *)gdkClipboard mimetypes:(const char * const *)mime_types;
+{
+ [super init];
+
+ self->mimeTypes = g_strdupv ((char **)mime_types);
+ self->clipboard = g_object_ref (gdkClipboard);
+
+ return self;
+}
+
+-(void)dealloc
+{
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_pointer (&self->mimeTypes, g_strfreev);
+ g_clear_object (&self->clipboard);
+ g_clear_object (&self->cancellable);
+
+ [super dealloc];
+}
+
+-(void)pasteboardFinishedWithDataProvider:(NSPasteboard *)pasteboard
+{
+ g_clear_object (&self->clipboard);
+}
+
+-(NSArray *)types
+{
+ NSMutableArray *ret = [[NSMutableArray alloc] init];
+
+ for (guint i = 0; self->mimeTypes[i]; i++)
+ {
+ const char *mime_type = self->mimeTypes[i];
+ NSPasteboardType type;
+ NSPasteboardType alternate = nil;
+
+ if ((type = _gdk_macos_clipboard_to_ns_type (mime_type, &alternate)))
+ {
+ [ret addObject:type];
+ if (alternate)
+ [ret addObject:alternate];
+ }
+ }
+
+ return g_steal_pointer (&ret);
+}
+
+static void
+on_data_ready_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkClipboard *clipboard = (GdkClipboard *)object;
+ WriteRequest *wr = user_data;
+ GError *error = NULL;
+ NSData *data = nil;
+
+ g_assert (GDK_IS_CLIPBOARD (clipboard));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (wr != NULL);
+ g_assert (G_IS_MEMORY_OUTPUT_STREAM (wr->stream));
+ g_assert ([wr->item isKindOfClass:[NSPasteboardItem class]]);
+
+ if (gdk_clipboard_write_finish (clipboard, result, &error))
+ {
+ gsize size;
+ gpointer bytes;
+
+ g_output_stream_close (G_OUTPUT_STREAM (wr->stream), NULL, NULL);
+
+ size = g_memory_output_stream_get_size (wr->stream);
+ bytes = g_memory_output_stream_steal_data (wr->stream);
+ data = [[NSData alloc] initWithBytesNoCopy:bytes
+ length:size
+ deallocator:^(void *alloc, NSUInteger length) { g_free (alloc); }];
+ }
+ else
+ {
+ g_warning ("Failed to serialize clipboard contents: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ [wr->item setData:data forType:wr->type];
+
+ wr->done = TRUE;
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+-(void) pasteboard:(NSPasteboard *)pasteboard
+ item:(NSPasteboardItem *)item
+ provideDataForType:(NSPasteboardType)type
+{
+ const char *mime_type = _gdk_macos_clipboard_from_ns_type (type);
+ GMainContext *main_context = g_main_context_default ();
+ WriteRequest *wr;
+
+ if (self->clipboard == NULL || mime_type == NULL)
+ {
+ [item setData:[NSData data] forType:type];
+ return;
+ }
+
+ wr = g_slice_new0 (WriteRequest);
+ wr->item = [item retain];
+ wr->stream = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ());
+ wr->type = type;
+ wr->main_context = g_main_context_ref (main_context);
+ wr->done = FALSE;
+
+ gdk_clipboard_write_async (self->clipboard,
+ mime_type,
+ G_OUTPUT_STREAM (wr->stream),
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ on_data_ready_cb,
+ wr);
+
+ /* We're forced to provide data synchronously via this API
+ * so we must block on the main loop. Using another main loop
+ * than the default tends to get us locked up here, so that is
+ * what we'll do for now.
+ */
+ while (!wr->done)
+ g_main_context_iteration (wr->main_context, TRUE);
+
+ write_request_free (wr);
+}
+
+@end
diff --git a/gdk/quartz/gdkdnd-quartz.h b/gdk/macos/gdkmacoscursor-private.h
similarity index 62%
rename from gdk/quartz/gdkdnd-quartz.h
rename to gdk/macos/gdkmacoscursor-private.h
index 990904e760..b3e5bac70d 100644
--- a/gdk/quartz/gdkdnd-quartz.h
+++ b/gdk/macos/gdkmacoscursor-private.h
@@ -1,7 +1,7 @@
-/* gdkdnd-quartz.h
+/* gdkmacoscursor-private.h
*
- * Copyright (C) 2005 Imendio AB
- * Copyright (C) 2010 Kristian Rietveld
+ * 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
@@ -17,29 +17,16 @@
* License along with this library. If not, see .
*/
-#ifndef __GDK_QUARTZ_DND__
-#define __GDK_QUARTZ_DND__
-
-#include
-#include "gdkquartzdnd.h"
+#ifndef __GDK_MACOS_CURSOR_PRIVATE_H__
+#define __GDK_MACOS_CURSOR_PRIVATE_H__
#include
+#include
G_BEGIN_DECLS
-struct _GdkQuartzDragContext
-{
- GdkDragContext context;
-
- id dragging_info;
- GdkDevice *device;
-};
-
-struct _GdkQuartzDragContextClass
-{
- GdkDragContextClass context_class;
-};
+NSCursor *_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor);
G_END_DECLS
-#endif /* __GDK_QUARTZ_DND__ */
+#endif /* __GDK_MACOS_CURSOR_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacoscursor.c b/gdk/macos/gdkmacoscursor.c
new file mode 100644
index 0000000000..a6da5c922b
--- /dev/null
+++ b/gdk/macos/gdkmacoscursor.c
@@ -0,0 +1,181 @@
+/* gdkcursor-macos.c
+ *
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ */
+
+#include "config.h"
+
+#include
+
+#include "gdkmacoscursor-private.h"
+
+/* OS X only exports a number of cursor types in its public NSCursor interface.
+ * By overriding the private _coreCursorType method, we can tell OS X to load
+ * one of its internal cursors instead (since cursor images are loaded on demand
+ * instead of in advance). WebKit does this too.
+ */
+
+@interface gdkCoreCursor : NSCursor {
+@private
+ int type;
+ BOOL override;
+}
+@end
+
+@implementation gdkCoreCursor
+
+- (long long)_coreCursorType
+{
+ if (self->override)
+ return self->type;
+ return [super _coreCursorType];
+}
+
+#define CUSTOM_CURSOR_CTOR(name, id) \
+ + (gdkCoreCursor *)name \
+ { \
+ gdkCoreCursor *obj; \
+ obj = [self new]; \
+ if (obj) { \
+ obj->override = YES; \
+ obj->type = id; \
+ } \
+ return obj; \
+ }
+CUSTOM_CURSOR_CTOR(gdkHelpCursor, 40)
+CUSTOM_CURSOR_CTOR(gdkProgressCursor, 4)
+/* TODO OS X doesn't seem to have a way to get this. There is an undocumented
+ * method +[NSCursor _waitCursor], but it doesn't actually return this cursor,
+ * but rather some odd low-quality non-animating version of this cursor. Use
+ * the progress cursor instead for now.
+ */
+CUSTOM_CURSOR_CTOR(gdkWaitCursor, 4)
+CUSTOM_CURSOR_CTOR(gdkAliasCursor, 2)
+CUSTOM_CURSOR_CTOR(gdkMoveCursor, 39)
+/* TODO OS X doesn't seem to provide one; copy the move cursor for now
+ * since it looks similar to what we want. */
+CUSTOM_CURSOR_CTOR(gdkAllScrollCursor, 39)
+CUSTOM_CURSOR_CTOR(gdkNEResizeCursor, 29)
+CUSTOM_CURSOR_CTOR(gdkNWResizeCursor, 33)
+CUSTOM_CURSOR_CTOR(gdkSEResizeCursor, 35)
+CUSTOM_CURSOR_CTOR(gdkSWResizeCursor, 37)
+CUSTOM_CURSOR_CTOR(gdkEWResizeCursor, 28)
+CUSTOM_CURSOR_CTOR(gdkNSResizeCursor, 32)
+CUSTOM_CURSOR_CTOR(gdkNESWResizeCursor, 30)
+CUSTOM_CURSOR_CTOR(gdkNWSEResizeCursor, 34)
+CUSTOM_CURSOR_CTOR(gdkZoomInCursor, 42)
+CUSTOM_CURSOR_CTOR(gdkZoomOutCursor, 43)
+#undef CUSTOM_CURSOR_CTOR
+
+@end
+
+struct CursorsByName {
+ const gchar *name;
+ NSString *selector;
+};
+
+static const struct CursorsByName cursors_by_name[] = {
+ /* Link & Status */
+ { "context-menu", @"contextualMenuCursor" },
+ { "help", @"gdkHelpCursor" },
+ { "pointer", @"pointingHandCursor" },
+ { "progress", @"gdkProgressCursor" },
+ { "wait", @"gdkWaitCursor" },
+ /* Selection */
+ { "cell", @"crosshairCursor" },
+ { "crosshair", @"crosshairCursor" },
+ { "text", @"IBeamCursor" },
+ { "vertical-text", @"IBeamCursorForVerticalLayout" },
+ /* Drag & Drop */
+ { "alias", @"gdkAliasCursor" },
+ { "copy", @"dragCopyCursor" },
+ { "move", @"gdkMoveCursor" },
+ { "no-drop", @"operationNotAllowedCursor" },
+ { "not-allowed", @"operationNotAllowedCursor" },
+ { "grab", @"openHandCursor" },
+ { "grabbing", @"closedHandCursor" },
+ /* Resize & Scrolling */
+ { "all-scroll", @"gdkAllScrollCursor" },
+ { "col-resize", @"resizeLeftRightCursor" },
+ { "row-resize", @"resizeUpDownCursor" },
+
+ /* Undocumented cursors to match native resizing */
+ { "e-resize", @"_windowResizeEastWestCursor" },
+ { "w-resize", @"_windowResizeEastWestCursor" },
+ { "n-resize", @"_windowResizeNorthSouthCursor" },
+ { "s-resize", @"_windowResizeNorthSouthCursor" },
+
+ { "ne-resize", @"gdkNEResizeCursor" },
+ { "nw-resize", @"gdkNWResizeCursor" },
+ { "se-resize", @"gdkSEResizeCursor" },
+ { "sw-resize", @"gdkSWResizeCursor" },
+ { "ew-resize", @"gdkEWResizeCursor" },
+ { "ns-resize", @"gdkNSResizeCursor" },
+ { "nesw-resize", @"gdkNESWResizeCursor" },
+ { "nwse-resize", @"gdkNWSEResizeCursor" },
+ /* Zoom */
+ { "zoom-in", @"gdkZoomInCursor" },
+ { "zoom-out", @"gdkZoomOutCursor" },
+};
+
+static NSCursor *
+create_blank_cursor (void)
+{
+ NSCursor *nscursor;
+ NSImage *nsimage;
+ NSSize size = { 1.0, 1.0 };
+
+ nsimage = [[NSImage alloc] initWithSize:size];
+ nscursor = [[NSCursor alloc] initWithImage:nsimage
+ hotSpot:NSMakePoint(0.0, 0.0)];
+ [nsimage release];
+
+ return nscursor;
+}
+
+NSCursor *
+_gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor)
+{
+ const char *name = NULL;
+ NSCursor *nscursor;
+ SEL selector = @selector(arrowCursor);
+
+ g_return_val_if_fail (!cursor || GDK_IS_CURSOR (cursor), NULL);
+
+ if (cursor != NULL)
+ name = gdk_cursor_get_name (cursor);
+
+ if (name == NULL)
+ goto load_cursor;
+
+ if (strcmp (name, "none") == 0)
+ return create_blank_cursor ();
+
+ for (guint i = 0; i < G_N_ELEMENTS (cursors_by_name); i++)
+ {
+ if (strcmp (cursors_by_name[i].name, name) == 0)
+ {
+ selector = NSSelectorFromString (cursors_by_name[i].selector);
+ break;
+ }
+ }
+
+load_cursor:
+ nscursor = [[gdkCoreCursor class] performSelector:selector];
+ [nscursor retain];
+ return nscursor;
+}
diff --git a/gdk/macos/gdkmacosdevice.c b/gdk/macos/gdkmacosdevice.c
new file mode 100644
index 0000000000..8e621fa6fe
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2009 Carlos Garnacho
+ * Copyright 2010 Kristian Rietveld
+ * 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 .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+
+#include "gdkdeviceprivate.h"
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacoscursor-private.h"
+#include "gdkmacosdevice.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacossurface-private.h"
+
+struct _GdkMacosDevice
+{
+ GdkDevice parent_instance;
+};
+
+struct _GdkMacosDeviceClass
+{
+ GdkDeviceClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosDevice, gdk_macos_device, GDK_TYPE_DEVICE)
+
+static void
+gdk_macos_device_set_surface_cursor (GdkDevice *device,
+ GdkSurface *surface,
+ GdkCursor *cursor)
+{
+ NSCursor *nscursor;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (!cursor || GDK_IS_CURSOR (cursor));
+
+ nscursor = _gdk_macos_cursor_get_ns_cursor (cursor);
+
+ if (nscursor != NULL)
+ {
+ [nscursor set];
+ [nscursor release];
+ }
+}
+
+static GdkSurface *
+gdk_macos_device_surface_at_position (GdkDevice *device,
+ gdouble *win_x,
+ gdouble *win_y,
+ GdkModifierType *state)
+{
+ GdkMacosDisplay *display;
+ GdkMacosSurface *surface;
+ NSPoint point;
+ gint x;
+ gint y;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (win_x != NULL);
+ g_assert (win_y != NULL);
+
+ point = [NSEvent mouseLocation];
+ display = GDK_MACOS_DISPLAY (gdk_device_get_display (device));
+
+ if (state != NULL)
+ *state = (_gdk_macos_display_get_current_keyboard_modifiers (display) |
+ _gdk_macos_display_get_current_mouse_modifiers (display));
+
+ surface = _gdk_macos_display_get_surface_at_display_coords (display, point.x, point.y, &x, &y);
+
+ *win_x = x;
+ *win_y = y;
+
+ return GDK_SURFACE (surface);
+}
+
+static GdkGrabStatus
+gdk_macos_device_grab (GdkDevice *device,
+ GdkSurface *window,
+ gboolean owner_events,
+ GdkEventMask event_mask,
+ GdkSurface *confine_to,
+ GdkCursor *cursor,
+ guint32 time_)
+{
+ /* Should remain empty */
+ return GDK_GRAB_SUCCESS;
+}
+
+static void
+gdk_macos_device_ungrab (GdkDevice *device,
+ guint32 time_)
+{
+ GdkMacosDevice *self = (GdkMacosDevice *)device;
+ GdkDeviceGrabInfo *grab;
+ GdkDisplay *display;
+
+ g_assert (GDK_IS_MACOS_DEVICE (self));
+
+ display = gdk_device_get_display (device);
+ grab = _gdk_display_get_last_device_grab (display, device);
+
+ if (grab != NULL)
+ grab->serial_end = 0;
+
+ _gdk_display_device_grab_update (display, device, device, 0);
+}
+
+static void
+gdk_macos_device_get_state (GdkDevice *device,
+ GdkSurface *surface,
+ gdouble *axes,
+ GdkModifierType *mask)
+{
+ gdouble x_pos, y_pos;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ gdk_surface_get_device_position (surface, device, &x_pos, &y_pos, mask);
+
+ if (axes != NULL)
+ {
+ axes[0] = x_pos;
+ axes[1] = y_pos;
+ }
+}
+
+static void
+gdk_macos_device_query_state (GdkDevice *device,
+ GdkSurface *surface,
+ GdkSurface **child_surface,
+ gdouble *win_x,
+ gdouble *win_y,
+ GdkModifierType *mask)
+{
+ GdkDisplay *display;
+ NSPoint point;
+ int sx = 0;
+ int sy = 0;
+ int x_tmp;
+ int y_tmp;
+
+ g_assert (GDK_IS_MACOS_DEVICE (device));
+ g_assert (!surface || GDK_IS_MACOS_SURFACE (surface));
+
+ if (child_surface)
+ *child_surface = surface;
+
+ display = gdk_device_get_display (device);
+ point = [NSEvent mouseLocation];
+ _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
+ point.x, point.y,
+ &x_tmp, &y_tmp);
+
+ if (surface)
+ _gdk_macos_surface_get_root_coords (GDK_MACOS_SURFACE (surface), &sx, &sy);
+
+ if (win_x)
+ *win_x = x_tmp - sx;
+
+ if (win_y)
+ *win_y = y_tmp - sy;
+
+ if (mask)
+ *mask = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display)) |
+ _gdk_macos_display_get_current_mouse_modifiers (GDK_MACOS_DISPLAY (display));
+}
+
+static void
+gdk_macos_device_class_init (GdkMacosDeviceClass *klass)
+{
+ GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
+
+ device_class->get_state = gdk_macos_device_get_state;
+ device_class->grab = gdk_macos_device_grab;
+ device_class->query_state = gdk_macos_device_query_state;
+ device_class->set_surface_cursor = gdk_macos_device_set_surface_cursor;
+ device_class->surface_at_position = gdk_macos_device_surface_at_position;
+ device_class->ungrab = gdk_macos_device_ungrab;
+}
+
+static void
+gdk_macos_device_init (GdkMacosDevice *self)
+{
+ _gdk_device_add_axis (GDK_DEVICE (self), GDK_AXIS_X, 0, 0, 1);
+ _gdk_device_add_axis (GDK_DEVICE (self), GDK_AXIS_Y, 0, 0, 1);
+}
diff --git a/gdk/macos/gdkmacosdevice.h b/gdk/macos/gdkmacosdevice.h
new file mode 100644
index 0000000000..c81fde5d1a
--- /dev/null
+++ b/gdk/macos/gdkmacosdevice.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * 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 can be included directly."
+#endif
+
+#include
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDevice GdkMacosDevice;
+typedef struct _GdkMacosDeviceClass GdkMacosDeviceClass;
+
+#define GDK_TYPE_MACOS_DEVICE (gdk_macos_device_get_type())
+#define GDK_MACOS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DEVICE, GdkMacosDevice))
+#define GDK_IS_MACOS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DEVICE))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_device_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DEVICE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h
new file mode 100644
index 0000000000..9f3d55853b
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DISPLAY_PRIVATE_H__
+#define __GDK_MACOS_DISPLAY_PRIVATE_H__
+
+#include
+
+#include "gdkdisplayprivate.h"
+
+#include "gdkmacosdisplay.h"
+#include "gdkmacoskeymap.h"
+#include "gdkmacossurface.h"
+
+G_BEGIN_DECLS
+
+struct _GdkMacosDisplay
+{
+ GdkDisplay parent_instance;
+
+ char *name;
+ GdkMacosKeymap *keymap;
+
+ /* An list of GdkMacosMonitor. The first instance is always the primary
+ * monitor. This contains the 0,0 coordinate in quartz coordinates, but may
+ * not be 0,0 in GDK coordinates.
+ */
+ GListStore *monitors;
+
+ /* A queue of surfaces that have been made "main" so that we can update the
+ * main status to the next surface when a window has lost main status (such
+ * as when destroyed). This uses the GdkMacosSurface main link.
+ */
+ GQueue main_surfaces;
+
+ /* A queue of surfaces sorted by their front-to-back ordering on the screen.
+ * This is updated occasionally when we know that the data we have cached
+ * has been invalidated. This uses the GdkMacosSurface.sorted link.
+ */
+ GQueue sorted_surfaces;
+
+ /* Our CVDisplayLink based GSource which we use to freeze/thaw the
+ * GdkFrameClock for the surface.
+ */
+ GSource *frame_source;
+
+ /* A queue of surfaces which we know are awaiting frames to be drawn. This
+ * uses the GdkMacosSurface.frame link.
+ */
+ GQueue awaiting_frames;
+
+ /* The surface that is receiving keyboard events */
+ GdkMacosSurface *keyboard_surface;
+
+ /* Used to translate from quartz coordinate space to GDK */
+ int width;
+ int height;
+ int min_x;
+ int min_y;
+ int max_x;
+ int max_y;
+};
+
+struct _GdkMacosDisplayClass
+{
+ GdkDisplayClass parent_class;
+};
+
+
+GdkDisplay *_gdk_macos_display_open (const gchar *display_name);
+int _gdk_macos_display_get_fd (GdkMacosDisplay *self);
+void _gdk_macos_display_queue_events (GdkMacosDisplay *self);
+void _gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y);
+void _gdk_macos_display_from_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y);
+NSScreen *_gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y);
+GdkMonitor *_gdk_macos_display_get_monitor_at_coords (GdkMacosDisplay *self,
+ int x,
+ int y);
+GdkMonitor *_gdk_macos_display_get_monitor_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y);
+GdkEvent *_gdk_macos_display_translate (GdkMacosDisplay *self,
+ NSEvent *event);
+void _gdk_macos_display_break_all_grabs (GdkMacosDisplay *self,
+ guint32 time);
+GdkModifierType _gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self);
+GdkModifierType _gdk_macos_display_get_current_mouse_modifiers (GdkMacosDisplay *self);
+GdkMacosSurface *_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self,
+ double x,
+ double y,
+ int *surface_x,
+ int *surface_y);
+void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self);
+void _gdk_macos_display_surface_removed (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_add_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_synthesize_motion (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+NSWindow *_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self,
+ int *x,
+ int *y);
+gboolean _gdk_macos_display_get_setting (GdkMacosDisplay *self,
+ const gchar *setting,
+ GValue *value);
+void _gdk_macos_display_reload_settings (GdkMacosDisplay *self);
+void _gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_became_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+void _gdk_macos_display_surface_became_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface);
+int _gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self);
+void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self);
+const GList *_gdk_macos_display_get_surfaces (GdkMacosDisplay *self);
+void _gdk_macos_display_send_button_event (GdkMacosDisplay *self,
+ NSEvent *nsevent);
+void _gdk_macos_display_warp_pointer (GdkMacosDisplay *self,
+ int x,
+ int y);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdisplay-settings.c b/gdk/macos/gdkmacosdisplay-settings.c
new file mode 100644
index 0000000000..558e0b81e0
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-settings.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright © 1998-2002 Tor Lillqvist
+ * Copyright © 2005-2008 Imendio AB
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+
+#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..8e86459987
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -0,0 +1,1219 @@
+/*
+ * Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright 1998-2002 Tor Lillqvist
+ * Copyright 2005-2008 Imendio AB
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#import "GdkMacosWindow.h"
+#import "GdkMacosBaseView.h"
+
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacossurface-private.h"
+
+#define GDK_MOD2_MASK (1 << 4)
+#define GRIP_WIDTH 15
+#define GRIP_HEIGHT 15
+#define GDK_LION_RESIZE 5
+
+static gboolean
+test_resize (NSEvent *event,
+ GdkMacosSurface *surface,
+ int x,
+ int y)
+{
+ NSWindow *window;
+
+ g_assert (event != NULL);
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ window = _gdk_macos_surface_get_native (surface);
+
+ /* Resizing from the resize indicator only begins if an NSLeftMouseButton
+ * event is received in the resizing area.
+ */
+ if ([event type] == NSEventTypeLeftMouseDown &&
+ [window showsResizeIndicator])
+ {
+ NSRect frame;
+
+ /* If the resize indicator is visible and the event is in the lower
+ * right 15x15 corner, we leave these events to Cocoa as to be
+ * handled as resize events. Applications may have widgets in this
+ * area. These will most likely be larger than 15x15 and for scroll
+ * bars there are also other means to move the scroll bar. Since
+ * the resize indicator is the only way of resizing windows on Mac
+ * OS, it is too important to not make functional.
+ */
+ frame = [[window contentView] bounds];
+ if (x > frame.size.width - GRIP_WIDTH &&
+ x < frame.size.width &&
+ y > frame.size.height - GRIP_HEIGHT &&
+ y < frame.size.height)
+ return TRUE;
+ }
+
+ /* If we're on Lion and within 5 pixels of an edge, then assume that the
+ * user wants to resize, and return NULL to let Quartz get on with it.
+ * We check the selector isRestorable to see if we're on 10.7. This
+ * extra check is in case the user starts dragging before GDK recognizes
+ * the grab.
+ *
+ * We perform this check for a button press of all buttons, because we
+ * do receive, for instance, a right mouse down event for a GDK surface
+ * for x-coordinate range [-3, 0], but we do not want to forward this
+ * into GDK. Forwarding such events into GDK will confuse the pointer
+ * window finding code, because there are no GdkSurfaces present in
+ * the range [-3, 0].
+ */
+ if (([event type] == NSEventTypeLeftMouseDown ||
+ [event type] == NSEventTypeRightMouseDown ||
+ [event type] == NSEventTypeOtherMouseDown))
+ {
+ if (x < GDK_LION_RESIZE ||
+ x > GDK_SURFACE (surface)->width - GDK_LION_RESIZE ||
+ y > GDK_SURFACE (surface)->height - GDK_LION_RESIZE)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static guint32
+get_time_from_ns_event (NSEvent *event)
+{
+ double time = [event timestamp];
+ /* cast via double->uint64 conversion to make sure that it is
+ * wrapped on 32-bit machines when it overflows
+ */
+ return (guint32) (guint64) (time * 1000.0);
+}
+
+static int
+get_mouse_button_from_ns_event (NSEvent *event)
+{
+ NSInteger button = [event buttonNumber];
+
+ switch (button)
+ {
+ case 0:
+ return 1;
+ case 1:
+ return 3;
+ case 2:
+ return 2;
+ default:
+ return button + 1;
+ }
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_buttons (NSUInteger nsbuttons)
+{
+ GdkModifierType modifiers = 0;
+
+ if (nsbuttons & (1 << 0))
+ modifiers |= GDK_BUTTON1_MASK;
+ if (nsbuttons & (1 << 1))
+ modifiers |= GDK_BUTTON3_MASK;
+ if (nsbuttons & (1 << 2))
+ modifiers |= GDK_BUTTON2_MASK;
+ if (nsbuttons & (1 << 3))
+ modifiers |= GDK_BUTTON4_MASK;
+ if (nsbuttons & (1 << 4))
+ modifiers |= GDK_BUTTON5_MASK;
+
+ return modifiers;
+}
+
+static GdkModifierType
+get_mouse_button_modifiers_from_ns_event (NSEvent *event)
+{
+ GdkModifierType state = 0;
+ int button;
+
+ /* This maps buttons 1 to 5 to GDK_BUTTON[1-5]_MASK */
+ button = get_mouse_button_from_ns_event (event);
+ if (button >= 1 && button <= 5)
+ state = (1 << (button + 7));
+
+ return state;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_flags (NSUInteger nsflags)
+{
+ GdkModifierType modifiers = 0;
+
+ if (nsflags & NSEventModifierFlagCapsLock)
+ modifiers |= GDK_LOCK_MASK;
+ if (nsflags & NSEventModifierFlagShift)
+ modifiers |= GDK_SHIFT_MASK;
+ if (nsflags & NSEventModifierFlagControl)
+ modifiers |= GDK_CONTROL_MASK;
+ if (nsflags & NSEventModifierFlagOption)
+ modifiers |= GDK_ALT_MASK;
+ if (nsflags & NSEventModifierFlagCommand)
+ modifiers |= GDK_MOD2_MASK;
+
+ return modifiers;
+}
+
+static GdkModifierType
+get_keyboard_modifiers_from_ns_event (NSEvent *nsevent)
+{
+ return get_keyboard_modifiers_from_ns_flags ([nsevent modifierFlags]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_mouse_modifiers (GdkMacosDisplay *self)
+{
+ return get_mouse_button_modifiers_from_ns_buttons ([NSEvent pressedMouseButtons]);
+}
+
+GdkModifierType
+_gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self)
+{
+ return get_keyboard_modifiers_from_ns_flags ([NSEvent modifierFlags]);
+}
+
+static GdkEvent *
+fill_button_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkSeat *seat;
+ GdkEventType type;
+ GdkModifierType state;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ /* Ignore button events outside the window coords */
+ if (x < 0 || x > GDK_SURFACE (surface)->width ||
+ y < 0 || y > GDK_SURFACE (surface)->height)
+ return NULL;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ type = GDK_BUTTON_PRESS;
+ state &= ~get_mouse_button_modifiers_from_ns_event (nsevent);
+ break;
+
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseUp:
+ type = GDK_BUTTON_RELEASE;
+ state |= get_mouse_button_modifiers_from_ns_event (nsevent);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ return gdk_button_event_new (type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ get_mouse_button_from_ns_event (nsevent),
+ x,
+ y,
+ NULL);
+}
+
+static GdkEvent *
+synthesize_crossing_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkEventType event_type;
+ GdkModifierType state;
+ GdkSeat *seat;
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeMouseEntered:
+ event_type = GDK_ENTER_NOTIFY;
+ break;
+
+ case NSEventTypeMouseExited:
+ event_type = GDK_LEAVE_NOTIFY;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+ return gdk_crossing_event_new (event_type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ x,
+ y,
+ GDK_CROSSING_NORMAL,
+ GDK_NOTIFY_NONLINEAR);
+}
+
+static inline guint
+get_group_from_ns_event (NSEvent *nsevent)
+{
+ return ([nsevent modifierFlags] & NSEventModifierFlagOption) ? 1 : 0;
+}
+
+static void
+add_virtual_modifiers (GdkModifierType *state)
+{
+ if (*state & GDK_MOD2_MASK)
+ *state |= GDK_META_MASK;
+}
+
+static GdkEvent *
+fill_key_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ GdkEventType type)
+{
+ GdkTranslatedKey translated = {0};
+ GdkTranslatedKey no_lock = {0};
+ GdkModifierType consumed;
+ GdkModifierType state;
+ GdkKeymap *keymap;
+ gboolean is_modifier;
+ GdkSeat *seat;
+ guint keycode;
+ guint keyval;
+ guint group;
+ int layout;
+ int level;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ keymap = gdk_display_get_keymap (GDK_DISPLAY (display));
+ keycode = [nsevent keyCode];
+ keyval = GDK_KEY_VoidSymbol;
+ state = get_keyboard_modifiers_from_ns_event (nsevent);
+ group = get_group_from_ns_event (nsevent);
+ is_modifier = _gdk_macos_keymap_is_modifier (keycode);
+
+ gdk_keymap_translate_keyboard_state (keymap, keycode, state, group,
+ &keyval, &layout, &level, &consumed);
+
+ /* If the key press is a modifier, the state should include the mask for
+ * that modifier but only for releases, not presses. This matches the
+ * X11 backend behavior.
+ */
+ if (is_modifier)
+ {
+ guint mask = 0;
+
+ switch (keyval)
+ {
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Meta_L:
+ mask = GDK_MOD2_MASK;
+ break;
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Shift_L:
+ mask = GDK_SHIFT_MASK;
+ break;
+ case GDK_KEY_Caps_Lock:
+ mask = GDK_LOCK_MASK;
+ break;
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Alt_L:
+ mask = GDK_ALT_MASK;
+ break;
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Control_L:
+ mask = GDK_CONTROL_MASK;
+ break;
+ default:
+ mask = 0;
+ }
+
+ if (type == GDK_KEY_PRESS)
+ state &= ~mask;
+ else if (type == GDK_KEY_RELEASE)
+ state |= mask;
+ }
+
+ state |= _gdk_macos_display_get_current_mouse_modifiers (display);
+ add_virtual_modifiers (&state);
+
+ translated.keyval = keyval;
+ translated.consumed = consumed;
+ translated.layout = layout;
+ translated.level = level;
+
+ if (state & GDK_LOCK_MASK)
+ {
+ gdk_keymap_translate_keyboard_state (keymap,
+ keycode,
+ state & ~GDK_LOCK_MASK,
+ group,
+ &keyval,
+ &layout,
+ &level,
+ &consumed);
+
+ no_lock.keyval = keycode;
+ no_lock.consumed = consumed;
+ no_lock.layout = layout;
+ no_lock.level = level;
+ }
+ else
+ {
+ no_lock = translated;
+ }
+
+ return gdk_key_event_new (type,
+ GDK_SURFACE (surface),
+ gdk_seat_get_keyboard (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ [nsevent keyCode],
+ state,
+ is_modifier,
+ &translated,
+ &no_lock);
+}
+
+static GdkEvent *
+fill_pinch_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ static double last_scale = 1.0;
+ static enum {
+ FP_STATE_IDLE,
+ FP_STATE_UPDATE
+ } last_state = FP_STATE_IDLE;
+ GdkSeat *seat;
+ GdkTouchpadGesturePhase phase;
+ gdouble angle_delta = 0.0;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ /* fill_pinch_event handles the conversion from the two OSX gesture events
+ * NSEventTypeMagnfiy and NSEventTypeRotate to the GDK_TOUCHPAD_PINCH event.
+ * The normal behavior of the OSX events is that they produce as sequence of
+ * 1 x NSEventPhaseBegan,
+ * n x NSEventPhaseChanged,
+ * 1 x NSEventPhaseEnded
+ * This can happen for both the Magnify and the Rotate events independently.
+ * As both events are summarized in one GDK_TOUCHPAD_PINCH event sequence, a
+ * little state machine handles the case of two NSEventPhaseBegan events in
+ * a sequence, e.g. Magnify(Began), Magnify(Changed)..., Rotate(Began)...
+ * such that PINCH(STARTED), PINCH(UPDATE).... will not show a second
+ * PINCH(STARTED) event.
+ */
+
+ switch ((int)[nsevent phase])
+ {
+ case NSEventPhaseBegan:
+ switch (last_state)
+ {
+ case FP_STATE_IDLE:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
+ last_state = FP_STATE_UPDATE;
+ last_scale = 1.0;
+ break;
+ case FP_STATE_UPDATE:
+ /* We have already received a PhaseBegan event but no PhaseEnded
+ event. This can happen, e.g. Magnify(Began), Magnify(Change)...
+ Rotate(Began), Rotate (Change),...., Magnify(End) Rotate(End)
+ */
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+ }
+ break;
+
+ case NSEventPhaseChanged:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+
+ case NSEventPhaseEnded:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
+ switch (last_state)
+ {
+ case FP_STATE_IDLE:
+ /* We are idle but have received a second PhaseEnded event.
+ This can happen because we have Magnify and Rotate OSX
+ event sequences. We just send a second end GDK_PHASE_END.
+ */
+ break;
+ case FP_STATE_UPDATE:
+ last_state = FP_STATE_IDLE;
+ break;
+ }
+ break;
+
+ case NSEventPhaseCancelled:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+ last_state = FP_STATE_IDLE;
+ break;
+
+ case NSEventPhaseMayBegin:
+ case NSEventPhaseStationary:
+ phase = GDK_TOUCHPAD_GESTURE_PHASE_CANCEL;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeMagnify:
+ last_scale *= [nsevent magnification] + 1.0;
+ angle_delta = 0.0;
+ break;
+
+ case NSEventTypeRotate:
+ angle_delta = - [nsevent rotation] * G_PI / 180.0;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+
+ return gdk_touchpad_event_new_pinch (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ get_time_from_ns_event (nsevent),
+ get_keyboard_modifiers_from_ns_event (nsevent),
+ phase,
+ x,
+ y,
+ 2,
+ 0.0,
+ 0.0,
+ last_scale,
+ angle_delta);
+
+}
+
+static gboolean
+is_motion_event (NSEventType event_type)
+{
+ return (event_type == NSEventTypeLeftMouseDragged ||
+ event_type == NSEventTypeRightMouseDragged ||
+ event_type == NSEventTypeOtherMouseDragged ||
+ event_type == NSEventTypeMouseMoved);
+}
+
+static GdkEvent *
+fill_motion_event (GdkMacosDisplay *display,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkSeat *seat;
+ GdkModifierType state;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+ g_assert (is_motion_event ([nsevent type]));
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (display));
+ state = get_keyboard_modifiers_from_ns_event (nsevent) |
+ _gdk_macos_display_get_current_mouse_modifiers (display);
+
+ return gdk_motion_event_new (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ x,
+ y,
+ NULL);
+}
+
+static GdkEvent *
+fill_scroll_event (GdkMacosDisplay *self,
+ GdkMacosSurface *surface,
+ NSEvent *nsevent,
+ int x,
+ int y)
+{
+ GdkScrollDirection direction = 0;
+ GdkModifierType state;
+ GdkDevice *pointer;
+ GdkEvent *ret = NULL;
+ GdkSeat *seat;
+ gdouble dx;
+ gdouble dy;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (nsevent != NULL);
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ pointer = gdk_seat_get_pointer (seat);
+ state = _gdk_macos_display_get_current_mouse_modifiers (self) |
+ _gdk_macos_display_get_current_keyboard_modifiers (self);
+
+ dx = [nsevent deltaX];
+ dy = [nsevent deltaY];
+
+ if ([nsevent hasPreciseScrollingDeltas])
+ {
+ gdouble sx;
+ gdouble sy;
+
+ /*
+ * TODO: We probably need another event type for the
+ * high precision scroll events since sx and dy
+ * are in a unit we don't quite support. For now,
+ * to slow it down multiply by .1.
+ */
+
+ sx = [nsevent scrollingDeltaX] * .1;
+ sy = [nsevent scrollingDeltaY] * .1;
+
+ if (sx != 0.0 || dx != 0.0)
+ ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ -sx,
+ -sy,
+ FALSE);
+
+ /* Fall through for scroll emulation */
+ }
+
+ if (dy != 0.0)
+ {
+ if (dy < 0.0)
+ direction = GDK_SCROLL_DOWN;
+ else
+ direction = GDK_SCROLL_UP;
+
+ dx = 0.0;
+ }
+ else if (dx != 0.0)
+ {
+ if (dx < 0.0)
+ direction = GDK_SCROLL_RIGHT;
+ else
+ direction = GDK_SCROLL_LEFT;
+
+ dy = 0.0;
+ }
+
+ if (dx != 0.0 || dy != 0.0)
+ {
+ if ([nsevent hasPreciseScrollingDeltas])
+ {
+ GdkEvent *emulated;
+
+ emulated = gdk_scroll_event_new_discrete (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ direction,
+ TRUE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), emulated);
+ }
+ else
+ {
+ g_assert (ret == NULL);
+
+ ret = gdk_scroll_event_new (GDK_SURFACE (surface),
+ pointer,
+ NULL,
+ NULL,
+ get_time_from_ns_event (nsevent),
+ state,
+ dx,
+ dy,
+ FALSE);
+ }
+ }
+
+ return g_steal_pointer (&ret);
+}
+
+static gboolean
+is_mouse_button_press_event (NSEventType type)
+{
+ switch ((int)type)
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static void
+get_surface_point_from_screen_point (GdkSurface *surface,
+ NSPoint screen_point,
+ int *x,
+ int *y)
+{
+ NSWindow *nswindow;
+ NSPoint point;
+
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+ point = [nswindow convertPointFromScreen:screen_point];
+
+ *x = point.x;
+ *y = surface->height - point.y;
+}
+
+static GdkSurface *
+find_surface_under_pointer (GdkMacosDisplay *self,
+ NSPoint screen_point,
+ int *x,
+ int *y)
+{
+ GdkPointerSurfaceInfo *info;
+ GdkSurface *surface;
+ GdkSeat *seat;
+ int x_tmp, y_tmp;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ info = _gdk_display_get_pointer_info (GDK_DISPLAY (self),
+ gdk_seat_get_pointer (seat));
+ surface = info->surface_under_pointer;
+
+ if (surface == NULL)
+ {
+ GdkMacosSurface *found;
+
+ found = _gdk_macos_display_get_surface_at_display_coords (self,
+ screen_point.x,
+ screen_point.y,
+ &x_tmp, &y_tmp);
+
+ if (found)
+ {
+ surface = GDK_SURFACE (found);
+ info->surface_under_pointer = g_object_ref (surface);
+ }
+ }
+
+ if (surface)
+ {
+ _gdk_macos_display_from_display_coords (self,
+ screen_point.x,
+ screen_point.y,
+ &x_tmp, &y_tmp);
+ *x = x_tmp - GDK_MACOS_SURFACE (surface)->root_x;
+ *y = y_tmp - GDK_MACOS_SURFACE (surface)->root_y;
+ /* If the coordinates are out of window bounds, this surface is not
+ * under the pointer and we thus return NULL. This can occur when
+ * surface under pointer has not yet been updated due to a very recent
+ * window resize. Alternatively, we should no longer be relying on
+ * the surface_under_pointer value which is maintained in gdkwindow.c.
+ */
+ if (*x < 0 || *y < 0 || *x >= surface->width || *y >= surface->height)
+ return NULL;
+ }
+
+ return surface;
+}
+
+static GdkSurface *
+get_surface_from_ns_event (GdkMacosDisplay *self,
+ NSEvent *nsevent,
+ NSPoint *screen_point,
+ int *x,
+ int *y)
+{
+ GdkSurface *surface = NULL;
+ NSWindow *nswindow = [nsevent window];
+
+ if (nswindow)
+ {
+ GdkMacosBaseView *view;
+ NSPoint point, view_point;
+ NSRect view_frame;
+
+ view = (GdkMacosBaseView *)[nswindow contentView];
+ surface = GDK_SURFACE ([view gdkSurface]);
+
+ point = [nsevent locationInWindow];
+ view_point = [view convertPoint:point fromView:nil];
+ view_frame = [view frame];
+
+ /* NSEvents come in with a window set, but with window coordinates
+ * out of window bounds. For e.g. moved events this is fine, we use
+ * this information to properly handle enter/leave notify and motion
+ * events. For mouse button press/release, we want to avoid forwarding
+ * these events however, because the window they relate to is not the
+ * window set in the event. This situation appears to occur when button
+ * presses come in just before (or just after?) a window is resized and
+ * also when a button press occurs on the OS X window titlebar.
+ *
+ * By setting surface to NULL, we do another attempt to get the right
+ * surface window below.
+ */
+ if (is_mouse_button_press_event ([nsevent type]) &&
+ (view_point.x < view_frame.origin.x ||
+ view_point.x >= view_frame.origin.x + view_frame.size.width ||
+ view_point.y < view_frame.origin.y ||
+ view_point.y >= view_frame.origin.y + view_frame.size.height))
+ {
+ NSRect windowRect = [nswindow frame];
+
+ surface = NULL;
+
+ /* This is a hack for button presses to break all grabs. E.g. if
+ * a menu is open and one clicks on the title bar (or anywhere
+ * out of window bounds), we really want to pop down the menu (by
+ * breaking the grabs) before OS X handles the action of the title
+ * bar button.
+ *
+ * Because we cannot ingest this event into GDK, we have to do it
+ * here, not very nice.
+ */
+ _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+
+ /* If the X,Y is on the frame itself, then we don't want to discover
+ * the surface under the pointer at all so that we let OS X handle
+ * it instead. We add padding to include resize operations too.
+ */
+ windowRect.origin.x = -GDK_LION_RESIZE;
+ windowRect.origin.y = -GDK_LION_RESIZE;
+ windowRect.size.width += (2 * GDK_LION_RESIZE);
+ windowRect.size.height += (2 * GDK_LION_RESIZE);
+ if (NSPointInRect (point, windowRect))
+ return NULL;
+ }
+ else
+ {
+ *screen_point = [(GdkMacosWindow *)[nsevent window] convertPointToScreen:point];
+ *x = point.x;
+ *y = surface->height - point.y;
+ }
+ }
+
+ if (!surface)
+ {
+ /* Fallback used when no NSSurface set. This happens e.g. when
+ * we allow motion events without a window set in gdk_event_translate()
+ * that occur immediately after the main menu bar was clicked/used.
+ * This fallback will not return coordinates contained in a window's
+ * titlebar.
+ */
+ *screen_point = [NSEvent mouseLocation];
+ surface = find_surface_under_pointer (self, *screen_point, x, y);
+ }
+
+ return surface;
+}
+
+static GdkMacosSurface *
+find_surface_for_keyboard_event (NSEvent *nsevent)
+{
+ GdkMacosBaseView *view = (GdkMacosBaseView *)[[nsevent window] contentView];
+ GdkSurface *surface = GDK_SURFACE ([view gdkSurface]);
+ GdkDisplay *display = gdk_surface_get_display (surface);
+ GdkSeat *seat = gdk_display_get_default_seat (display);
+ GdkDevice *device = gdk_seat_get_keyboard (seat);
+ GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (display, device);
+
+ if (grab && grab->surface && !grab->owner_events)
+ return GDK_MACOS_SURFACE (grab->surface);
+
+ return GDK_MACOS_SURFACE (surface);
+}
+
+static GdkMacosSurface *
+find_surface_for_mouse_event (GdkMacosDisplay *self,
+ NSEvent *nsevent,
+ int *x,
+ int *y)
+{
+ NSPoint point;
+ NSEventType event_type;
+ GdkSurface *surface;
+ GdkDisplay *display;
+ GdkDevice *pointer;
+ GdkDeviceGrabInfo *grab;
+ GdkSeat *seat;
+
+ surface = get_surface_from_ns_event (self, nsevent, &point, x, y);
+ display = gdk_surface_get_display (surface);
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ pointer = gdk_seat_get_pointer (seat);
+
+ event_type = [nsevent type];
+
+ /* From the docs for XGrabPointer:
+ *
+ * If owner_events is True and if a generated pointer event
+ * would normally be reported to this client, it is reported
+ * as usual. Otherwise, the event is reported with respect to
+ * the grab_window and is reported only if selected by
+ * event_mask. For either value of owner_events, unreported
+ * events are discarded.
+ */
+ if ((grab = _gdk_display_get_last_device_grab (display, pointer)))
+ {
+ if (grab->owner_events)
+ {
+ /* For owner events, we need to use the surface under the
+ * pointer, not the window from the NSEvent, since that is
+ * reported with respect to the key window, which could be
+ * wrong.
+ */
+ GdkSurface *surface_under_pointer;
+ int x_tmp, y_tmp;
+
+ surface_under_pointer = find_surface_under_pointer (self, point, &x_tmp, &y_tmp);
+ if (surface_under_pointer)
+ {
+ surface = surface_under_pointer;
+ *x = x_tmp;
+ *y = y_tmp;
+ }
+
+ return GDK_MACOS_SURFACE (surface);
+ }
+ else
+ {
+ /* Finally check the grab window. */
+ GdkSurface *grab_surface = grab->surface;
+ get_surface_point_from_screen_point (grab_surface, point, x, y);
+ return GDK_MACOS_SURFACE (grab_surface);
+ }
+
+ return NULL;
+ }
+ else
+ {
+ /* The non-grabbed case. */
+ GdkSurface *surface_under_pointer;
+ int x_tmp, y_tmp;
+
+ /* Ignore all events but mouse moved that might be on the title
+ * bar (above the content view). The reason is that otherwise
+ * gdk gets confused about getting e.g. button presses with no
+ * window (the title bar is not known to it).
+ */
+ if (event_type != NSEventTypeMouseMoved)
+ {
+ if (*y < 0)
+ return NULL;
+ }
+
+ /* As for owner events, we need to use the surface under the
+ * pointer, not the window from the NSEvent.
+ */
+ surface_under_pointer = find_surface_under_pointer (self, point, &x_tmp, &y_tmp);
+ if (surface_under_pointer != NULL)
+ {
+ surface = surface_under_pointer;
+ *x = x_tmp;
+ *y = y_tmp;
+ }
+
+ return GDK_MACOS_SURFACE (surface);
+ }
+
+ return NULL;
+}
+
+/* This function finds the correct window to send an event to, taking
+ * into account grabs, event propagation, and event masks.
+ */
+static GdkMacosSurface *
+find_surface_for_ns_event (GdkMacosDisplay *self,
+ NSEvent *nsevent,
+ int *x,
+ int *y)
+{
+ GdkMacosBaseView *view;
+ GdkSurface *surface;
+ NSPoint point;
+ int x_tmp;
+ int y_tmp;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (nsevent != NULL);
+ g_assert (x != NULL);
+ g_assert (y != NULL);
+
+ view = (GdkMacosBaseView *)[[nsevent window] contentView];
+
+ if (!(surface = get_surface_from_ns_event (self, nsevent, &point, x, y)))
+ return NULL;
+
+ _gdk_macos_display_from_display_coords (self, point.x, point.y, &x_tmp, &y_tmp);
+
+ switch ((int)[nsevent type])
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseUp:
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDragged:
+ case NSEventTypeMouseMoved:
+ case NSEventTypeScrollWheel:
+ case NSEventTypeMagnify:
+ case NSEventTypeRotate:
+ return find_surface_for_mouse_event (self, nsevent, x, y);
+
+ case NSEventTypeMouseEntered:
+ case NSEventTypeMouseExited:
+ /* Only handle our own entered/exited events, not the ones for the
+ * titlebar buttons.
+ */
+ if ([nsevent trackingArea] == [view trackingArea])
+ return GDK_MACOS_SURFACE (surface);
+ else
+ return NULL;
+
+ case NSEventTypeKeyDown:
+ case NSEventTypeKeyUp:
+ case NSEventTypeFlagsChanged:
+ return find_surface_for_keyboard_event (nsevent);
+
+ default:
+ /* Ignore everything else. */
+ return NULL;
+ }
+}
+
+GdkEvent *
+_gdk_macos_display_translate (GdkMacosDisplay *self,
+ NSEvent *nsevent)
+{
+ GdkMacosSurface *surface;
+ GdkMacosWindow *window;
+ NSEventType event_type;
+ GdkEvent *ret = NULL;
+ int x;
+ int y;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+ g_return_val_if_fail (nsevent != NULL, NULL);
+
+ /* There is no support for real desktop wide grabs, so we break
+ * grabs when the application loses focus (gets deactivated).
+ */
+ event_type = [nsevent type];
+ if (event_type == NSEventTypeAppKitDefined)
+ {
+ if ([nsevent subtype] == NSEventSubtypeApplicationDeactivated)
+ _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+
+ /* This could potentially be used to break grabs when clicking
+ * on the title. The subtype 20 is undocumented so it's probably
+ * not a good idea: else if (subtype == 20) break_all_grabs ();
+ */
+
+ /* Leave all AppKit events to AppKit. */
+ return NULL;
+ }
+
+ if (!(surface = find_surface_for_ns_event (self, nsevent, &x, &y)))
+ return NULL;
+
+ if (!(window = (GdkMacosWindow *)_gdk_macos_surface_get_native (surface)))
+ return NULL;
+
+ /* Ignore events and break grabs while the window is being
+ * dragged. This is a workaround for the window getting events for
+ * the window title.
+ */
+ if ([window isInMove])
+ {
+ _gdk_macos_display_break_all_grabs (self, get_time_from_ns_event (nsevent));
+ return NULL;
+ }
+
+ /* Also when in a manual resize or move , we ignore events so that
+ * these are pushed to GdkMacosNSWindow's sendEvent handler.
+ */
+ if ([window isInManualResizeOrMove])
+ return NULL;
+
+ /* Make sure we have a GdkSurface */
+ if (!(surface = [window gdkSurface]))
+ return NULL;
+
+ /* Quartz handles resizing on its own, so stay out of the way. */
+ if (test_resize (nsevent, surface, x, y))
+ return NULL;
+
+ if ((event_type == NSEventTypeRightMouseDown ||
+ event_type == NSEventTypeOtherMouseDown ||
+ event_type == NSEventTypeLeftMouseDown))
+ {
+ if (![NSApp isActive])
+ [NSApp activateIgnoringOtherApps:YES];
+
+ if (![window isKeyWindow])
+ [window makeKeyWindow];
+ }
+
+ switch ((int)event_type)
+ {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseUp:
+ ret = fill_button_event (self, surface, nsevent, x, y);
+ break;
+
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDragged:
+ case NSEventTypeMouseMoved:
+ ret = fill_motion_event (self, surface, nsevent, x, y);
+ break;
+
+ case NSEventTypeMagnify:
+ case NSEventTypeRotate:
+ ret = fill_pinch_event (self, surface, nsevent, x, y);
+ break;
+
+ case NSEventTypeMouseExited:
+ [[NSCursor arrowCursor] set];
+ /* fallthrough */
+ case NSEventTypeMouseEntered:
+ ret = synthesize_crossing_event (self, surface, nsevent, x, y);
+ break;
+
+ case NSEventTypeKeyDown:
+ case NSEventTypeKeyUp:
+ case NSEventTypeFlagsChanged: {
+ GdkEventType type = _gdk_macos_keymap_get_event_type (nsevent);
+
+ if (type)
+ ret = fill_key_event (self, surface, nsevent, type);
+
+ break;
+ }
+
+ case NSEventTypeScrollWheel:
+ ret = fill_scroll_event (self, surface, nsevent, x, y);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+void
+_gdk_macos_display_synthesize_motion (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ GdkModifierType state;
+ GdkEvent *event;
+ GdkSeat *seat;
+ NSPoint point;
+ GList *node;
+ int x;
+ int y;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ point = [NSEvent mouseLocation];
+ _gdk_macos_display_from_display_coords (self, point.x, point.y, &x, &y);
+
+ state = _gdk_macos_display_get_current_keyboard_modifiers (self) |
+ _gdk_macos_display_get_current_mouse_modifiers (self);
+
+ event = gdk_motion_event_new (GDK_SURFACE (surface),
+ gdk_seat_get_pointer (seat),
+ NULL,
+ NULL,
+ get_time_from_ns_event ([NSApp currentEvent]),
+ state,
+ x,
+ y,
+ NULL);
+ node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+}
+
+void
+_gdk_macos_display_send_button_event (GdkMacosDisplay *self,
+ NSEvent *nsevent)
+{
+ GdkMacosSurface *surface;
+ GdkEvent *event;
+ int x;
+ int y;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (nsevent != NULL);
+
+ if ((surface = find_surface_for_ns_event (self, nsevent, &x, &y)) &&
+ (event = fill_button_event (self, surface, nsevent, x, y)))
+ _gdk_windowing_got_event (GDK_DISPLAY (self),
+ _gdk_event_queue_append (GDK_DISPLAY (self), event),
+ event,
+ _gdk_display_get_next_serial (GDK_DISPLAY (self)));
+}
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
new file mode 100644
index 0000000000..800696eb09
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -0,0 +1,1071 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include
+
+#import "GdkMacosWindow.h"
+
+#include "gdkdisplayprivate.h"
+#include "gdkeventsprivate.h"
+
+#include "gdkdisplaylinksource.h"
+#include "gdkmacosclipboard-private.h"
+#include "gdkmacoscairocontext-private.h"
+#include "gdkmacoseventsource-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosglcontext-private.h"
+#include "gdkmacoskeymap-private.h"
+#include "gdkmacosmonitor-private.h"
+#include "gdkmacosseat-private.h"
+#include "gdkmacossurface-private.h"
+#include "gdkmacosutils-private.h"
+
+/**
+ * SECTION:macos_interaction
+ * @Short_description: macOS backend-specific functions
+ * @Title: macOS Interaction
+ * @Include: gdk/macos/gdkmacos.h
+ *
+ * The functions in this section are specific to the GDK macOS backend.
+ * To use them, you need to include the `` 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).
+ * |[
+ * #ifdef GDK_WINDOWING_MACOS
+ * if (GDK_IS_MACOS_DISPLAY (display))
+ * {
+ * // make macOS-specific calls here
+ * }
+ * else
+ * #endif
+ * #ifdef GDK_WINDOWING_X11
+ * if (GDK_IS_X11_DISPLAY (display))
+ * {
+ * // make X11-specific calls here
+ * }
+ * else
+ * #endif
+ * g_error ("Unsupported GDK backend");
+ * ]|
+ */
+
+G_DEFINE_TYPE (GdkMacosDisplay, gdk_macos_display, GDK_TYPE_DISPLAY)
+
+static GSource *event_source;
+
+static GdkMacosMonitor *
+get_monitor (GdkMacosDisplay *self,
+ guint position)
+{
+ GdkMacosMonitor *monitor;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ /* Get the monitor but return a borrowed reference */
+ monitor = g_list_model_get_item (G_LIST_MODEL (self->monitors), position);
+ if (monitor != NULL)
+ g_object_unref (monitor);
+
+ return monitor;
+}
+
+static gboolean
+gdk_macos_display_get_setting (GdkDisplay *display,
+ const gchar *setting,
+ GValue *value)
+{
+ return _gdk_macos_display_get_setting (GDK_MACOS_DISPLAY (display), setting, value);
+}
+
+static GListModel *
+gdk_macos_display_get_monitors (GdkDisplay *display)
+{
+ return G_LIST_MODEL (GDK_MACOS_DISPLAY (display)->monitors);
+}
+
+static GdkMonitor *
+gdk_macos_display_get_monitor_at_surface (GdkDisplay *display,
+ GdkSurface *surface)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ CGDirectDisplayID screen_id;
+ guint n_monitors;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ screen_id = _gdk_macos_surface_get_screen_id (GDK_MACOS_SURFACE (surface));
+ n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+ for (guint i = 0; i < n_monitors; i++)
+ {
+ GdkMacosMonitor *monitor = get_monitor (self, i);
+
+ if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+ return GDK_MONITOR (monitor);
+ }
+
+ return GDK_MONITOR (get_monitor (self, 0));
+}
+
+static GdkMacosMonitor *
+gdk_macos_display_find_monitor (GdkMacosDisplay *self,
+ CGDirectDisplayID screen_id)
+{
+ guint n_monitors;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+ for (guint i = 0; i < n_monitors; i++)
+ {
+ GdkMacosMonitor *monitor = get_monitor (self, i);
+
+ if (screen_id == _gdk_macos_monitor_get_screen_id (monitor))
+ return monitor;
+ }
+
+ return NULL;
+}
+
+static void
+gdk_macos_display_update_bounds (GdkMacosDisplay *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ self->min_x = G_MAXINT;
+ self->min_y = G_MAXINT;
+
+ self->max_x = G_MININT;
+ self->max_y = G_MININT;
+
+ for (id obj in [NSScreen screens])
+ {
+ NSRect geom = [(NSScreen *)obj frame];
+
+ self->min_x = MIN (self->min_x, geom.origin.x);
+ self->min_y = MIN (self->min_y, geom.origin.y);
+ self->max_x = MAX (self->max_x, geom.origin.x + geom.size.width);
+ self->max_y = MAX (self->max_y, geom.origin.y + geom.size.height);
+ }
+
+ self->width = self->max_x - self->min_x;
+ self->height = self->max_y - self->min_y;
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ GdkMacosDisplay *self = observer;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ _gdk_macos_display_reload_monitors (self);
+
+ /* Now we need to update all our surface positions since they
+ * probably just changed origins. We ignore the popup surfaces
+ * since we can rely on the toplevel surfaces to handle that.
+ */
+ for (const GList *iter = _gdk_macos_display_get_surfaces (self);
+ iter != NULL;
+ iter = iter->next)
+ {
+ GdkMacosSurface *surface = iter->data;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ if (GDK_IS_TOPLEVEL (surface))
+ _gdk_macos_surface_update_position (surface);
+ }
+}
+
+static void
+gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ GdkMacosDisplay *self = observer;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ _gdk_macos_display_reload_settings (self);
+}
+
+void
+_gdk_macos_display_reload_monitors (GdkMacosDisplay *self)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GArray *seen;
+ guint n_monitors;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ gdk_macos_display_update_bounds (self);
+
+ seen = g_array_new (FALSE, FALSE, sizeof (CGDirectDisplayID));
+
+ for (id obj in [NSScreen screens])
+ {
+ CGDirectDisplayID screen_id;
+ GdkMacosMonitor *monitor;
+
+ screen_id = [[[obj deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
+ g_array_append_val (seen, screen_id);
+
+ if ((monitor = gdk_macos_display_find_monitor (self, screen_id)))
+ {
+ _gdk_macos_monitor_reconfigure (monitor);
+ }
+ else
+ {
+ monitor = _gdk_macos_monitor_new (self, screen_id);
+ g_list_store_append (self->monitors, monitor);
+ g_object_unref (monitor);
+ }
+ }
+
+ n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+ for (guint i = n_monitors; i > 0; i--)
+ {
+ GdkMacosMonitor *monitor = get_monitor (self, i - 1);
+ CGDirectDisplayID screen_id = _gdk_macos_monitor_get_screen_id (monitor);
+ gboolean found = FALSE;
+
+ for (guint j = 0; j < seen->len; j++)
+ {
+ if (screen_id == g_array_index (seen, CGDirectDisplayID, j))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ g_list_store_remove (self->monitors, i - 1);
+ }
+
+ g_array_unref (seen);
+
+ GDK_END_MACOS_ALLOC_POOL;
+}
+
+static void
+gdk_macos_display_load_seat (GdkMacosDisplay *self)
+{
+ GdkSeat *seat;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ seat = _gdk_macos_seat_new (self);
+ gdk_display_add_seat (GDK_DISPLAY (self), seat);
+ g_object_unref (seat);
+}
+
+static gboolean
+gdk_macos_display_frame_cb (gpointer data)
+{
+ GdkMacosDisplay *self = data;
+ GdkDisplayLinkSource *source;
+ gint64 presentation_time;
+ gint64 now;
+ GList *iter;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ source = (GdkDisplayLinkSource *)self->frame_source;
+
+ presentation_time = source->presentation_time;
+ now = g_source_get_time ((GSource *)source);
+
+ iter = self->awaiting_frames.head;
+
+ while (iter != NULL)
+ {
+ GdkMacosSurface *surface = iter->data;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ iter = iter->next;
+
+ _gdk_macos_display_remove_frame_callback (self, surface);
+ _gdk_macos_surface_thaw (surface,
+ source->presentation_time,
+ source->refresh_interval);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gdk_macos_display_load_display_link (GdkMacosDisplay *self)
+{
+ self->frame_source = gdk_display_link_source_new ();
+ g_source_set_callback (self->frame_source,
+ gdk_macos_display_frame_cb,
+ self,
+ NULL);
+ g_source_attach (self->frame_source, NULL);
+}
+
+static const gchar *
+gdk_macos_display_get_name (GdkDisplay *display)
+{
+ return GDK_MACOS_DISPLAY (display)->name;
+}
+
+static void
+gdk_macos_display_beep (GdkDisplay *display)
+{
+ NSBeep ();
+}
+
+static void
+gdk_macos_display_flush (GdkDisplay *display)
+{
+ /* Not Supported */
+}
+
+static void
+gdk_macos_display_sync (GdkDisplay *display)
+{
+ /* Not Supported */
+}
+
+static gulong
+gdk_macos_display_get_next_serial (GdkDisplay *display)
+{
+ return 0;
+}
+
+static gboolean
+gdk_macos_display_has_pending (GdkDisplay *display)
+{
+ return _gdk_event_queue_find_first (display) ||
+ _gdk_macos_event_source_check_pending ();
+}
+
+static void
+gdk_macos_display_notify_startup_complete (GdkDisplay *display,
+ const gchar *startup_notification_id)
+{
+ /* Not Supported */
+}
+
+static void
+gdk_macos_display_queue_events (GdkDisplay *display)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ NSEvent *nsevent;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if ((nsevent = _gdk_macos_event_source_get_pending ()))
+ {
+ GdkEvent *event = _gdk_macos_display_translate (self, nsevent);
+
+ if (event != NULL)
+ _gdk_windowing_got_event (GDK_DISPLAY (self),
+ _gdk_event_queue_append (GDK_DISPLAY (self), event),
+ event,
+ 0);
+ else
+ [NSApp sendEvent:nsevent];
+
+ [nsevent release];
+ }
+}
+
+static void
+_gdk_macos_display_surface_added (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+ g_assert (!queue_contains (&self->sorted_surfaces, &surface->sorted));
+ g_assert (!queue_contains (&self->main_surfaces, &surface->main));
+ g_assert (!queue_contains (&self->awaiting_frames, &surface->frame));
+ g_assert (surface->sorted.data == surface);
+ g_assert (surface->main.data == surface);
+ g_assert (surface->frame.data == surface);
+
+ if (GDK_IS_TOPLEVEL (surface))
+ g_queue_push_tail_link (&self->main_surfaces, &surface->main);
+
+ _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_removed (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (self->keyboard_surface == surface)
+ _gdk_macos_display_surface_resigned_key (self, surface);
+
+ g_queue_unlink (&self->sorted_surfaces, &surface->sorted);
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ _gdk_macos_display_surface_resigned_main (self, surface);
+
+ if (queue_contains (&self->awaiting_frames, &surface->frame))
+ g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+ g_return_if_fail (self->keyboard_surface != surface);
+}
+
+void
+_gdk_macos_display_surface_became_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ GdkDevice *keyboard;
+ GdkEvent *event;
+ GdkSeat *seat;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+ g_return_if_fail (self->keyboard_surface == NULL);
+
+ self->keyboard_surface = surface;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ keyboard = gdk_seat_get_keyboard (seat);
+ event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, TRUE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), event);
+
+ /* We just became the active window. Unlike X11, Mac OS X does
+ * not send us motion events while the window does not have focus
+ * ("is not key"). We send a dummy motion notify event now, so that
+ * everything in the window is set to correct state.
+ */
+ _gdk_macos_display_synthesize_motion (self, surface);
+}
+
+void
+_gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (self->keyboard_surface == surface)
+ {
+ GdkDevice *keyboard;
+ GdkEvent *event;
+ GdkSeat *seat;
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ keyboard = gdk_seat_get_keyboard (seat);
+ event = gdk_focus_event_new (GDK_SURFACE (surface), keyboard, NULL, FALSE);
+ _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ }
+
+ self->keyboard_surface = NULL;
+
+ _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_became_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ g_queue_unlink (&self->main_surfaces, &surface->main);
+
+ g_queue_push_head_link (&self->main_surfaces, &surface->main);
+
+ _gdk_macos_display_clear_sorting (self);
+}
+
+void
+_gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ GdkMacosSurface *new_surface = NULL;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->main_surfaces, &surface->main))
+ g_queue_unlink (&self->main_surfaces, &surface->main);
+
+ _gdk_macos_display_clear_sorting (self);
+
+ if (GDK_SURFACE (surface)->transient_for &&
+ gdk_surface_get_mapped (GDK_SURFACE (surface)->transient_for))
+ {
+ new_surface = GDK_MACOS_SURFACE (GDK_SURFACE (surface)->transient_for);
+ }
+ else
+ {
+ const GList *surfaces = _gdk_macos_display_get_surfaces (self);
+
+ for (const GList *iter = surfaces; iter; iter = iter->next)
+ {
+ GdkMacosSurface *item = iter->data;
+
+ g_assert (GDK_IS_MACOS_SURFACE (item));
+
+ if (item == surface)
+ continue;
+
+ if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (item)))
+ {
+ new_surface = item;
+ break;
+ }
+ }
+ }
+
+ if (new_surface != NULL)
+ {
+ NSWindow *nswindow = _gdk_macos_surface_get_native (new_surface);
+ [nswindow makeKeyAndOrderFront:nswindow];
+ }
+
+ _gdk_macos_display_clear_sorting (self);
+}
+
+static GdkSurface *
+gdk_macos_display_create_surface (GdkDisplay *display,
+ GdkSurfaceType surface_type,
+ GdkSurface *parent,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+ GdkMacosSurface *surface;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (!parent || GDK_IS_MACOS_SURFACE (parent));
+
+ surface = _gdk_macos_surface_new (self, surface_type, parent, x, y, width, height);
+
+ if (surface != NULL)
+ _gdk_macos_display_surface_added (self, surface);
+
+ return GDK_SURFACE (surface);
+}
+
+static GdkKeymap *
+gdk_macos_display_get_keymap (GdkDisplay *display)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)display;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ return GDK_KEYMAP (self->keymap);
+}
+
+static void
+gdk_macos_display_load_clipboard (GdkMacosDisplay *self)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ GDK_DISPLAY (self)->clipboard = _gdk_macos_clipboard_new (self);
+}
+
+static gboolean
+gdk_macos_display_make_gl_context_current (GdkDisplay *display,
+ GdkGLContext *gl_context)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (display));
+ g_assert (GDK_IS_MACOS_GL_CONTEXT (gl_context));
+
+ return _gdk_macos_gl_context_make_current (GDK_MACOS_GL_CONTEXT (gl_context));
+}
+
+static void
+gdk_macos_display_finalize (GObject *object)
+{
+ GdkMacosDisplay *self = (GdkMacosDisplay *)object;
+
+ CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+ NULL);
+
+ CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ CFSTR ("NSUserDefaultsDidChangeNotification"),
+ NULL);
+
+ g_clear_object (&GDK_DISPLAY (self)->clipboard);
+ g_clear_pointer (&self->frame_source, g_source_unref);
+ g_clear_object (&self->monitors);
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (gdk_macos_display_parent_class)->finalize (object);
+}
+
+static void
+gdk_macos_display_class_init (GdkMacosDisplayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkDisplayClass *display_class = GDK_DISPLAY_CLASS (klass);
+
+ object_class->finalize = gdk_macos_display_finalize;
+
+ display_class->cairo_context_type = GDK_TYPE_MACOS_CAIRO_CONTEXT;
+
+ display_class->beep = gdk_macos_display_beep;
+ display_class->create_surface = gdk_macos_display_create_surface;
+ display_class->flush = gdk_macos_display_flush;
+ display_class->get_keymap = gdk_macos_display_get_keymap;
+ display_class->get_monitors = gdk_macos_display_get_monitors;
+ display_class->get_monitor_at_surface = gdk_macos_display_get_monitor_at_surface;
+ display_class->get_next_serial = gdk_macos_display_get_next_serial;
+ display_class->get_name = gdk_macos_display_get_name;
+ display_class->get_setting = gdk_macos_display_get_setting;
+ display_class->has_pending = gdk_macos_display_has_pending;
+ display_class->make_gl_context_current = gdk_macos_display_make_gl_context_current;
+ display_class->notify_startup_complete = gdk_macos_display_notify_startup_complete;
+ display_class->queue_events = gdk_macos_display_queue_events;
+ display_class->sync = gdk_macos_display_sync;
+}
+
+static void
+gdk_macos_display_init (GdkMacosDisplay *self)
+{
+ self->monitors = g_list_store_new (GDK_TYPE_MONITOR);
+
+ gdk_display_set_composited (GDK_DISPLAY (self), TRUE);
+ gdk_display_set_input_shapes (GDK_DISPLAY (self), FALSE);
+ gdk_display_set_rgba (GDK_DISPLAY (self), TRUE);
+}
+
+GdkDisplay *
+_gdk_macos_display_open (const gchar *display_name)
+{
+ static GdkMacosDisplay *self;
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+
+ /* Until we can have multiple GdkMacosEventSource instances
+ * running concurrently, we can't exactly support multiple
+ * display connections. So just short-circuit if we already
+ * have one active.
+ */
+ if (self != NULL)
+ return NULL;
+
+ GDK_NOTE (MISC, g_message ("opening display %s", display_name ? display_name : ""));
+
+ /* Make the current process a foreground application, i.e. an app
+ * with a user interface, in case we're not running from a .app bundle
+ */
+ TransformProcessType (&psn, kProcessTransformToForegroundApplication);
+
+ [NSApplication sharedApplication];
+
+ self = g_object_new (GDK_TYPE_MACOS_DISPLAY, NULL);
+ self->name = g_strdup (display_name);
+ self->keymap = _gdk_macos_keymap_new (self);
+
+ gdk_macos_display_load_seat (self);
+ gdk_macos_display_load_clipboard (self);
+
+ /* Load CVDisplayLink before monitors to access refresh rates */
+ gdk_macos_display_load_display_link (self);
+ _gdk_macos_display_reload_monitors (self);
+
+ CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (),
+ self,
+ gdk_macos_display_monitors_changed_cb,
+ CFSTR ("NSApplicationDidChangeScreenParametersNotification"),
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (),
+ self,
+ gdk_macos_display_user_defaults_changed_cb,
+ CFSTR ("NSUserDefaultsDidChangeNotification"),
+ NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ if (event_source == NULL)
+ {
+ event_source = _gdk_macos_event_source_new (self);
+ g_source_attach (event_source, NULL);
+ }
+
+ g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+ gdk_display_emit_opened (GDK_DISPLAY (self));
+
+ return GDK_DISPLAY (self);
+}
+
+void
+_gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if (out_y)
+ *out_y = self->height - y + self->min_y;
+
+ if (out_x)
+ *out_x = x + self->min_x;
+}
+
+void
+_gdk_macos_display_from_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *out_x,
+ int *out_y)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if (out_y != NULL)
+ *out_y = self->height - y + self->min_y;
+
+ if (out_x != NULL)
+ *out_x = x - self->min_x;
+}
+
+GdkMonitor *
+_gdk_macos_display_get_monitor_at_coords (GdkMacosDisplay *self,
+ int x,
+ int y)
+{
+ guint n_monitors;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+ n_monitors = g_list_model_get_n_items (G_LIST_MODEL (self->monitors));
+
+ for (guint i = 0; i < n_monitors; i++)
+ {
+ GdkMacosMonitor *monitor = get_monitor (self, i);
+
+ if (gdk_rectangle_contains_point (&GDK_MONITOR (monitor)->geometry, x, y))
+ return GDK_MONITOR (monitor);
+ }
+
+ return NULL;
+}
+
+GdkMonitor *
+_gdk_macos_display_get_monitor_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+ _gdk_macos_display_from_display_coords (self, x, y, &x, &y);
+
+ return _gdk_macos_display_get_monitor_at_coords (self, x, y);
+}
+
+NSScreen *
+_gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
+ int x,
+ int y)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ NSArray *screens;
+ NSScreen *screen = NULL;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+ screens = [NSScreen screens];
+
+ for (id obj in screens)
+ {
+ NSRect geom = [obj frame];
+
+ if (x >= geom.origin.x && x <= geom.origin.x + geom.size.width &&
+ y >= geom.origin.y && y <= geom.origin.y + geom.size.height)
+ {
+ screen = obj;
+ break;
+ }
+ }
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return screen;
+}
+
+void
+_gdk_macos_display_break_all_grabs (GdkMacosDisplay *self,
+ guint32 time)
+{
+ GdkDevice *devices[2];
+ GdkSeat *seat;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ seat = gdk_display_get_default_seat (GDK_DISPLAY (self));
+ devices[0] = gdk_seat_get_keyboard (seat);
+ devices[1] = gdk_seat_get_pointer (seat);
+
+ for (guint i = 0; i < G_N_ELEMENTS (devices); i++)
+ {
+ GdkDevice *device = devices[i];
+ GdkDeviceGrabInfo *grab;
+
+ grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), device);
+
+ if (grab != NULL)
+ {
+ GdkEvent *event;
+ GList *node;
+
+ event = gdk_grab_broken_event_new (grab->surface,
+ device,
+ NULL,
+ grab->surface,
+ TRUE);
+ node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+ }
+ }
+}
+
+void
+_gdk_macos_display_queue_events (GdkMacosDisplay *self)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ gdk_macos_display_queue_events (GDK_DISPLAY (self));
+}
+
+static GdkMacosSurface *
+_gdk_macos_display_get_surface_at_coords (GdkMacosDisplay *self,
+ int x,
+ int y,
+ int *surface_x,
+ int *surface_y)
+{
+ const GList *surfaces;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+ g_return_val_if_fail (surface_x != NULL, NULL);
+ g_return_val_if_fail (surface_y != NULL, NULL);
+
+ surfaces = _gdk_macos_display_get_surfaces (self);
+
+ for (const GList *iter = surfaces; iter; iter = iter->next)
+ {
+ GdkSurface *surface = iter->data;
+ NSWindow *nswindow;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ if (!gdk_surface_get_mapped (surface))
+ continue;
+
+ nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
+
+ if (x >= GDK_MACOS_SURFACE (surface)->root_x &&
+ y >= GDK_MACOS_SURFACE (surface)->root_y &&
+ x <= (GDK_MACOS_SURFACE (surface)->root_x + surface->width) &&
+ y <= (GDK_MACOS_SURFACE (surface)->root_y + surface->height))
+ {
+ *surface_x = x - GDK_MACOS_SURFACE (surface)->root_x;
+ *surface_y = y - GDK_MACOS_SURFACE (surface)->root_y;
+
+ return GDK_MACOS_SURFACE (surface);
+ }
+ }
+
+ *surface_x = 0;
+ *surface_y = 0;
+
+ return NULL;
+}
+
+GdkMacosSurface *
+_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self,
+ double x,
+ double y,
+ int *surface_x,
+ int *surface_y)
+{
+ int x_gdk;
+ int y_gdk;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+ g_return_val_if_fail (surface_x != NULL, NULL);
+ g_return_val_if_fail (surface_y != NULL, NULL);
+
+ _gdk_macos_display_from_display_coords (self, x, y, &x_gdk, &y_gdk);
+
+ return _gdk_macos_display_get_surface_at_coords (self, x_gdk, y_gdk, surface_x, surface_y);
+}
+
+void
+_gdk_macos_display_add_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (!queue_contains (&self->awaiting_frames, &surface->frame))
+ {
+ g_queue_push_tail_link (&self->awaiting_frames, &surface->frame);
+
+ if (self->awaiting_frames.length == 1)
+ gdk_display_link_source_unpause ((GdkDisplayLinkSource *)self->frame_source);
+ }
+}
+
+void
+_gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self,
+ GdkMacosSurface *surface)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+ g_return_if_fail (GDK_IS_MACOS_SURFACE (surface));
+
+ if (queue_contains (&self->awaiting_frames, &surface->frame))
+ {
+ g_queue_unlink (&self->awaiting_frames, &surface->frame);
+
+ if (self->awaiting_frames.length == 0)
+ gdk_display_link_source_pause ((GdkDisplayLinkSource *)self->frame_source);
+ }
+}
+
+NSWindow *
+_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self,
+ int *x,
+ int *y)
+{
+ GdkMacosSurface *surface;
+ NSPoint point;
+
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+
+ point = [NSEvent mouseLocation];
+
+ surface = _gdk_macos_display_get_surface_at_display_coords (self, point.x, point.y, x, y);
+ if (surface != NULL)
+ return _gdk_macos_surface_get_native (surface);
+
+ return NULL;
+}
+
+int
+_gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), 60 * 1000);
+
+ if (self->frame_source == NULL)
+ return 60 * 1000;
+
+ return ((GdkDisplayLinkSource *)self->frame_source)->refresh_rate;
+}
+
+void
+_gdk_macos_display_clear_sorting (GdkMacosDisplay *self)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ self->sorted_surfaces.head = NULL;
+ self->sorted_surfaces.tail = NULL;
+ self->sorted_surfaces.length = 0;
+}
+
+const GList *
+_gdk_macos_display_get_surfaces (GdkMacosDisplay *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), NULL);
+
+ if (self->sorted_surfaces.length == 0)
+ {
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ NSArray *array = [NSApp orderedWindows];
+ GQueue sorted = G_QUEUE_INIT;
+
+ for (id obj in array)
+ {
+ NSWindow *nswindow = (NSWindow *)obj;
+ GdkMacosSurface *surface;
+
+ if (!GDK_IS_MACOS_WINDOW (nswindow))
+ continue;
+
+ surface = [(GdkMacosWindow *)nswindow gdkSurface];
+
+ surface->sorted.prev = NULL;
+ surface->sorted.next = NULL;
+
+ g_queue_push_tail_link (&sorted, &surface->sorted);
+ }
+
+ self->sorted_surfaces = sorted;
+
+ /* We don't get notification of clipboard changes from the system so we
+ * instead update it every time the foreground changes (and thusly
+ * rebuild the sorted list). Things could change other ways, such as
+ * with scripts, but that is currently out of scope for us.
+ */
+ _gdk_macos_clipboard_check_externally_modified (
+ GDK_MACOS_CLIPBOARD (GDK_DISPLAY (self)->clipboard));
+
+ GDK_END_MACOS_ALLOC_POOL;
+ }
+
+ return self->sorted_surfaces.head;
+}
+
+void
+_gdk_macos_display_warp_pointer (GdkMacosDisplay *self,
+ int x,
+ int y)
+{
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ _gdk_macos_display_to_display_coords (self, x, y, &x, &y);
+
+ CGWarpMouseCursorPosition ((CGPoint) { x, y });
+}
diff --git a/gdk/macos/gdkmacosdisplay.h b/gdk/macos/gdkmacosdisplay.h
new file mode 100644
index 0000000000..7c5730d0c7
--- /dev/null
+++ b/gdk/macos/gdkmacosdisplay.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * 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 can be included directly."
+#endif
+
+#include
+
+G_BEGIN_DECLS
+
+#ifdef GTK_COMPILATION
+typedef struct _GdkMacosDisplay GdkMacosDisplay;
+#else
+typedef GdkDisplay GdkMacosDisplay;
+#endif
+typedef struct _GdkMacosDisplayClass GdkMacosDisplayClass;
+
+#define GDK_TYPE_MACOS_DISPLAY (gdk_macos_display_get_type())
+#define GDK_MACOS_DISPLAY(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DISPLAY, GdkMacosDisplay))
+#define GDK_IS_MACOS_DISPLAY(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DISPLAY))
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_display_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DISPLAY_H__ */
diff --git a/gdk/macos/gdkmacosdrag-private.h b/gdk/macos/gdkmacosdrag-private.h
new file mode 100644
index 0000000000..98075f27ef
--- /dev/null
+++ b/gdk/macos/gdkmacosdrag-private.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DRAG_PRIVATE_H__
+#define __GDK_MACOS_DRAG_PRIVATE_H__
+
+#include "gdkdragprivate.h"
+
+#include "gdkmacosdragsurface-private.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_DRAG (gdk_macos_drag_get_type ())
+#define GDK_MACOS_DRAG(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DRAG, GdkMacosDrag))
+#define GDK_MACOS_DRAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_MACOS_DRAG, GdkMacosDragClass))
+#define GDK_IS_MACOS_DRAG(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DRAG))
+#define GDK_IS_MACOS_DRAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_MACOS_DRAG))
+#define GDK_MACOS_DRAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_MACOS_DRAG, GdkMacosDragClass))
+
+typedef struct _GdkMacosDrag GdkMacosDrag;
+typedef struct _GdkMacosDragClass GdkMacosDragClass;
+
+struct _GdkMacosDrag
+{
+ GdkDrag parent_instance;
+
+ GdkMacosDragSurface *drag_surface;
+ GdkSeat *drag_seat;
+ GdkCursor *cursor;
+
+ int hot_x;
+ int hot_y;
+
+ int last_x;
+ int last_y;
+
+ int start_x;
+ int start_y;
+
+ guint did_update : 1;
+ guint cancelled : 1;
+};
+
+struct _GdkMacosDragClass
+{
+ GdkDragClass parent_class;
+};
+
+GType gdk_macos_drag_get_type (void) G_GNUC_CONST;
+gboolean _gdk_macos_drag_begin (GdkMacosDrag *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DRAG_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdrag.c b/gdk/macos/gdkmacosdrag.c
new file mode 100644
index 0000000000..de1324c674
--- /dev/null
+++ b/gdk/macos/gdkmacosdrag.c
@@ -0,0 +1,617 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkdeviceprivate.h"
+#include "gdkintl.h"
+
+#include "gdkmacoscursor-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosdrag-private.h"
+#include "gdkmacosdragsurface-private.h"
+
+#define BIG_STEP 20
+#define SMALL_STEP 1
+#define ANIM_TIME 500000 /* .5 seconds */
+
+typedef struct
+{
+ GdkMacosDrag *drag;
+ GdkFrameClock *frame_clock;
+ gint64 start_time;
+} GdkMacosZoomback;
+
+G_DEFINE_TYPE (GdkMacosDrag, gdk_macos_drag, GDK_TYPE_DRAG)
+
+enum {
+ PROP_0,
+ PROP_DRAG_SURFACE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static double
+ease_out_cubic (double t)
+{
+ double p = t - 1;
+ return p * p * p + 1;
+}
+
+static void
+gdk_macos_zoomback_destroy (GdkMacosZoomback *zb)
+{
+ gdk_surface_hide (GDK_SURFACE (zb->drag->drag_surface));
+ g_clear_object (&zb->drag);
+ g_slice_free (GdkMacosZoomback, zb);
+}
+
+static gboolean
+gdk_macos_zoomback_timeout (gpointer data)
+{
+ GdkMacosZoomback *zb = data;
+ GdkFrameClock *frame_clock;
+ GdkMacosDrag *drag;
+ gint64 current_time;
+ double f;
+ double t;
+
+ g_assert (zb != NULL);
+ g_assert (GDK_IS_MACOS_DRAG (zb->drag));
+
+ drag = zb->drag;
+ frame_clock = zb->frame_clock;
+
+ if (!frame_clock)
+ return G_SOURCE_REMOVE;
+
+ current_time = gdk_frame_clock_get_frame_time (frame_clock);
+ f = (current_time - zb->start_time) / (double) ANIM_TIME;
+ if (f >= 1.0)
+ return G_SOURCE_REMOVE;
+
+ t = ease_out_cubic (f);
+
+ _gdk_macos_surface_move (GDK_MACOS_SURFACE (drag->drag_surface),
+ (drag->last_x - drag->hot_x) +
+ (drag->start_x - drag->last_x) * t,
+ (drag->last_y - drag->hot_y) +
+ (drag->start_y - drag->last_y) * t);
+ _gdk_macos_surface_set_opacity (GDK_MACOS_SURFACE (drag->drag_surface), 1.0 - f);
+
+ /* Make sure we're topmost */
+ _gdk_macos_surface_show (GDK_MACOS_SURFACE (drag->drag_surface));
+
+ return G_SOURCE_CONTINUE;
+}
+
+static GdkSurface *
+gdk_macos_drag_get_drag_surface (GdkDrag *drag)
+{
+ return GDK_SURFACE (GDK_MACOS_DRAG (drag)->drag_surface);
+}
+
+static void
+gdk_macos_drag_set_hotspot (GdkDrag *drag,
+ int hot_x,
+ int hot_y)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+ int change_x;
+ int change_y;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ change_x = hot_x - self->hot_x;
+ change_y = hot_y - self->hot_y;
+
+ self->hot_x = hot_x;
+ self->hot_y = hot_y;
+
+ if (change_x || change_y)
+ _gdk_macos_surface_move (GDK_MACOS_SURFACE (self->drag_surface),
+ GDK_SURFACE (self->drag_surface)->x + change_x,
+ GDK_SURFACE (self->drag_surface)->y + change_y);
+}
+
+static void
+gdk_macos_drag_drop_done (GdkDrag *drag,
+ gboolean success)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+ GdkMacosZoomback *zb;
+ guint id;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ if (success)
+ {
+ gdk_surface_hide (GDK_SURFACE (self->drag_surface));
+ g_object_unref (drag);
+ return;
+ }
+
+ /* Apple HIG suggests doing a "zoomback" animation of the surface back
+ * towards the original position.
+ */
+ zb = g_slice_new0 (GdkMacosZoomback);
+ zb->drag = g_object_ref (self);
+ zb->frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self->drag_surface));
+ zb->start_time = gdk_frame_clock_get_frame_time (zb->frame_clock);
+
+ id = g_timeout_add_full (G_PRIORITY_DEFAULT, 17,
+ gdk_macos_zoomback_timeout,
+ zb,
+ (GDestroyNotify) gdk_macos_zoomback_destroy);
+ g_source_set_name_by_id (id, "[gtk] gdk_macos_zoomback_timeout");
+ g_object_unref (drag);
+}
+
+static void
+gdk_macos_drag_set_cursor (GdkDrag *drag,
+ GdkCursor *cursor)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+ NSCursor *nscursor;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+ g_assert (!cursor || GDK_IS_CURSOR (cursor));
+
+ g_set_object (&self->cursor, cursor);
+
+ nscursor = _gdk_macos_cursor_get_ns_cursor (cursor);
+
+ if (nscursor != NULL)
+ [nscursor set];
+}
+
+static gboolean
+drag_grab (GdkMacosDrag *self)
+{
+ GdkSeat *seat;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ seat = gdk_device_get_seat (gdk_drag_get_device (GDK_DRAG (self)));
+
+ if (gdk_seat_grab (seat,
+ GDK_SURFACE (self->drag_surface),
+ GDK_SEAT_CAPABILITY_ALL_POINTING,
+ FALSE,
+ self->cursor,
+ NULL,
+ NULL,
+ NULL) != GDK_GRAB_SUCCESS)
+ return FALSE;
+
+ g_set_object (&self->drag_seat, seat);
+
+ return TRUE;
+}
+
+static void
+drag_ungrab (GdkMacosDrag *self)
+{
+ GdkDisplay *display;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ display = gdk_drag_get_display (GDK_DRAG (self));
+ _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), GDK_CURRENT_TIME);
+}
+
+static void
+gdk_macos_drag_cancel (GdkDrag *drag,
+ GdkDragCancelReason reason)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ if (self->cancelled)
+ return;
+
+ self->cancelled = TRUE;
+ drag_ungrab (self);
+ gdk_drag_drop_done (drag, FALSE);
+}
+
+static void
+gdk_macos_drag_drop_performed (GdkDrag *drag,
+ guint32 time)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ drag_ungrab (self);
+ g_signal_emit_by_name (drag, "dnd-finished");
+ gdk_drag_drop_done (drag, TRUE);
+}
+
+static void
+gdk_drag_get_current_actions (GdkModifierType state,
+ gint button,
+ GdkDragAction actions,
+ GdkDragAction *suggested_action,
+ GdkDragAction *possible_actions)
+{
+ *suggested_action = 0;
+ *possible_actions = 0;
+
+ if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
+ {
+ *suggested_action = GDK_ACTION_ASK;
+ *possible_actions = actions;
+ }
+ else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
+ {
+ if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
+ {
+ if (actions & GDK_ACTION_LINK)
+ {
+ *suggested_action = GDK_ACTION_LINK;
+ *possible_actions = GDK_ACTION_LINK;
+ }
+ }
+ else if (state & GDK_CONTROL_MASK)
+ {
+ if (actions & GDK_ACTION_COPY)
+ {
+ *suggested_action = GDK_ACTION_COPY;
+ *possible_actions = GDK_ACTION_COPY;
+ }
+ }
+ else
+ {
+ if (actions & GDK_ACTION_MOVE)
+ {
+ *suggested_action = GDK_ACTION_MOVE;
+ *possible_actions = GDK_ACTION_MOVE;
+ }
+ }
+ }
+ else
+ {
+ *possible_actions = actions;
+
+ if ((state & (GDK_ALT_MASK)) && (actions & GDK_ACTION_ASK))
+ *suggested_action = GDK_ACTION_ASK;
+ else if (actions & GDK_ACTION_COPY)
+ *suggested_action = GDK_ACTION_COPY;
+ else if (actions & GDK_ACTION_MOVE)
+ *suggested_action = GDK_ACTION_MOVE;
+ else if (actions & GDK_ACTION_LINK)
+ *suggested_action = GDK_ACTION_LINK;
+ }
+}
+
+static void
+gdk_drag_update (GdkDrag *drag,
+ gdouble x_root,
+ gdouble y_root,
+ GdkModifierType mods,
+ guint32 evtime)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)drag;
+ GdkDragAction suggested_action;
+ GdkDragAction possible_actions;
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+
+ self->last_x = x_root;
+ self->last_y = y_root;
+
+ gdk_drag_get_current_actions (mods,
+ GDK_BUTTON_PRIMARY,
+ gdk_drag_get_actions (drag),
+ &suggested_action,
+ &possible_actions);
+
+ _gdk_macos_drag_surface_drag_motion (self->drag_surface,
+ x_root - self->hot_x,
+ y_root - self->hot_y,
+ suggested_action,
+ possible_actions,
+ evtime);
+
+ if (!self->did_update)
+ {
+ self->start_x = self->last_x;
+ self->start_y = self->last_y;
+ self->did_update = TRUE;
+ }
+}
+
+static gboolean
+gdk_dnd_handle_motion_event (GdkDrag *drag,
+ GdkEvent *event)
+{
+ double x, y;
+ int x_root, y_root;
+
+ g_assert (GDK_IS_MACOS_DRAG (drag));
+ g_assert (event != NULL);
+
+ /* Ignore motion while doing zoomback */
+ if (GDK_MACOS_DRAG (drag)->cancelled)
+ return FALSE;
+
+ gdk_event_get_position (event, &x, &y);
+ x_root = event->surface->x + x;
+ y_root = event->surface->y + y;
+ gdk_drag_update (drag, x_root, y_root,
+ gdk_event_get_modifier_state (event),
+ gdk_event_get_time (event));
+
+ return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_grab_broken_event (GdkDrag *drag,
+ GdkEvent *event)
+{
+ GdkMacosDrag *self = GDK_MACOS_DRAG (drag);
+ gboolean is_implicit = gdk_grab_broken_event_get_implicit (event);
+ GdkSurface *grab_surface = gdk_grab_broken_event_get_grab_surface (event);
+
+ /* Don't cancel if we break the implicit grab from the initial button_press. */
+ if (is_implicit || grab_surface == (GdkSurface *)self->drag_surface)
+ return FALSE;
+
+ if (gdk_event_get_device (event) != gdk_drag_get_device (drag))
+ return FALSE;
+
+ gdk_drag_cancel (drag, GDK_DRAG_CANCEL_ERROR);
+
+ return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_button_event (GdkDrag *drag,
+ GdkEvent *event)
+{
+ GdkMacosDrag *self = GDK_MACOS_DRAG (drag);
+
+ g_assert (GDK_IS_MACOS_DRAG (self));
+ g_assert (event != NULL);
+
+#if 0
+ /* FIXME: Check the button matches */
+ if (event->button != self->button)
+ return FALSE;
+#endif
+
+ if (gdk_drag_get_selected_action (drag) != 0)
+ g_signal_emit_by_name (drag, "drop-performed");
+ else
+ gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET);
+
+ return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_key_event (GdkDrag *drag,
+ GdkEvent *event)
+{
+ GdkMacosDrag *self = GDK_MACOS_DRAG (drag);
+ GdkModifierType state;
+ GdkDevice *pointer;
+ gint dx, dy;
+
+ dx = dy = 0;
+ state = gdk_event_get_modifier_state (event);
+ pointer = gdk_device_get_associated_device (gdk_event_get_device (event));
+
+ if (event->event_type == GDK_KEY_PRESS)
+ {
+ guint keyval = gdk_key_event_get_keyval (event);
+
+ switch (keyval)
+ {
+ case GDK_KEY_Escape:
+ gdk_drag_cancel (drag, GDK_DRAG_CANCEL_USER_CANCELLED);
+ return TRUE;
+
+ case GDK_KEY_space:
+ case GDK_KEY_Return:
+ case GDK_KEY_ISO_Enter:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_KP_Space:
+ if (gdk_drag_get_selected_action (drag) != 0)
+ g_signal_emit_by_name (drag, "drop-performed");
+ else
+ gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET);
+
+ return TRUE;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ dy = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP;
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ dy = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP;
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ dx = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP;
+ break;
+
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ dx = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* The state is not yet updated in the event, so we need
+ * to query it here. We could use XGetModifierMapping, but
+ * that would be overkill.
+ */
+ _gdk_device_query_state (pointer, NULL, NULL, NULL, NULL, &state);
+
+ if (dx != 0 || dy != 0)
+ {
+ GdkDisplay *display = gdk_event_get_display ((GdkEvent *)event);
+
+ self->last_x += dx;
+ self->last_y += dy;
+
+ _gdk_macos_display_warp_pointer (GDK_MACOS_DISPLAY (display),
+ self->last_x,
+ self->last_y);
+ }
+
+ gdk_drag_update (drag,
+ self->last_x, self->last_y,
+ state,
+ gdk_event_get_time (event));
+
+ return TRUE;
+}
+
+static gboolean
+gdk_macos_drag_handle_event (GdkDrag *drag,
+ GdkEvent *event)
+{
+ g_assert (GDK_IS_MACOS_DRAG (drag));
+ g_assert (event != NULL);
+
+ switch ((guint) event->event_type)
+ {
+ case GDK_MOTION_NOTIFY:
+ return gdk_dnd_handle_motion_event (drag, event);
+
+ case GDK_BUTTON_RELEASE:
+ return gdk_dnd_handle_button_event (drag, event);
+
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ return gdk_dnd_handle_key_event (drag, event);
+
+ case GDK_GRAB_BROKEN:
+ return gdk_dnd_handle_grab_broken_event (drag, event);
+
+ default:
+ return FALSE;
+ }
+}
+
+static void
+gdk_macos_drag_finalize (GObject *object)
+{
+ GdkMacosDrag *self = (GdkMacosDrag *)object;
+ GdkMacosDragSurface *drag_surface = g_steal_pointer (&self->drag_surface);
+
+ g_clear_object (&self->cursor);
+ g_clear_object (&self->drag_seat);
+
+ G_OBJECT_CLASS (gdk_macos_drag_parent_class)->finalize (object);
+
+ if (drag_surface)
+ gdk_surface_destroy (GDK_SURFACE (drag_surface));
+}
+
+static void
+gdk_macos_drag_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkMacosDrag *self = GDK_MACOS_DRAG (object);
+
+ switch (prop_id)
+ {
+ case PROP_DRAG_SURFACE:
+ g_value_set_object (value, self->drag_surface);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gdk_macos_drag_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdkMacosDrag *self = GDK_MACOS_DRAG (object);
+
+ switch (prop_id)
+ {
+ case PROP_DRAG_SURFACE:
+ self->drag_surface = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gdk_macos_drag_class_init (GdkMacosDragClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkDragClass *drag_class = GDK_DRAG_CLASS (klass);
+
+ object_class->finalize = gdk_macos_drag_finalize;
+ object_class->get_property = gdk_macos_drag_get_property;
+ object_class->set_property = gdk_macos_drag_set_property;
+
+ drag_class->get_drag_surface = gdk_macos_drag_get_drag_surface;
+ drag_class->set_hotspot = gdk_macos_drag_set_hotspot;
+ drag_class->drop_done = gdk_macos_drag_drop_done;
+ drag_class->set_cursor = gdk_macos_drag_set_cursor;
+ drag_class->cancel = gdk_macos_drag_cancel;
+ drag_class->drop_performed = gdk_macos_drag_drop_performed;
+ drag_class->handle_event = gdk_macos_drag_handle_event;
+
+ properties [PROP_DRAG_SURFACE] =
+ g_param_spec_object ("drag-surface",
+ P_("Drag Surface"),
+ P_("Drag Surface"),
+ GDK_TYPE_MACOS_DRAG_SURFACE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gdk_macos_drag_init (GdkMacosDrag *self)
+{
+}
+
+gboolean
+_gdk_macos_drag_begin (GdkMacosDrag *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_DRAG (self), FALSE);
+
+ _gdk_macos_surface_show (GDK_MACOS_SURFACE (self->drag_surface));
+
+ return drag_grab (self);
+}
diff --git a/gdk/macos/gdkmacosdragsurface-private.h b/gdk/macos/gdkmacosdragsurface-private.h
new file mode 100644
index 0000000000..cf7408f308
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface-private.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__
+#define __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__
+
+#include "gdkmacossurface-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GdkMacosDragSurface GdkMacosDragSurface;
+typedef struct _GdkMacosDragSurfaceClass GdkMacosDragSurfaceClass;
+
+#define GDK_TYPE_MACOS_DRAG_SURFACE (_gdk_macos_drag_surface_get_type())
+#define GDK_MACOS_DRAG_SURFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_MACOS_DRAG_SURFACE, GdkMacosDragSurface))
+#define GDK_IS_MACOS_DRAG_SURFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_MACOS_DRAG_SURFACE))
+
+GType _gdk_macos_drag_surface_get_type (void);
+GdkMacosSurface *_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height);
+void _gdk_macos_drag_surface_drag_motion (GdkMacosDragSurface *self,
+ int x_root,
+ int y_root,
+ GdkDragAction suggested_action,
+ GdkDragAction possible_actions,
+ guint32 evtime);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_DRAG_SURFACE_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosdragsurface.c b/gdk/macos/gdkmacosdragsurface.c
new file mode 100644
index 0000000000..2b03583604
--- /dev/null
+++ b/gdk/macos/gdkmacosdragsurface.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkdragsurfaceprivate.h"
+
+#include "gdkmacosdragsurface-private.h"
+#include "gdkmacosdisplay-private.h"
+#include "gdkmacosutils-private.h"
+
+struct _GdkMacosDragSurface
+{
+ GdkMacosSurface parent_instance;
+};
+
+struct _GdkMacosDragSurfaceClass
+{
+ GdkMacosSurfaceClass parent_instance;
+};
+
+static gboolean
+_gdk_macos_drag_surface_present (GdkDragSurface *surface,
+ int width,
+ int height)
+{
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ _gdk_macos_surface_move_resize (GDK_MACOS_SURFACE (surface),
+ -1, -1,
+ width, height);
+
+ if (!GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface)))
+ _gdk_macos_surface_show (GDK_MACOS_SURFACE (surface));
+
+ return GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface));
+}
+
+static void
+drag_surface_iface_init (GdkDragSurfaceInterface *iface)
+{
+ iface->present = _gdk_macos_drag_surface_present;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkMacosDragSurface, _gdk_macos_drag_surface, GDK_TYPE_MACOS_SURFACE,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_DRAG_SURFACE, drag_surface_iface_init))
+
+static void
+_gdk_macos_drag_surface_class_init (GdkMacosDragSurfaceClass *klass)
+{
+}
+
+static void
+_gdk_macos_drag_surface_init (GdkMacosDragSurface *self)
+{
+}
+
+GdkMacosSurface *
+_gdk_macos_drag_surface_new (GdkMacosDisplay *display,
+ GdkFrameClock *frame_clock,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GDK_BEGIN_MACOS_ALLOC_POOL;
+
+ GdkMacosWindow *window;
+ GdkMacosSurface *self;
+ NSScreen *screen;
+ NSUInteger style_mask;
+ NSRect content_rect;
+ NSRect screen_rect;
+ int nx;
+ int ny;
+
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+ g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+
+ style_mask = NSWindowStyleMaskBorderless;
+
+ _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
+
+ screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
+ screen_rect = [screen frame];
+ nx -= screen_rect.origin.x;
+ ny -= screen_rect.origin.y;
+ content_rect = NSMakeRect (nx, ny - height, width, height);
+
+ window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
+ styleMask:style_mask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:screen];
+
+ [window setOpaque:NO];
+ [window setBackgroundColor:[NSColor clearColor]];
+ [window setDecorated:NO];
+
+ self = g_object_new (GDK_TYPE_MACOS_DRAG_SURFACE,
+ "display", display,
+ "frame-clock", frame_clock,
+ "native", window,
+ NULL);
+
+ GDK_END_MACOS_ALLOC_POOL;
+
+ return g_steal_pointer (&self);
+}
+
+void
+_gdk_macos_drag_surface_drag_motion (GdkMacosDragSurface *self,
+ int x_root,
+ int y_root,
+ GdkDragAction suggested_action,
+ GdkDragAction possible_actions,
+ guint32 evtime)
+{
+ g_return_if_fail (GDK_IS_MACOS_DRAG_SURFACE (self));
+
+ _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x_root, y_root);
+}
diff --git a/gdk/macos/gdkmacoseventsource-private.h b/gdk/macos/gdkmacoseventsource-private.h
new file mode 100644
index 0000000000..09853a18ac
--- /dev/null
+++ b/gdk/macos/gdkmacoseventsource-private.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+#define __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+
+#include
+
+#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/quartz/gdkeventloop-quartz.c b/gdk/macos/gdkmacoseventsource.c
similarity index 69%
rename from gdk/quartz/gdkeventloop-quartz.c
rename to gdk/macos/gdkmacoseventsource.c
index 031a207b4a..f173433de4 100644
--- a/gdk/quartz/gdkeventloop-quartz.c
+++ b/gdk/macos/gdkmacoseventsource.c
@@ -1,3 +1,23 @@
+/* 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 .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
#include "config.h"
#include
@@ -6,10 +26,13 @@
#include
#include
-#include "gdkprivate-quartz.h"
-#include
+#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
@@ -55,16 +78,16 @@ 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 need to call select_thread_collect_poll()
+ * 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_priority to pass to g_main_loop_check()
+ * max_prioritiy to pass to g_main_loop_check()
*/
static gint run_loop_max_priority;
@@ -95,7 +118,7 @@ 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 emitted
+ * 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;
@@ -132,7 +155,7 @@ static gint getting_events = 0;
************************************************************/
/* The states in our state machine, see comments in select_thread_func()
- * for descriptions of each state
+ * for descriptiions of each state
*/
typedef enum {
BEFORE_START,
@@ -220,93 +243,93 @@ 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 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;
+ {
+ case BEFORE_START:
+ /* The select thread has not been started yet
+ */
+ g_assert_not_reached ();
- next_pollfds = NULL;
- next_n_pollfds = 0;
+ 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;
- 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_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);
- 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 ();
+ current_pollfds = next_pollfds;
+ current_n_pollfds = next_n_pollfds;
- read (select_thread_wakeup_pipe[0], &c, 1);
+ next_pollfds = NULL;
+ next_n_pollfds = 0;
- if (select_thread_state == POLLING_DESCRIPTORS)
- {
- signal_main_thread ();
- select_thread_set_state (WAITING);
- }
- break;
- }
+ 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
+static void
got_fd_activity (void *info)
{
NSEvent *event;
/* Post a message so we'll break out of the message loop */
- event = [NSEvent otherEventWithType: NSApplicationDefined
- location: NSZeroPoint
- modifierFlags: 0
- timestamp: 0
- windowNumber: 0
- context: nil
- subtype: GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP
- data1: 0
- data2: 0];
+ 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];
}
@@ -315,21 +338,21 @@ 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;
+ break;
g_warning ("Failed to create select thread, sleeping and trying again");
sleep (1);
@@ -339,7 +362,7 @@ select_thread_start (void)
#ifdef G_ENABLE_DEBUG
static void
dump_poll_result (GPollFD *ufds,
- guint nfds)
+ guint nfds)
{
GString *s;
gint i;
@@ -348,44 +371,44 @@ dump_poll_result (GPollFD *ufds,
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)
+ 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");
- }
+ 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
-gboolean
+static gboolean
pollfds_equal (GPollFD *old_pollfds,
- guint old_n_pollfds,
- GPollFD *new_pollfds,
- guint new_n_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;
+ old_pollfds[i].events != new_pollfds[i].events)
+ return FALSE;
}
return TRUE;
}
-/* Begins a polling operation with the specified GPollFD array; the
+/* 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.
*
@@ -396,7 +419,8 @@ pollfds_equal (GPollFD *old_pollfds,
*/
static gint
select_thread_start_poll (GPollFD *ufds,
- guint nfds, gint timeout)
+ guint nfds,
+ gint timeout)
{
gint n_ready;
gboolean have_new_pollfds = FALSE;
@@ -406,10 +430,10 @@ select_thread_start_poll (GPollFD *ufds,
for (i = 0; i < nfds; i++)
if (ufds[i].fd == -1)
{
- poll_fd_index = i;
- break;
+ poll_fd_index = i;
+ break;
}
-
+
if (nfds == 0 ||
(nfds == 1 && poll_fd_index >= 0))
{
@@ -431,35 +455,35 @@ select_thread_start_poll (GPollFD *ufds,
{
#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);
- }
+ {
+ 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;
- }
+ {
+ 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)
{
@@ -481,7 +505,7 @@ select_thread_start_poll (GPollFD *ufds,
* 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
@@ -498,13 +522,13 @@ select_thread_start_poll (GPollFD *ufds,
* from a file descriptor that hangs.
*/
if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
- have_new_pollfds = TRUE;
+ 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);
- }
+ {
+ 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;
@@ -512,28 +536,28 @@ select_thread_start_poll (GPollFD *ufds,
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);
- }
- }
-
+ {
+ 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;
@@ -553,32 +577,32 @@ 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 (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++;
+ }
+ }
- 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);
- }
+ {
+ g_message ("EventLoop: Found ready file descriptors after waiting");
+ dump_poll_result (ufds, nfds);
+ }
#endif
}
@@ -591,14 +615,20 @@ select_thread_collect_poll (GPollFD *ufds, guint nfds)
********* Main Loop Source *********
************************************************************/
+typedef struct _GdkMacosEventSource
+{
+ GSource source;
+ GdkDisplay *display;
+} GdkMacosEventSource;
+
gboolean
-_gdk_quartz_event_loop_check_pending (void)
+_gdk_macos_event_source_check_pending (void)
{
return current_events && current_events->head;
}
-NSEvent*
-_gdk_quartz_event_loop_get_pending (void)
+NSEvent *
+_gdk_macos_event_source_get_pending (void)
{
NSEvent *event = NULL;
@@ -608,16 +638,11 @@ _gdk_quartz_event_loop_get_pending (void)
return event;
}
-void
-_gdk_quartz_event_loop_release_event (NSEvent *event)
-{
- [event release];
-}
-
static gboolean
-gdk_event_prepare (GSource *source,
- gint *timeout)
+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
@@ -647,55 +672,65 @@ gdk_event_prepare (GSource *source,
*timeout = -1;
- if (_gdk_display->event_pause_count > 0)
- retval = _gdk_event_queue_find_first (_gdk_display) != NULL;
+ 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 (_gdk_display) != NULL ||
- _gdk_quartz_event_loop_check_pending ());
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
return retval;
}
static gboolean
-gdk_event_check (GSource *source)
+gdk_macos_event_source_check (GSource *source)
{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
gboolean retval;
- if (_gdk_display->event_pause_count > 0)
- retval = _gdk_event_queue_find_first (_gdk_display) != NULL;
+ 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 (_gdk_display) != NULL ||
- _gdk_quartz_event_loop_check_pending ());
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
return retval;
}
static gboolean
-gdk_event_dispatch (GSource *source,
- GSourceFunc callback,
- gpointer user_data)
+gdk_macos_event_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
GdkEvent *event;
- _gdk_quartz_display_queue_events (_gdk_display);
+ _gdk_macos_display_queue_events (GDK_MACOS_DISPLAY (event_source->display));
- event = _gdk_event_unqueue (_gdk_display);
+ event = _gdk_event_unqueue (event_source->display);
if (event)
{
_gdk_event_emit (event);
- g_object_unref (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_event_prepare,
- gdk_event_check,
- gdk_event_dispatch,
- NULL
+ gdk_macos_event_source_prepare,
+ gdk_macos_event_source_check,
+ gdk_macos_event_source_dispatch,
+ gdk_macos_event_source_finalize,
};
/************************************************************
@@ -704,8 +739,8 @@ static GSourceFuncs event_funcs = {
static gint
poll_func (GPollFD *ufds,
- guint nfds,
- gint timeout_)
+ guint nfds,
+ gint timeout_)
{
NSEvent *event;
NSDate *limit_date;
@@ -727,9 +762,9 @@ poll_func (GPollFD *ufds,
limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
getting_events++;
- event = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: limit_date
- inMode: NSDefaultRunLoopMode
+ event = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: limit_date
+ inMode: NSDefaultRunLoopMode
dequeue: YES];
getting_events--;
@@ -744,10 +779,10 @@ poll_func (GPollFD *ufds,
*/
if (last_ufds == ufds && n_ready < 0)
n_ready = select_thread_collect_poll (ufds, nfds);
-
+
if (event &&
- [event type] == NSApplicationDefined &&
- [event subtype] == GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP)
+ [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,
@@ -756,7 +791,7 @@ poll_func (GPollFD *ufds,
event = NULL;
}
- if (event)
+ if (event)
{
if (!current_events)
current_events = g_queue_new ();
@@ -775,11 +810,11 @@ poll_func (GPollFD *ufds,
*/
static gint
query_main_context (GMainContext *context,
- int max_priority,
- int *timeout)
+ int max_priority,
+ int *timeout)
{
gint nfds;
-
+
if (!run_loop_pollfds)
{
run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
@@ -787,8 +822,8 @@ query_main_context (GMainContext *context,
}
while ((nfds = g_main_context_query (context, max_priority, timeout,
- run_loop_pollfds,
- run_loop_pollfds_size)) > run_loop_pollfds_size)
+ run_loop_pollfds,
+ run_loop_pollfds_size)) > run_loop_pollfds_size)
{
g_free (run_loop_pollfds);
run_loop_pollfds_size = nfds;
@@ -804,24 +839,24 @@ 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;
- }
+ {
+ 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"));
- }
+ {
+ /* 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"));
+ }
}
}
@@ -844,8 +879,8 @@ run_loop_before_sources (void)
* 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);
+
+ 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
@@ -855,7 +890,7 @@ run_loop_before_sources (void)
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"));
@@ -865,7 +900,7 @@ run_loop_before_sources (void)
static void
dummy_timer_callback (CFRunLoopTimerRef timer,
- void *info)
+ void *info)
{
/* Nothing; won't normally even be called */
}
@@ -883,9 +918,9 @@ run_loop_before_waiting (void)
* 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);
-
+
+ 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);
@@ -902,18 +937,18 @@ run_loop_before_waiting (void)
* 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);
+ 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;
}
@@ -932,13 +967,13 @@ run_loop_after_waiting (void)
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"));
@@ -960,8 +995,8 @@ run_loop_exit (void)
static void
run_loop_observer_callback (CFRunLoopObserverRef observer,
- CFRunLoopActivity activity,
- void *info)
+ CFRunLoopActivity activity,
+ void *info)
{
switch (activity)
{
@@ -972,6 +1007,11 @@ run_loop_observer_callback (CFRunLoopObserverRef observer,
g_return_if_fail (current_loop_level > 0);
current_loop_level--;
break;
+ case kCFRunLoopBeforeTimers:
+ case kCFRunLoopBeforeSources:
+ case kCFRunLoopBeforeWaiting:
+ case kCFRunLoopAfterWaiting:
+ case kCFRunLoopAllActivities:
default:
break;
}
@@ -999,6 +1039,8 @@ run_loop_observer_callback (CFRunLoopObserverRef observer,
case kCFRunLoopExit:
run_loop_exit ();
break;
+ case kCFRunLoopAllActivities:
+ /* TODO: Do most of the above? */
default:
break;
}
@@ -1006,41 +1048,47 @@ run_loop_observer_callback (CFRunLoopObserverRef observer,
/************************************************************/
-void
-_gdk_quartz_event_loop_init (void)
+GSource *
+_gdk_macos_event_source_new (GdkMacosDisplay *display)
{
- GSource *source;
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 (GSource));
- g_source_set_name (source, "GDK Quartz event source");
+ 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);
- g_source_attach (source, NULL);
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 */
+ kCFRunLoopAllActivities,
+ true, /* repeats: not one-shot */
+ 0, /* order (priority) */
+ run_loop_observer_callback,
+ NULL);
+ CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
+
+ /* Initialize our autorelease pool */
autorelease_pool = [[NSAutoreleasePool alloc] init];
+
+ return source;
}
diff --git a/gdk/macos/gdkmacosglcontext-private.h b/gdk/macos/gdkmacosglcontext-private.h
new file mode 100644
index 0000000000..e976939eb1
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext-private.h
@@ -0,0 +1,60 @@
+/* gdkmacosglcontext-private.h
+ *
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ */
+
+#ifndef __GDK_MACOS_GL_CONTEXT_PRIVATE_H__
+#define __GDK_MACOS_GL_CONTEXT_PRIVATE_H__
+
+#include "gdkglcontextprivate.h"
+#include "gdkdisplayprivate.h"
+#include "gdksurface.h"
+#include "gdkinternals.h"
+
+#include "gdkmacosglcontext.h"
+#include "gdkmacossurface.h"
+
+#import
+#import
+#import
+
+G_BEGIN_DECLS
+
+struct _GdkMacosGLContext
+{
+ GdkGLContext parent_instance;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ NSOpenGLContext *gl_context;
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ gboolean is_attached;
+};
+
+struct _GdkMacosGLContextClass
+{
+ GdkGLContextClass parent_class;
+};
+
+GdkGLContext *_gdk_macos_gl_context_new (GdkMacosSurface *surface,
+ gboolean attached,
+ GdkGLContext *share,
+ GError **error);
+gboolean _gdk_macos_gl_context_make_current (GdkMacosGLContext *self);
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_GL_CONTEXT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosglcontext.c b/gdk/macos/gdkmacosglcontext.c
new file mode 100644
index 0000000000..0d1e03e1d2
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gdkmacosglcontext-private.h"
+#include "gdkmacossurface-private.h"
+
+#include "gdkinternals.h"
+#include "gdkintl.h"
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+G_DEFINE_TYPE (GdkMacosGLContext, gdk_macos_gl_context, GDK_TYPE_GL_CONTEXT)
+
+static void
+gdk_macos_gl_context_end_frame (GdkDrawContext *context,
+ cairo_region_t *painted)
+{
+ GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (context);
+
+ g_assert (GDK_IS_MACOS_GL_CONTEXT (self));
+
+ [self->gl_context flushBuffer];
+}
+
+static void
+gdk_macos_gl_context_dispose (GObject *gobject)
+{
+ GdkMacosGLContext *context_macos = GDK_MACOS_GL_CONTEXT (gobject);
+
+ if (context_macos->gl_context != NULL)
+ {
+ [context_macos->gl_context clearDrawable];
+ [context_macos->gl_context release];
+ context_macos->gl_context = NULL;
+ }
+
+ G_OBJECT_CLASS (gdk_macos_gl_context_parent_class)->dispose (gobject);
+}
+
+static void
+gdk_macos_gl_context_class_init (GdkMacosGLContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
+
+ object_class->dispose = gdk_macos_gl_context_dispose;
+
+ draw_context_class->end_frame = gdk_macos_gl_context_end_frame;
+}
+
+static void
+gdk_macos_gl_context_init (GdkMacosGLContext *self)
+{
+}
+
+GdkGLContext *
+_gdk_macos_gl_context_new (GdkMacosSurface *surface,
+ gboolean attached,
+ GdkGLContext *share,
+ GError **error)
+{
+ static const NSOpenGLPixelFormatAttribute attrs[] = {
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ 0
+ };
+
+ NSOpenGLPixelFormat *format;
+ GdkMacosGLContext *context = NULL;
+ NSOpenGLContext *ctx;
+ GdkDisplay *display;
+ NSView *nsview;
+ GLint sync_to_framerate = 1;
+
+ g_return_val_if_fail (GDK_IS_MACOS_SURFACE (surface), NULL);
+ g_return_val_if_fail (!share || GDK_IS_MACOS_GL_CONTEXT (share), NULL);
+
+ display = gdk_surface_get_display (GDK_SURFACE (surface));
+
+ if (!(format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]))
+ {
+ g_set_error_literal (error,
+ GDK_GL_ERROR,
+ GDK_GL_ERROR_NOT_AVAILABLE,
+ _("Unable to create a GL pixel format"));
+ goto failure;
+ }
+
+ ctx = [[NSOpenGLContext alloc] initWithFormat:format
+ shareContext:share ? GDK_MACOS_GL_CONTEXT (share)->gl_context : nil];
+ if (ctx == NULL)
+ {
+ g_set_error_literal (error,
+ GDK_GL_ERROR,
+ GDK_GL_ERROR_NOT_AVAILABLE,
+ _("Unable to create a GL context"));
+ goto failure;
+ }
+
+ nsview = _gdk_macos_surface_get_view (surface);
+ [nsview setWantsBestResolutionOpenGLSurface:YES];
+ [ctx setValues:&sync_to_framerate forParameter:NSOpenGLCPSwapInterval];
+ [ctx setView:nsview];
+
+ GDK_NOTE (OPENGL,
+ g_print ("Created NSOpenGLContext[%p]\n", ctx));
+
+ context = g_object_new (GDK_TYPE_MACOS_GL_CONTEXT,
+ "surface", surface,
+ "shared-context", share,
+ NULL);
+
+ context->gl_context = ctx;
+ context->is_attached = attached;
+
+failure:
+ if (format != NULL)
+ [format release];
+
+ return GDK_GL_CONTEXT (context);
+}
+
+gboolean
+_gdk_macos_gl_context_make_current (GdkMacosGLContext *self)
+{
+ g_return_val_if_fail (GDK_IS_MACOS_GL_CONTEXT (self), FALSE);
+
+ [self->gl_context makeCurrentContext];
+
+ return TRUE;
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
diff --git a/gdk/macos/gdkmacosglcontext.h b/gdk/macos/gdkmacosglcontext.h
new file mode 100644
index 0000000000..e4add89502
--- /dev/null
+++ b/gdk/macos/gdkmacosglcontext.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_GL_CONTEXT_H__
+#define __GDK_MACOS_GL_CONTEXT_H__
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_GL_CONTEXT (gdk_macos_gl_context_get_type ())
+#define GDK_MACOS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_MACOS_GL_CONTEXT, GdkMacosGLContext))
+#define GDK_IS_MACOS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_MACOS_GL_CONTEXT))
+
+typedef struct _GdkMacosGLContext GdkMacosGLContext;
+typedef struct _GdkMacosGLContextClass GdkMacosGLContextClass;
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_gl_context_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_MACOS_GL_CONTEXT_H__ */
diff --git a/gdk/macos/gdkmacoskeymap-private.h b/gdk/macos/gdkmacoskeymap-private.h
new file mode 100644
index 0000000000..34ff21b225
--- /dev/null
+++ b/gdk/macos/gdkmacoskeymap-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GDK_MACOS_KEYMAP_PRIVATE_H__
+#define __GDK_MACOS_KEYMAP_PRIVATE_H__
+
+#include
+
+#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 .
+ *
+ * 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
+#include
+#include
+
+#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