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