src/video/cocoa/SDL_cocoawindow.m
author Ryan C. Gordon <icculus@icculus.org>
Tue, 21 Jan 2020 17:40:16 -0500
changeset 13436 2f35731b8d70
parent 13422 fd6a12de91c7
permissions -rw-r--r--
audio: Fixed a '//' style comment.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #if SDL_VIDEO_DRIVER_COCOA
    24 
    25 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
    26 # error SDL for Mac OS X must be built with a 10.7 SDK or above.
    27 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1070 */
    28 
    29 #include "SDL_syswm.h"
    30 #include "SDL_timer.h"  /* For SDL_GetTicks() */
    31 #include "SDL_hints.h"
    32 #include "../SDL_sysvideo.h"
    33 #include "../../events/SDL_keyboard_c.h"
    34 #include "../../events/SDL_mouse_c.h"
    35 #include "../../events/SDL_touch_c.h"
    36 #include "../../events/SDL_windowevents_c.h"
    37 #include "../../events/SDL_dropevents_c.h"
    38 #include "SDL_cocoavideo.h"
    39 #include "SDL_cocoashape.h"
    40 #include "SDL_cocoamouse.h"
    41 #include "SDL_cocoamousetap.h"
    42 #include "SDL_cocoaopengl.h"
    43 #include "SDL_cocoaopengles.h"
    44 #include "SDL_assert.h"
    45 
    46 /* #define DEBUG_COCOAWINDOW */
    47 
    48 #ifdef DEBUG_COCOAWINDOW
    49 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
    50 #else
    51 #define DLog(...) do { } while (0)
    52 #endif
    53 
    54 
    55 #define FULLSCREEN_MASK (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN)
    56 
    57 #ifndef MAC_OS_X_VERSION_10_12
    58 #define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
    59 #endif
    60 
    61 @interface SDLWindow : NSWindow <NSDraggingDestination>
    62 /* These are needed for borderless/fullscreen windows */
    63 - (BOOL)canBecomeKeyWindow;
    64 - (BOOL)canBecomeMainWindow;
    65 - (void)sendEvent:(NSEvent *)event;
    66 - (void)doCommandBySelector:(SEL)aSelector;
    67 
    68 /* Handle drag-and-drop of files onto the SDL window. */
    69 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
    70 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
    71 - (BOOL)wantsPeriodicDraggingUpdates;
    72 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
    73 
    74 - (SDL_Window*)findSDLWindow;
    75 @end
    76 
    77 @implementation SDLWindow
    78 
    79 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
    80 {
    81     /* Only allow using the macOS native fullscreen toggle menubar item if the
    82      * window is resizable and not in a SDL fullscreen mode.
    83      */
    84     if ([menuItem action] == @selector(toggleFullScreen:)) {
    85         SDL_Window *window = [self findSDLWindow];
    86         if (window == NULL) {
    87             return NO;
    88         } else if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0) {
    89             return NO;
    90         } else if ((window->flags & SDL_WINDOW_RESIZABLE) == 0) {
    91             return NO;
    92         }
    93     }
    94     return [super validateMenuItem:menuItem];
    95 }
    96 
    97 - (BOOL)canBecomeKeyWindow
    98 {
    99     return YES;
   100 }
   101 
   102 - (BOOL)canBecomeMainWindow
   103 {
   104     return YES;
   105 }
   106 
   107 - (void)sendEvent:(NSEvent *)event
   108 {
   109     [super sendEvent:event];
   110 
   111     if ([event type] != NSEventTypeLeftMouseUp) {
   112         return;
   113     }
   114 
   115     id delegate = [self delegate];
   116     if (![delegate isKindOfClass:[Cocoa_WindowListener class]]) {
   117         return;
   118     }
   119 
   120     if ([delegate isMoving]) {
   121         [delegate windowDidFinishMoving];
   122     }
   123 }
   124 
   125 /* We'll respond to selectors by doing nothing so we don't beep.
   126  * The escape key gets converted to a "cancel" selector, etc.
   127  */
   128 - (void)doCommandBySelector:(SEL)aSelector
   129 {
   130     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
   131 }
   132 
   133 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   134 {
   135     if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) {
   136         return NSDragOperationGeneric;
   137     }
   138 
   139     return NSDragOperationNone; /* no idea what to do with this, reject it. */
   140 }
   141 
   142 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   143 { @autoreleasepool
   144 {
   145     NSPasteboard *pasteboard = [sender draggingPasteboard];
   146     NSArray *types = [NSArray arrayWithObject:NSFilenamesPboardType];
   147     NSString *desiredType = [pasteboard availableTypeFromArray:types];
   148     SDL_Window *sdlwindow = [self findSDLWindow];
   149 
   150     if (desiredType == nil) {
   151         return NO;  /* can't accept anything that's being dropped here. */
   152     }
   153 
   154     NSData *data = [pasteboard dataForType:desiredType];
   155     if (data == nil) {
   156         return NO;
   157     }
   158 
   159     SDL_assert([desiredType isEqualToString:NSFilenamesPboardType]);
   160     NSArray *array = [pasteboard propertyListForType:@"NSFilenamesPboardType"];
   161 
   162     for (NSString *path in array) {
   163         NSURL *fileURL = [NSURL fileURLWithPath:path];
   164         NSNumber *isAlias = nil;
   165 
   166         [fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil];
   167 
   168         /* If the URL is an alias, resolve it. */
   169         if ([isAlias boolValue]) {
   170             NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI;
   171             NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil];
   172             if (bookmark != nil) {
   173                 NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark
   174                                                                options:opts
   175                                                          relativeToURL:nil
   176                                                    bookmarkDataIsStale:nil
   177                                                                  error:nil];
   178 
   179                 if (resolvedURL != nil) {
   180                     fileURL = resolvedURL;
   181                 }
   182             }
   183         }
   184 
   185         if (!SDL_SendDropFile(sdlwindow, [[fileURL path] UTF8String])) {
   186             return NO;
   187         }
   188     }
   189 
   190     SDL_SendDropComplete(sdlwindow);
   191     return YES;
   192 }}
   193 
   194 - (BOOL)wantsPeriodicDraggingUpdates
   195 {
   196     return NO;
   197 }
   198 
   199 - (SDL_Window*)findSDLWindow
   200 {
   201     SDL_Window *sdlwindow = NULL;
   202     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   203 
   204     /* !!! FIXME: is there a better way to do this? */
   205     if (_this) {
   206         for (sdlwindow = _this->windows; sdlwindow; sdlwindow = sdlwindow->next) {
   207             NSWindow *nswindow = ((SDL_WindowData *) sdlwindow->driverdata)->nswindow;
   208             if (nswindow == self) {
   209                 break;
   210             }
   211         }
   212     }
   213 
   214     return sdlwindow;
   215 }
   216 
   217 @end
   218 
   219 
   220 static Uint32 s_moveHack;
   221 
   222 static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
   223 {
   224     r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height;
   225 }
   226 
   227 static void
   228 ScheduleContextUpdates(SDL_WindowData *data)
   229 {
   230     if (!data || !data->nscontexts) {
   231         return;
   232     }
   233 
   234     /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
   235     #ifdef __clang__
   236     #pragma clang diagnostic push
   237     #pragma clang diagnostic ignored "-Wdeprecated-declarations"
   238     #endif
   239 
   240     NSOpenGLContext *currentContext = [NSOpenGLContext currentContext];
   241     NSMutableArray *contexts = data->nscontexts;
   242     @synchronized (contexts) {
   243         for (SDLOpenGLContext *context in contexts) {
   244             if (context == currentContext) {
   245                 [context update];
   246             } else {
   247                 [context scheduleUpdate];
   248             }
   249         }
   250     }
   251 
   252     #ifdef __clang__
   253     #pragma clang diagnostic pop
   254     #endif
   255 }
   256 
   257 /* !!! FIXME: this should use a hint callback. */
   258 static int
   259 GetHintCtrlClickEmulateRightClick()
   260 {
   261     return SDL_GetHintBoolean(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, SDL_FALSE);
   262 }
   263 
   264 static NSUInteger
   265 GetWindowWindowedStyle(SDL_Window * window)
   266 {
   267     NSUInteger style = 0;
   268 
   269     if (window->flags & SDL_WINDOW_BORDERLESS) {
   270         style = NSWindowStyleMaskBorderless;
   271     } else {
   272         style = (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable);
   273     }
   274     if (window->flags & SDL_WINDOW_RESIZABLE) {
   275         style |= NSWindowStyleMaskResizable;
   276     }
   277     return style;
   278 }
   279 
   280 static NSUInteger
   281 GetWindowStyle(SDL_Window * window)
   282 {
   283     NSUInteger style = 0;
   284 
   285     if (window->flags & SDL_WINDOW_FULLSCREEN) {
   286         style = NSWindowStyleMaskBorderless;
   287     } else {
   288         style = GetWindowWindowedStyle(window);
   289     }
   290     return style;
   291 }
   292 
   293 static SDL_bool
   294 SetWindowStyle(SDL_Window * window, NSUInteger style)
   295 {
   296     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   297     NSWindow *nswindow = data->nswindow;
   298 
   299     /* The view responder chain gets messed with during setStyleMask */
   300     if ([[nswindow contentView] nextResponder] == data->listener) {
   301         [[nswindow contentView] setNextResponder:nil];
   302     }
   303 
   304     [nswindow setStyleMask:style];
   305 
   306     /* The view responder chain gets messed with during setStyleMask */
   307     if ([[nswindow contentView] nextResponder] != data->listener) {
   308         [[nswindow contentView] setNextResponder:data->listener];
   309     }
   310 
   311     return SDL_TRUE;
   312 }
   313 
   314 
   315 @implementation Cocoa_WindowListener
   316 
   317 - (void)listen:(SDL_WindowData *)data
   318 {
   319     NSNotificationCenter *center;
   320     NSWindow *window = data->nswindow;
   321     NSView *view = [window contentView];
   322 
   323     _data = data;
   324     observingVisible = YES;
   325     wasCtrlLeft = NO;
   326     wasVisible = [window isVisible];
   327     isFullscreenSpace = NO;
   328     inFullscreenTransition = NO;
   329     pendingWindowOperation = PENDING_OPERATION_NONE;
   330     isMoving = NO;
   331     isDragAreaRunning = NO;
   332 
   333     center = [NSNotificationCenter defaultCenter];
   334 
   335     if ([window delegate] != nil) {
   336         [center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window];
   337         [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window];
   338         [center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window];
   339         [center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window];
   340         [center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window];
   341         [center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window];
   342         [center addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window];
   343         [center addObserver:self selector:@selector(windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window];
   344         [center addObserver:self selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window];
   345         [center addObserver:self selector:@selector(windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window];
   346         [center addObserver:self selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window];
   347         [center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window];
   348         [center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
   349         [center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
   350     } else {
   351         [window setDelegate:self];
   352     }
   353 
   354     /* Haven't found a delegate / notification that triggers when the window is
   355      * ordered out (is not visible any more). You can be ordered out without
   356      * minimizing, so DidMiniaturize doesn't work. (e.g. -[NSWindow orderOut:])
   357      */
   358     [window addObserver:self
   359              forKeyPath:@"visible"
   360                 options:NSKeyValueObservingOptionNew
   361                 context:NULL];
   362 
   363     [window setNextResponder:self];
   364     [window setAcceptsMouseMovedEvents:YES];
   365 
   366     [view setNextResponder:self];
   367 
   368     [view setAcceptsTouchEvents:YES];
   369 }
   370 
   371 - (void)observeValueForKeyPath:(NSString *)keyPath
   372                       ofObject:(id)object
   373                         change:(NSDictionary *)change
   374                        context:(void *)context
   375 {
   376     if (!observingVisible) {
   377         return;
   378     }
   379 
   380     if (object == _data->nswindow && [keyPath isEqualToString:@"visible"]) {
   381         int newVisibility = [[change objectForKey:@"new"] intValue];
   382         if (newVisibility) {
   383             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
   384         } else {
   385             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
   386         }
   387     }
   388 }
   389 
   390 -(void) pauseVisibleObservation
   391 {
   392     observingVisible = NO;
   393     wasVisible = [_data->nswindow isVisible];
   394 }
   395 
   396 -(void) resumeVisibleObservation
   397 {
   398     BOOL isVisible = [_data->nswindow isVisible];
   399     observingVisible = YES;
   400     if (wasVisible != isVisible) {
   401         if (isVisible) {
   402             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
   403         } else {
   404             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
   405         }
   406 
   407         wasVisible = isVisible;
   408     }
   409 }
   410 
   411 -(BOOL) setFullscreenSpace:(BOOL) state
   412 {
   413     SDL_Window *window = _data->window;
   414     NSWindow *nswindow = _data->nswindow;
   415     SDL_VideoData *videodata = ((SDL_WindowData *) window->driverdata)->videodata;
   416 
   417     if (!videodata->allow_spaces) {
   418         return NO;  /* Spaces are forcibly disabled. */
   419     } else if (state && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   420         return NO;  /* we only allow you to make a Space on FULLSCREEN_DESKTOP windows. */
   421     } else if (!state && ((window->last_fullscreen_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   422         return NO;  /* we only handle leaving the Space on windows that were previously FULLSCREEN_DESKTOP. */
   423     } else if (state == isFullscreenSpace) {
   424         return YES;  /* already there. */
   425     }
   426 
   427     if (inFullscreenTransition) {
   428         if (state) {
   429             [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
   430         } else {
   431             [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
   432         }
   433         return YES;
   434     }
   435     inFullscreenTransition = YES;
   436 
   437     /* you need to be FullScreenPrimary, or toggleFullScreen doesn't work. Unset it again in windowDidExitFullScreen. */
   438     [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
   439     [nswindow performSelectorOnMainThread: @selector(toggleFullScreen:) withObject:nswindow waitUntilDone:NO];
   440     return YES;
   441 }
   442 
   443 -(BOOL) isInFullscreenSpace
   444 {
   445     return isFullscreenSpace;
   446 }
   447 
   448 -(BOOL) isInFullscreenSpaceTransition
   449 {
   450     return inFullscreenTransition;
   451 }
   452 
   453 -(void) addPendingWindowOperation:(PendingWindowOperation) operation
   454 {
   455     pendingWindowOperation = operation;
   456 }
   457 
   458 - (void)close
   459 {
   460     NSNotificationCenter *center;
   461     NSWindow *window = _data->nswindow;
   462     NSView *view = [window contentView];
   463 
   464     center = [NSNotificationCenter defaultCenter];
   465 
   466     if ([window delegate] != self) {
   467         [center removeObserver:self name:NSWindowDidExposeNotification object:window];
   468         [center removeObserver:self name:NSWindowDidMoveNotification object:window];
   469         [center removeObserver:self name:NSWindowDidResizeNotification object:window];
   470         [center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window];
   471         [center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window];
   472         [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window];
   473         [center removeObserver:self name:NSWindowDidResignKeyNotification object:window];
   474         [center removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window];
   475         [center removeObserver:self name:NSWindowWillEnterFullScreenNotification object:window];
   476         [center removeObserver:self name:NSWindowDidEnterFullScreenNotification object:window];
   477         [center removeObserver:self name:NSWindowWillExitFullScreenNotification object:window];
   478         [center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window];
   479         [center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
   480         [center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
   481     } else {
   482         [window setDelegate:nil];
   483     }
   484 
   485     [window removeObserver:self forKeyPath:@"visible"];
   486 
   487     if ([window nextResponder] == self) {
   488         [window setNextResponder:nil];
   489     }
   490     if ([view nextResponder] == self) {
   491         [view setNextResponder:nil];
   492     }
   493 }
   494 
   495 - (BOOL)isMoving
   496 {
   497     return isMoving;
   498 }
   499 
   500 -(void) setPendingMoveX:(int)x Y:(int)y
   501 {
   502     pendingWindowWarpX = x;
   503     pendingWindowWarpY = y;
   504 }
   505 
   506 - (void)windowDidFinishMoving
   507 {
   508     if ([self isMoving]) {
   509         isMoving = NO;
   510 
   511         SDL_Mouse *mouse = SDL_GetMouse();
   512         if (pendingWindowWarpX != INT_MAX && pendingWindowWarpY != INT_MAX) {
   513             mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY);
   514             pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
   515         }
   516         if (mouse->relative_mode && !mouse->relative_mode_warp && mouse->focus == _data->window) {
   517             mouse->SetRelativeMouseMode(SDL_TRUE);
   518         }
   519     }
   520 }
   521 
   522 - (BOOL)windowShouldClose:(id)sender
   523 {
   524     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_CLOSE, 0, 0);
   525     return NO;
   526 }
   527 
   528 - (void)windowDidExpose:(NSNotification *)aNotification
   529 {
   530     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_EXPOSED, 0, 0);
   531 }
   532 
   533 - (void)windowWillMove:(NSNotification *)aNotification
   534 {
   535     if ([_data->nswindow isKindOfClass:[SDLWindow class]]) {
   536         pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
   537         isMoving = YES;
   538     }
   539 }
   540 
   541 - (void)windowDidMove:(NSNotification *)aNotification
   542 {
   543     int x, y;
   544     SDL_Window *window = _data->window;
   545     NSWindow *nswindow = _data->nswindow;
   546     BOOL fullscreen = window->flags & FULLSCREEN_MASK;
   547     NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
   548     ConvertNSRect([nswindow screen], fullscreen, &rect);
   549 
   550     if (inFullscreenTransition) {
   551         /* We'll take care of this at the end of the transition */
   552         return;
   553     }
   554 
   555     if (s_moveHack) {
   556         SDL_bool blockMove = ((SDL_GetTicks() - s_moveHack) < 500);
   557 
   558         s_moveHack = 0;
   559 
   560         if (blockMove) {
   561             /* Cocoa is adjusting the window in response to a mode change */
   562             rect.origin.x = window->x;
   563             rect.origin.y = window->y;
   564             ConvertNSRect([nswindow screen], fullscreen, &rect);
   565             [nswindow setFrameOrigin:rect.origin];
   566             return;
   567         }
   568     }
   569 
   570     x = (int)rect.origin.x;
   571     y = (int)rect.origin.y;
   572 
   573     ScheduleContextUpdates(_data);
   574 
   575     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
   576 }
   577 
   578 - (void)windowDidResize:(NSNotification *)aNotification
   579 {
   580     if (inFullscreenTransition) {
   581         /* We'll take care of this at the end of the transition */
   582         return;
   583     }
   584 
   585     SDL_Window *window = _data->window;
   586     NSWindow *nswindow = _data->nswindow;
   587     int x, y, w, h;
   588     NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
   589     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
   590     x = (int)rect.origin.x;
   591     y = (int)rect.origin.y;
   592     w = (int)rect.size.width;
   593     h = (int)rect.size.height;
   594 
   595     if (SDL_IsShapedWindow(window)) {
   596         Cocoa_ResizeWindowShape(window);
   597     }
   598 
   599     ScheduleContextUpdates(_data);
   600 
   601     /* The window can move during a resize event, such as when maximizing
   602        or resizing from a corner */
   603     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
   604     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
   605 
   606     const BOOL zoomed = [nswindow isZoomed];
   607     if (!zoomed) {
   608         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   609     } else if (zoomed) {
   610         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
   611     }
   612 }
   613 
   614 - (void)windowDidMiniaturize:(NSNotification *)aNotification
   615 {
   616     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   617 }
   618 
   619 - (void)windowDidDeminiaturize:(NSNotification *)aNotification
   620 {
   621     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   622 }
   623 
   624 - (void)windowDidBecomeKey:(NSNotification *)aNotification
   625 {
   626     SDL_Window *window = _data->window;
   627     SDL_Mouse *mouse = SDL_GetMouse();
   628 
   629     /* We're going to get keyboard events, since we're key. */
   630     /* This needs to be done before restoring the relative mouse mode. */
   631     SDL_SetKeyboardFocus(window);
   632 
   633     if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMoving]) {
   634         mouse->SetRelativeMouseMode(SDL_TRUE);
   635     }
   636 
   637     /* If we just gained focus we need the updated mouse position */
   638     if (!mouse->relative_mode) {
   639         NSPoint point;
   640         int x, y;
   641 
   642         point = [_data->nswindow mouseLocationOutsideOfEventStream];
   643         x = (int)point.x;
   644         y = (int)(window->h - point.y);
   645 
   646         if (x >= 0 && x < window->w && y >= 0 && y < window->h) {
   647             SDL_SendMouseMotion(window, mouse->mouseID, 0, x, y);
   648         }
   649     }
   650 
   651     /* Check to see if someone updated the clipboard */
   652     Cocoa_CheckClipboardUpdate(_data->videodata);
   653 
   654     if ((isFullscreenSpace) && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   655         [NSMenu setMenuBarVisible:NO];
   656     }
   657 
   658     const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock;
   659     _data->videodata->modifierFlags = (_data->videodata->modifierFlags & ~NSEventModifierFlagCapsLock) | newflags;
   660     SDL_ToggleModState(KMOD_CAPS, newflags != 0);
   661 }
   662 
   663 - (void)windowDidResignKey:(NSNotification *)aNotification
   664 {
   665     SDL_Mouse *mouse = SDL_GetMouse();
   666     if (mouse->relative_mode && !mouse->relative_mode_warp) {
   667         mouse->SetRelativeMouseMode(SDL_FALSE);
   668     }
   669 
   670     /* Some other window will get mouse events, since we're not key. */
   671     if (SDL_GetMouseFocus() == _data->window) {
   672         SDL_SetMouseFocus(NULL);
   673     }
   674 
   675     /* Some other window will get keyboard events, since we're not key. */
   676     if (SDL_GetKeyboardFocus() == _data->window) {
   677         SDL_SetKeyboardFocus(NULL);
   678     }
   679 
   680     if (isFullscreenSpace) {
   681         [NSMenu setMenuBarVisible:YES];
   682     }
   683 }
   684 
   685 - (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
   686 {
   687     NSNumber *oldscale = [[aNotification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey];
   688 
   689     if (inFullscreenTransition) {
   690         return;
   691     }
   692 
   693     if ([oldscale doubleValue] != [_data->nswindow backingScaleFactor]) {
   694         /* Force a resize event when the backing scale factor changes. */
   695         _data->window->w = 0;
   696         _data->window->h = 0;
   697         [self windowDidResize:aNotification];
   698     }
   699 }
   700 
   701 - (void)windowWillEnterFullScreen:(NSNotification *)aNotification
   702 {
   703     SDL_Window *window = _data->window;
   704 
   705     SetWindowStyle(window, (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable));
   706 
   707     isFullscreenSpace = YES;
   708     inFullscreenTransition = YES;
   709 }
   710 
   711 - (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
   712 {
   713     SDL_Window *window = _data->window;
   714 
   715     if (window->is_destroying) {
   716         return;
   717     }
   718 
   719     SetWindowStyle(window, GetWindowStyle(window));
   720 
   721     isFullscreenSpace = NO;
   722     inFullscreenTransition = NO;
   723     
   724     [self windowDidExitFullScreen:nil];
   725 }
   726 
   727 - (void)windowDidEnterFullScreen:(NSNotification *)aNotification
   728 {
   729     SDL_Window *window = _data->window;
   730     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   731     NSWindow *nswindow = data->nswindow;
   732 
   733     inFullscreenTransition = NO;
   734 
   735     if (pendingWindowOperation == PENDING_OPERATION_LEAVE_FULLSCREEN) {
   736         pendingWindowOperation = PENDING_OPERATION_NONE;
   737         [self setFullscreenSpace:NO];
   738     } else {
   739         /* Unset the resizable flag. 
   740            This is a workaround for https://bugzilla.libsdl.org/show_bug.cgi?id=3697
   741          */
   742         SetWindowStyle(window, [nswindow styleMask] & (~NSWindowStyleMaskResizable));
   743 
   744         if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   745             [NSMenu setMenuBarVisible:NO];
   746         }
   747 
   748         pendingWindowOperation = PENDING_OPERATION_NONE;
   749         /* Force the size change event in case it was delivered earlier
   750            while the window was still animating into place.
   751          */
   752         window->w = 0;
   753         window->h = 0;
   754         [self windowDidMove:aNotification];
   755         [self windowDidResize:aNotification];
   756     }
   757 }
   758 
   759 - (void)windowWillExitFullScreen:(NSNotification *)aNotification
   760 {
   761     SDL_Window *window = _data->window;
   762 
   763     isFullscreenSpace = NO;
   764     inFullscreenTransition = YES;
   765 
   766     /* As of macOS 10.11, the window seems to need to be resizable when exiting
   767        a Space, in order for it to resize back to its windowed-mode size.
   768        As of macOS 10.15, the window decorations can go missing sometimes after
   769        certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows
   770        sometimes. Making sure the style mask always uses the windowed mode style
   771        when returning to windowed mode from a space (instead of using a pending
   772        fullscreen mode style mask) seems to work around that issue.
   773      */
   774     SetWindowStyle(window, GetWindowWindowedStyle(window) | NSWindowStyleMaskResizable);
   775 }
   776 
   777 - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
   778 {
   779     SDL_Window *window = _data->window;
   780     
   781     if (window->is_destroying) {
   782         return;
   783     }
   784 
   785     SetWindowStyle(window, (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable));
   786     
   787     isFullscreenSpace = YES;
   788     inFullscreenTransition = NO;
   789     
   790     [self windowDidEnterFullScreen:nil];
   791 }
   792 
   793 - (void)windowDidExitFullScreen:(NSNotification *)aNotification
   794 {
   795     SDL_Window *window = _data->window;
   796     NSWindow *nswindow = _data->nswindow;
   797 
   798     inFullscreenTransition = NO;
   799 
   800     /* As of macOS 10.15, the window decorations can go missing sometimes after
   801        certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows
   802        sometimes. Making sure the style mask always uses the windowed mode style
   803        when returning to windowed mode from a space (instead of using a pending
   804        fullscreen mode style mask) seems to work around that issue.
   805      */
   806     SetWindowStyle(window, GetWindowWindowedStyle(window));
   807 
   808     if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
   809         [nswindow setLevel:NSFloatingWindowLevel];
   810     } else {
   811         [nswindow setLevel:kCGNormalWindowLevel];
   812     }
   813 
   814     if (pendingWindowOperation == PENDING_OPERATION_ENTER_FULLSCREEN) {
   815         pendingWindowOperation = PENDING_OPERATION_NONE;
   816         [self setFullscreenSpace:YES];
   817     } else if (pendingWindowOperation == PENDING_OPERATION_MINIMIZE) {
   818         pendingWindowOperation = PENDING_OPERATION_NONE;
   819         [nswindow miniaturize:nil];
   820     } else {
   821         /* Adjust the fullscreen toggle button and readd menu now that we're here. */
   822         if (window->flags & SDL_WINDOW_RESIZABLE) {
   823             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
   824             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
   825         } else {
   826             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged];
   827         }
   828         [NSMenu setMenuBarVisible:YES];
   829 
   830         pendingWindowOperation = PENDING_OPERATION_NONE;
   831 
   832 #if 0
   833 /* This fixed bug 3719, which is that changing window size while fullscreen
   834    doesn't take effect when leaving fullscreen, but introduces bug 3809,
   835    which is that a maximized window doesn't go back to normal size when
   836    restored, so this code is disabled until we can properly handle the
   837    beginning and end of maximize and restore.
   838  */
   839         /* Restore windowed size and position in case it changed while fullscreen */
   840         {
   841             NSRect rect;
   842             rect.origin.x = window->windowed.x;
   843             rect.origin.y = window->windowed.y;
   844             rect.size.width = window->windowed.w;
   845             rect.size.height = window->windowed.h;
   846             ConvertNSRect([nswindow screen], NO, &rect);
   847 
   848             s_moveHack = 0;
   849             [nswindow setContentSize:rect.size];
   850             [nswindow setFrameOrigin:rect.origin];
   851             s_moveHack = SDL_GetTicks();
   852         }
   853 #endif /* 0 */
   854 
   855         /* Force the size change event in case it was delivered earlier
   856            while the window was still animating into place.
   857          */
   858         window->w = 0;
   859         window->h = 0;
   860         [self windowDidMove:aNotification];
   861         [self windowDidResize:aNotification];
   862 
   863         /* FIXME: Why does the window get hidden? */
   864         if (window->flags & SDL_WINDOW_SHOWN) {
   865             Cocoa_ShowWindow(SDL_GetVideoDevice(), window);
   866         }
   867     }
   868 }
   869 
   870 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
   871 {
   872     if ((_data->window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   873         return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
   874     } else {
   875         return proposedOptions;
   876     }
   877 }
   878 
   879 /* We'll respond to key events by mostly doing nothing so we don't beep.
   880  * We could handle key messages here, but we lose some in the NSApp dispatch,
   881  * where they get converted to action messages, etc.
   882  */
   883 - (void)flagsChanged:(NSEvent *)theEvent
   884 {
   885     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   886 
   887     /* Catch capslock in here as a special case:
   888        https://developer.apple.com/library/archive/qa/qa1519/_index.html
   889        Note that technote's check of keyCode doesn't work. At least on the
   890        10.15 beta, capslock comes through here as keycode 255, but it's safe
   891        to send duplicate key events; SDL filters them out quickly in
   892        SDL_SendKeyboardKey(). */
   893 
   894     /* Also note that SDL_SendKeyboardKey expects all capslock events to be
   895        keypresses; it won't toggle the mod state if you send a keyrelease.  */
   896     const SDL_bool osenabled = ([theEvent modifierFlags] & NSEventModifierFlagCapsLock) ? SDL_TRUE : SDL_FALSE;
   897     const SDL_bool sdlenabled = (SDL_GetModState() & KMOD_CAPS) ? SDL_TRUE : SDL_FALSE;
   898     if (!osenabled && sdlenabled) {
   899         SDL_ToggleModState(KMOD_CAPS, SDL_FALSE);
   900         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
   901     } else if (osenabled && !sdlenabled) {
   902         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
   903     }
   904 }
   905 - (void)keyDown:(NSEvent *)theEvent
   906 {
   907     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   908 }
   909 - (void)keyUp:(NSEvent *)theEvent
   910 {
   911     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   912 }
   913 
   914 /* We'll respond to selectors by doing nothing so we don't beep.
   915  * The escape key gets converted to a "cancel" selector, etc.
   916  */
   917 - (void)doCommandBySelector:(SEL)aSelector
   918 {
   919     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
   920 }
   921 
   922 - (BOOL)processHitTest:(NSEvent *)theEvent
   923 {
   924     SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
   925 
   926     if (_data->window->hit_test) {  /* if no hit-test, skip this. */
   927         const NSPoint location = [theEvent locationInWindow];
   928         const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
   929         const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
   930         if (rc == SDL_HITTEST_DRAGGABLE) {
   931             if (!isDragAreaRunning) {
   932                 isDragAreaRunning = YES;
   933                 [_data->nswindow setMovableByWindowBackground:YES];
   934             }
   935             return YES;  /* dragging! */
   936         }
   937     }
   938 
   939     if (isDragAreaRunning) {
   940         isDragAreaRunning = NO;
   941         [_data->nswindow setMovableByWindowBackground:NO];
   942         return YES;  /* was dragging, drop event. */
   943     }
   944 
   945     return NO;  /* not a special area, carry on. */
   946 }
   947 
   948 - (void)mouseDown:(NSEvent *)theEvent
   949 {
   950     const SDL_Mouse *mouse = SDL_GetMouse();
   951     if (!mouse) {
   952         return;
   953     }
   954 
   955     const SDL_MouseID mouseID = mouse->mouseID;
   956     int button;
   957     int clicks;
   958 
   959     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   960     if ([theEvent window]) {
   961         NSRect windowRect = [[[theEvent window] contentView] frame];
   962         if (!NSMouseInRect([theEvent locationInWindow], windowRect, NO)) {
   963             return;
   964         }
   965     }
   966 
   967     if ([self processHitTest:theEvent]) {
   968         SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
   969         return;  /* dragging, drop event. */
   970     }
   971 
   972     switch ([theEvent buttonNumber]) {
   973     case 0:
   974         if (([theEvent modifierFlags] & NSEventModifierFlagControl) &&
   975             GetHintCtrlClickEmulateRightClick()) {
   976             wasCtrlLeft = YES;
   977             button = SDL_BUTTON_RIGHT;
   978         } else {
   979             wasCtrlLeft = NO;
   980             button = SDL_BUTTON_LEFT;
   981         }
   982         break;
   983     case 1:
   984         button = SDL_BUTTON_RIGHT;
   985         break;
   986     case 2:
   987         button = SDL_BUTTON_MIDDLE;
   988         break;
   989     default:
   990         button = (int) [theEvent buttonNumber] + 1;
   991         break;
   992     }
   993 
   994     clicks = (int) [theEvent clickCount];
   995 
   996     SDL_SendMouseButtonClicks(_data->window, mouseID, SDL_PRESSED, button, clicks);
   997 }
   998 
   999 - (void)rightMouseDown:(NSEvent *)theEvent
  1000 {
  1001     [self mouseDown:theEvent];
  1002 }
  1003 
  1004 - (void)otherMouseDown:(NSEvent *)theEvent
  1005 {
  1006     [self mouseDown:theEvent];
  1007 }
  1008 
  1009 - (void)mouseUp:(NSEvent *)theEvent
  1010 {
  1011     const SDL_Mouse *mouse = SDL_GetMouse();
  1012     if (!mouse) {
  1013         return;
  1014     }
  1015 
  1016     const SDL_MouseID mouseID = mouse->mouseID;
  1017     int button;
  1018     int clicks;
  1019 
  1020     if ([self processHitTest:theEvent]) {
  1021         SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
  1022         return;  /* stopped dragging, drop event. */
  1023     }
  1024 
  1025     switch ([theEvent buttonNumber]) {
  1026     case 0:
  1027         if (wasCtrlLeft) {
  1028             button = SDL_BUTTON_RIGHT;
  1029             wasCtrlLeft = NO;
  1030         } else {
  1031             button = SDL_BUTTON_LEFT;
  1032         }
  1033         break;
  1034     case 1:
  1035         button = SDL_BUTTON_RIGHT;
  1036         break;
  1037     case 2:
  1038         button = SDL_BUTTON_MIDDLE;
  1039         break;
  1040     default:
  1041         button = (int) [theEvent buttonNumber] + 1;
  1042         break;
  1043     }
  1044 
  1045     clicks = (int) [theEvent clickCount];
  1046 
  1047     SDL_SendMouseButtonClicks(_data->window, mouseID, SDL_RELEASED, button, clicks);
  1048 }
  1049 
  1050 - (void)rightMouseUp:(NSEvent *)theEvent
  1051 {
  1052     [self mouseUp:theEvent];
  1053 }
  1054 
  1055 - (void)otherMouseUp:(NSEvent *)theEvent
  1056 {
  1057     [self mouseUp:theEvent];
  1058 }
  1059 
  1060 - (void)mouseMoved:(NSEvent *)theEvent
  1061 {
  1062     SDL_Mouse *mouse = SDL_GetMouse();
  1063     if (!mouse) {
  1064         return;
  1065     }
  1066 
  1067     const SDL_MouseID mouseID = mouse->mouseID;
  1068     SDL_Window *window = _data->window;
  1069     NSPoint point;
  1070     int x, y;
  1071 
  1072     if ([self processHitTest:theEvent]) {
  1073         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
  1074         return;  /* dragging, drop event. */
  1075     }
  1076 
  1077     if (mouse->relative_mode) {
  1078         return;
  1079     }
  1080 
  1081     point = [theEvent locationInWindow];
  1082     x = (int)point.x;
  1083     y = (int)(window->h - point.y);
  1084 
  1085     if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
  1086         if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
  1087             if (x < 0) {
  1088                 x = 0;
  1089             } else if (x >= window->w) {
  1090                 x = window->w - 1;
  1091             }
  1092             if (y < 0) {
  1093                 y = 0;
  1094             } else if (y >= window->h) {
  1095                 y = window->h - 1;
  1096             }
  1097 
  1098 #if !SDL_MAC_NO_SANDBOX
  1099             CGPoint cgpoint;
  1100 
  1101             /* When SDL_MAC_NO_SANDBOX is set, this is handled by
  1102              * SDL_cocoamousetap.m.
  1103              */
  1104 
  1105             cgpoint.x = window->x + x;
  1106             cgpoint.y = window->y + y;
  1107 
  1108             CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
  1109             CGAssociateMouseAndMouseCursorPosition(YES);
  1110 
  1111             Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
  1112 #endif
  1113         }
  1114     }
  1115 
  1116     SDL_SendMouseMotion(window, mouseID, 0, x, y);
  1117 }
  1118 
  1119 - (void)mouseDragged:(NSEvent *)theEvent
  1120 {
  1121     [self mouseMoved:theEvent];
  1122 }
  1123 
  1124 - (void)rightMouseDragged:(NSEvent *)theEvent
  1125 {
  1126     [self mouseMoved:theEvent];
  1127 }
  1128 
  1129 - (void)otherMouseDragged:(NSEvent *)theEvent
  1130 {
  1131     [self mouseMoved:theEvent];
  1132 }
  1133 
  1134 - (void)scrollWheel:(NSEvent *)theEvent
  1135 {
  1136     Cocoa_HandleMouseWheel(_data->window, theEvent);
  1137 }
  1138 
  1139 - (void)touchesBeganWithEvent:(NSEvent *) theEvent
  1140 {
  1141     /* probably a MacBook trackpad; make this look like a synthesized event.
  1142        This is backwards from reality, but better matches user expectations. */
  1143     const BOOL istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent);
  1144 
  1145     NSSet *touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil];
  1146     const SDL_TouchID touchID = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[[touches anyObject] device];
  1147     int existingTouchCount = 0;
  1148 
  1149     for (NSTouch* touch in touches) {
  1150         if ([touch phase] != NSTouchPhaseBegan) {
  1151             existingTouchCount++;
  1152         }
  1153     }
  1154     if (existingTouchCount == 0) {
  1155         int numFingers = SDL_GetNumTouchFingers(touchID);
  1156         DLog("Reset Lost Fingers: %d", numFingers);
  1157         for (--numFingers; numFingers >= 0; --numFingers) {
  1158             SDL_Finger* finger = SDL_GetTouchFinger(touchID, numFingers);
  1159             /* trackpad touches have no window. If we really wanted one we could
  1160              * use the window that has mouse or keyboard focus.
  1161              * Sending a null window currently also prevents synthetic mouse
  1162              * events from being generated from touch events.
  1163              */
  1164             SDL_Window *window = NULL;
  1165             SDL_SendTouch(touchID, finger->id, window, SDL_FALSE, 0, 0, 0);
  1166         }
  1167     }
  1168 
  1169     DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount);
  1170     [self handleTouches:NSTouchPhaseBegan withEvent:theEvent];
  1171 }
  1172 
  1173 - (void)touchesMovedWithEvent:(NSEvent *) theEvent
  1174 {
  1175     [self handleTouches:NSTouchPhaseMoved withEvent:theEvent];
  1176 }
  1177 
  1178 - (void)touchesEndedWithEvent:(NSEvent *) theEvent
  1179 {
  1180     [self handleTouches:NSTouchPhaseEnded withEvent:theEvent];
  1181 }
  1182 
  1183 - (void)touchesCancelledWithEvent:(NSEvent *) theEvent
  1184 {
  1185     [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
  1186 }
  1187 
  1188 - (void)handleTouches:(NSTouchPhase) phase withEvent:(NSEvent *) theEvent
  1189 {
  1190     NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];
  1191 
  1192     /* probably a MacBook trackpad; make this look like a synthesized event.
  1193        This is backwards from reality, but better matches user expectations. */
  1194     const BOOL istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent);
  1195 
  1196     for (NSTouch *touch in touches) {
  1197         const SDL_TouchID touchId = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[touch device];
  1198         SDL_TouchDeviceType devtype = SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE;
  1199 
  1200         /* trackpad touches have no window. If we really wanted one we could
  1201          * use the window that has mouse or keyboard focus.
  1202          * Sending a null window currently also prevents synthetic mouse events
  1203          * from being generated from touch events.
  1204          */
  1205         SDL_Window *window = NULL;
  1206 
  1207 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202 /* Added in the 10.12.2 SDK. */
  1208         if ([touch respondsToSelector:@selector(type)]) {
  1209             /* TODO: Before implementing direct touch support here, we need to
  1210              * figure out whether the OS generates mouse events from them on its
  1211              * own. If it does, we should prevent SendTouch from generating
  1212              * synthetic mouse events for these touches itself (while also
  1213              * sending a window.) It will also need to use normalized window-
  1214              * relative coordinates via [touch locationInView:].
  1215              */
  1216             if ([touch type] == NSTouchTypeDirect) {
  1217                 continue;
  1218             }
  1219         }
  1220 #endif
  1221 
  1222         if (SDL_AddTouch(touchId, devtype, "") < 0) {
  1223             return;
  1224         }
  1225 
  1226         const SDL_FingerID fingerId = (SDL_FingerID)(intptr_t)[touch identity];
  1227         float x = [touch normalizedPosition].x;
  1228         float y = [touch normalizedPosition].y;
  1229         /* Make the origin the upper left instead of the lower left */
  1230         y = 1.0f - y;
  1231 
  1232         switch (phase) {
  1233         case NSTouchPhaseBegan:
  1234             SDL_SendTouch(touchId, fingerId, window, SDL_TRUE, x, y, 1.0f);
  1235             break;
  1236         case NSTouchPhaseEnded:
  1237         case NSTouchPhaseCancelled:
  1238             SDL_SendTouch(touchId, fingerId, window, SDL_FALSE, x, y, 1.0f);
  1239             break;
  1240         case NSTouchPhaseMoved:
  1241             SDL_SendTouchMotion(touchId, fingerId, window, x, y, 1.0f);
  1242             break;
  1243         default:
  1244             break;
  1245         }
  1246     }
  1247 }
  1248 
  1249 @end
  1250 
  1251 @interface SDLView : NSView {
  1252     SDL_Window *_sdlWindow;
  1253 }
  1254 
  1255 - (void)setSDLWindow:(SDL_Window*)window;
  1256 
  1257 /* The default implementation doesn't pass rightMouseDown to responder chain */
  1258 - (void)rightMouseDown:(NSEvent *)theEvent;
  1259 - (BOOL)mouseDownCanMoveWindow;
  1260 - (void)drawRect:(NSRect)dirtyRect;
  1261 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent;
  1262 - (BOOL)wantsUpdateLayer;
  1263 - (void)updateLayer;
  1264 @end
  1265 
  1266 @implementation SDLView
  1267 
  1268 - (void)setSDLWindow:(SDL_Window*)window
  1269 {
  1270     _sdlWindow = window;
  1271 }
  1272 
  1273 /* this is used on older macOS revisions, and newer ones which emulate old
  1274    NSOpenGLContext behaviour while still using a layer under the hood. 10.8 and
  1275    later use updateLayer, up until 10.14.2 or so, which uses drawRect without
  1276    a GraphicsContext and with a layer active instead (for OpenGL contexts). */
  1277 - (void)drawRect:(NSRect)dirtyRect
  1278 {
  1279     /* Force the graphics context to clear to black so we don't get a flash of
  1280        white until the app is ready to draw. In practice on modern macOS, this
  1281        only gets called for window creation and other extraordinary events. */
  1282     if ([NSGraphicsContext currentContext]) {
  1283         [[NSColor blackColor] setFill];
  1284         NSRectFill(dirtyRect);
  1285     } else if (self.layer) {
  1286         self.layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
  1287     }
  1288 
  1289     SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
  1290 }
  1291 
  1292 - (BOOL)wantsUpdateLayer
  1293 {
  1294     return YES;
  1295 }
  1296 
  1297 /* This is also called when a Metal layer is active. */
  1298 - (void)updateLayer
  1299 {
  1300     /* Force the graphics context to clear to black so we don't get a flash of
  1301        white until the app is ready to draw. In practice on modern macOS, this
  1302        only gets called for window creation and other extraordinary events. */
  1303     self.layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
  1304     ScheduleContextUpdates((SDL_WindowData *) _sdlWindow->driverdata);
  1305     SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
  1306 }
  1307 
  1308 - (void)rightMouseDown:(NSEvent *)theEvent
  1309 {
  1310     [[self nextResponder] rightMouseDown:theEvent];
  1311 }
  1312 
  1313 - (BOOL)mouseDownCanMoveWindow
  1314 {
  1315     /* Always say YES, but this doesn't do anything until we call
  1316        -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
  1317        during mouse events when we're using a drag area. */
  1318     return YES;
  1319 }
  1320 
  1321 - (void)resetCursorRects
  1322 {
  1323     [super resetCursorRects];
  1324     SDL_Mouse *mouse = SDL_GetMouse();
  1325 
  1326     if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) {
  1327         [self addCursorRect:[self bounds]
  1328                      cursor:mouse->cur_cursor->driverdata];
  1329     } else {
  1330         [self addCursorRect:[self bounds]
  1331                      cursor:[NSCursor invisibleCursor]];
  1332     }
  1333 }
  1334 
  1335 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
  1336 {
  1337     if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) {
  1338         return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE);
  1339     } else {
  1340         return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", SDL_FALSE);
  1341     }
  1342 }
  1343 @end
  1344 
  1345 static int
  1346 SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, SDL_bool created)
  1347 { @autoreleasepool
  1348 {
  1349     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1350     SDL_WindowData *data;
  1351 
  1352     /* Allocate the window data */
  1353     window->driverdata = data = (SDL_WindowData *) SDL_calloc(1, sizeof(*data));
  1354     if (!data) {
  1355         return SDL_OutOfMemory();
  1356     }
  1357     data->window = window;
  1358     data->nswindow = nswindow;
  1359     data->created = created;
  1360     data->videodata = videodata;
  1361     data->nscontexts = [[NSMutableArray alloc] init];
  1362 
  1363     /* Only store this for windows created by us since the content view might
  1364      * get replaced from under us otherwise, and we only need it when the
  1365      * window is guaranteed to be created by us (OpenGL contexts). */
  1366     data->sdlContentView = created ? [nswindow contentView] : nil;
  1367 
  1368     /* Create an event listener for the window */
  1369     data->listener = [[Cocoa_WindowListener alloc] init];
  1370 
  1371     /* Fill in the SDL window with the window data */
  1372     {
  1373         NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1374         ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1375         window->x = (int)rect.origin.x;
  1376         window->y = (int)rect.origin.y;
  1377         window->w = (int)rect.size.width;
  1378         window->h = (int)rect.size.height;
  1379     }
  1380 
  1381     /* Set up the listener after we create the view */
  1382     [data->listener listen:data];
  1383 
  1384     if ([nswindow isVisible]) {
  1385         window->flags |= SDL_WINDOW_SHOWN;
  1386     } else {
  1387         window->flags &= ~SDL_WINDOW_SHOWN;
  1388     }
  1389 
  1390     {
  1391         unsigned long style = [nswindow styleMask];
  1392 
  1393         if (style == NSWindowStyleMaskBorderless) {
  1394             window->flags |= SDL_WINDOW_BORDERLESS;
  1395         } else {
  1396             window->flags &= ~SDL_WINDOW_BORDERLESS;
  1397         }
  1398         if (style & NSWindowStyleMaskResizable) {
  1399             window->flags |= SDL_WINDOW_RESIZABLE;
  1400         } else {
  1401             window->flags &= ~SDL_WINDOW_RESIZABLE;
  1402         }
  1403     }
  1404 
  1405     /* isZoomed always returns true if the window is not resizable */
  1406     if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1407         window->flags |= SDL_WINDOW_MAXIMIZED;
  1408     } else {
  1409         window->flags &= ~SDL_WINDOW_MAXIMIZED;
  1410     }
  1411 
  1412     if ([nswindow isMiniaturized]) {
  1413         window->flags |= SDL_WINDOW_MINIMIZED;
  1414     } else {
  1415         window->flags &= ~SDL_WINDOW_MINIMIZED;
  1416     }
  1417 
  1418     if ([nswindow isKeyWindow]) {
  1419         window->flags |= SDL_WINDOW_INPUT_FOCUS;
  1420         SDL_SetKeyboardFocus(data->window);
  1421     }
  1422 
  1423     /* Prevents the window's "window device" from being destroyed when it is
  1424      * hidden. See http://www.mikeash.com/pyblog/nsopenglcontext-and-one-shot.html
  1425      */
  1426     [nswindow setOneShot:NO];
  1427 
  1428     /* All done! */
  1429     window->driverdata = data;
  1430     return 0;
  1431 }}
  1432 
  1433 int
  1434 Cocoa_CreateWindow(_THIS, SDL_Window * window)
  1435 { @autoreleasepool
  1436 {
  1437     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1438     NSWindow *nswindow;
  1439     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1440     NSRect rect;
  1441     SDL_Rect bounds;
  1442     NSUInteger style;
  1443     NSArray *screens = [NSScreen screens];
  1444 
  1445     Cocoa_GetDisplayBounds(_this, display, &bounds);
  1446     rect.origin.x = window->x;
  1447     rect.origin.y = window->y;
  1448     rect.size.width = window->w;
  1449     rect.size.height = window->h;
  1450     ConvertNSRect([screens objectAtIndex:0], (window->flags & FULLSCREEN_MASK), &rect);
  1451 
  1452     style = GetWindowStyle(window);
  1453 
  1454     /* Figure out which screen to place this window */
  1455     NSScreen *screen = nil;
  1456     for (NSScreen *candidate in screens) {
  1457         NSRect screenRect = [candidate frame];
  1458         if (rect.origin.x >= screenRect.origin.x &&
  1459             rect.origin.x < screenRect.origin.x + screenRect.size.width &&
  1460             rect.origin.y >= screenRect.origin.y &&
  1461             rect.origin.y < screenRect.origin.y + screenRect.size.height) {
  1462             screen = candidate;
  1463             rect.origin.x -= screenRect.origin.x;
  1464             rect.origin.y -= screenRect.origin.y;
  1465         }
  1466     }
  1467 
  1468     @try {
  1469         nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
  1470     }
  1471     @catch (NSException *e) {
  1472         return SDL_SetError("%s", [[e reason] UTF8String]);
  1473     }
  1474 
  1475 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 /* Added in the 10.12.0 SDK. */
  1476     /* By default, don't allow users to make our window tabbed in 10.12 or later */
  1477     if ([nswindow respondsToSelector:@selector(setTabbingMode:)]) {
  1478         [nswindow setTabbingMode:NSWindowTabbingModeDisallowed];
  1479     }
  1480 #endif
  1481 
  1482     if (videodata->allow_spaces) {
  1483         SDL_assert(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6);
  1484         SDL_assert([nswindow respondsToSelector:@selector(toggleFullScreen:)]);
  1485         /* we put FULLSCREEN_DESKTOP windows in their own Space, without a toggle button or menubar, later */
  1486         if (window->flags & SDL_WINDOW_RESIZABLE) {
  1487             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
  1488             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
  1489         }
  1490     }
  1491 
  1492     if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
  1493         [nswindow setLevel:NSFloatingWindowLevel];
  1494     }
  1495 
  1496     /* Create a default view for this window */
  1497     rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1498     SDLView *contentView = [[SDLView alloc] initWithFrame:rect];
  1499     [contentView setSDLWindow:window];
  1500 
  1501     /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
  1502     #ifdef __clang__
  1503     #pragma clang diagnostic push
  1504     #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1505     #endif
  1506     /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when
  1507      * the NSHighResolutionCapable boolean is set in Info.plist. */
  1508     if ([contentView respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
  1509         BOOL highdpi = (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
  1510         [contentView setWantsBestResolutionOpenGLSurface:highdpi];
  1511     }
  1512     #ifdef __clang__
  1513     #pragma clang diagnostic pop
  1514     #endif
  1515 
  1516 #if SDL_VIDEO_OPENGL_ES2
  1517 #if SDL_VIDEO_OPENGL_EGL
  1518     if ((window->flags & SDL_WINDOW_OPENGL) &&
  1519         _this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
  1520         [contentView setWantsLayer:TRUE];
  1521     }
  1522 #endif /* SDL_VIDEO_OPENGL_EGL */
  1523 #endif /* SDL_VIDEO_OPENGL_ES2 */
  1524     [nswindow setContentView:contentView];
  1525     [contentView release];
  1526 
  1527     if (SetupWindowData(_this, window, nswindow, SDL_TRUE) < 0) {
  1528         [nswindow release];
  1529         return -1;
  1530     }
  1531 
  1532     if (!(window->flags & SDL_WINDOW_OPENGL)) {
  1533         return 0;
  1534     }
  1535     
  1536     /* The rest of this macro mess is for OpenGL or OpenGL ES windows */
  1537 #if SDL_VIDEO_OPENGL_ES2
  1538     if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
  1539 #if SDL_VIDEO_OPENGL_EGL
  1540         if (Cocoa_GLES_SetupWindow(_this, window) < 0) {
  1541             Cocoa_DestroyWindow(_this, window);
  1542             return -1;
  1543         }
  1544         return 0;
  1545 #else
  1546         return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
  1547 #endif /* SDL_VIDEO_OPENGL_EGL */
  1548     }
  1549 #endif /* SDL_VIDEO_OPENGL_ES2 */
  1550     return 0;
  1551 }}
  1552 
  1553 int
  1554 Cocoa_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
  1555 { @autoreleasepool
  1556 {
  1557     NSWindow *nswindow = (NSWindow *) data;
  1558     NSString *title;
  1559 
  1560     /* Query the title from the existing window */
  1561     title = [nswindow title];
  1562     if (title) {
  1563         window->title = SDL_strdup([title UTF8String]);
  1564     }
  1565 
  1566     return SetupWindowData(_this, window, nswindow, SDL_FALSE);
  1567 }}
  1568 
  1569 void
  1570 Cocoa_SetWindowTitle(_THIS, SDL_Window * window)
  1571 { @autoreleasepool
  1572 {
  1573     const char *title = window->title ? window->title : "";
  1574     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1575     NSString *string = [[NSString alloc] initWithUTF8String:title];
  1576     [nswindow setTitle:string];
  1577     [string release];
  1578 }}
  1579 
  1580 void
  1581 Cocoa_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
  1582 { @autoreleasepool
  1583 {
  1584     NSImage *nsimage = Cocoa_CreateImage(icon);
  1585 
  1586     if (nsimage) {
  1587         [NSApp setApplicationIconImage:nsimage];
  1588     }
  1589 }}
  1590 
  1591 void
  1592 Cocoa_SetWindowPosition(_THIS, SDL_Window * window)
  1593 { @autoreleasepool
  1594 {
  1595     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1596     NSWindow *nswindow = windata->nswindow;
  1597     NSRect rect;
  1598     Uint32 moveHack;
  1599 
  1600     rect.origin.x = window->x;
  1601     rect.origin.y = window->y;
  1602     rect.size.width = window->w;
  1603     rect.size.height = window->h;
  1604     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1605 
  1606     moveHack = s_moveHack;
  1607     s_moveHack = 0;
  1608     [nswindow setFrameOrigin:rect.origin];
  1609     s_moveHack = moveHack;
  1610 
  1611     ScheduleContextUpdates(windata);
  1612 }}
  1613 
  1614 void
  1615 Cocoa_SetWindowSize(_THIS, SDL_Window * window)
  1616 { @autoreleasepool
  1617 {
  1618     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1619     NSWindow *nswindow = windata->nswindow;
  1620     NSRect rect;
  1621     Uint32 moveHack;
  1622 
  1623     /* Cocoa will resize the window from the bottom-left rather than the
  1624      * top-left when -[nswindow setContentSize:] is used, so we must set the
  1625      * entire frame based on the new size, in order to preserve the position.
  1626      */
  1627     rect.origin.x = window->x;
  1628     rect.origin.y = window->y;
  1629     rect.size.width = window->w;
  1630     rect.size.height = window->h;
  1631     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1632 
  1633     moveHack = s_moveHack;
  1634     s_moveHack = 0;
  1635     [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES];
  1636     s_moveHack = moveHack;
  1637 
  1638     ScheduleContextUpdates(windata);
  1639 }}
  1640 
  1641 void
  1642 Cocoa_SetWindowMinimumSize(_THIS, SDL_Window * window)
  1643 { @autoreleasepool
  1644 {
  1645     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1646 
  1647     NSSize minSize;
  1648     minSize.width = window->min_w;
  1649     minSize.height = window->min_h;
  1650 
  1651     [windata->nswindow setContentMinSize:minSize];
  1652 }}
  1653 
  1654 void
  1655 Cocoa_SetWindowMaximumSize(_THIS, SDL_Window * window)
  1656 { @autoreleasepool
  1657 {
  1658     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1659 
  1660     NSSize maxSize;
  1661     maxSize.width = window->max_w;
  1662     maxSize.height = window->max_h;
  1663 
  1664     [windata->nswindow setContentMaxSize:maxSize];
  1665 }}
  1666 
  1667 void
  1668 Cocoa_ShowWindow(_THIS, SDL_Window * window)
  1669 { @autoreleasepool
  1670 {
  1671     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1672     NSWindow *nswindow = windowData->nswindow;
  1673 
  1674     if (![nswindow isMiniaturized]) {
  1675         [windowData->listener pauseVisibleObservation];
  1676         [nswindow makeKeyAndOrderFront:nil];
  1677         [windowData->listener resumeVisibleObservation];
  1678     }
  1679 }}
  1680 
  1681 void
  1682 Cocoa_HideWindow(_THIS, SDL_Window * window)
  1683 { @autoreleasepool
  1684 {
  1685     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1686 
  1687     [nswindow orderOut:nil];
  1688 }}
  1689 
  1690 void
  1691 Cocoa_RaiseWindow(_THIS, SDL_Window * window)
  1692 { @autoreleasepool
  1693 {
  1694     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1695     NSWindow *nswindow = windowData->nswindow;
  1696 
  1697     /* makeKeyAndOrderFront: has the side-effect of deminiaturizing and showing
  1698        a minimized or hidden window, so check for that before showing it.
  1699      */
  1700     [windowData->listener pauseVisibleObservation];
  1701     if (![nswindow isMiniaturized] && [nswindow isVisible]) {
  1702         [NSApp activateIgnoringOtherApps:YES];
  1703         [nswindow makeKeyAndOrderFront:nil];
  1704     }
  1705     [windowData->listener resumeVisibleObservation];
  1706 }}
  1707 
  1708 void
  1709 Cocoa_MaximizeWindow(_THIS, SDL_Window * window)
  1710 { @autoreleasepool
  1711 {
  1712     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1713     NSWindow *nswindow = windata->nswindow;
  1714 
  1715     [nswindow zoom:nil];
  1716 
  1717     ScheduleContextUpdates(windata);
  1718 }}
  1719 
  1720 void
  1721 Cocoa_MinimizeWindow(_THIS, SDL_Window * window)
  1722 { @autoreleasepool
  1723 {
  1724     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1725     NSWindow *nswindow = data->nswindow;
  1726 
  1727     if ([data->listener isInFullscreenSpaceTransition]) {
  1728         [data->listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
  1729     } else {
  1730         [nswindow miniaturize:nil];
  1731     }
  1732 }}
  1733 
  1734 void
  1735 Cocoa_RestoreWindow(_THIS, SDL_Window * window)
  1736 { @autoreleasepool
  1737 {
  1738     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1739 
  1740     if ([nswindow isMiniaturized]) {
  1741         [nswindow deminiaturize:nil];
  1742     } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1743         [nswindow zoom:nil];
  1744     }
  1745 }}
  1746 
  1747 void
  1748 Cocoa_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
  1749 { @autoreleasepool
  1750 {
  1751     if (SetWindowStyle(window, GetWindowStyle(window))) {
  1752         if (bordered) {
  1753             Cocoa_SetWindowTitle(_this, window);  /* this got blanked out. */
  1754         }
  1755     }
  1756 }}
  1757 
  1758 void
  1759 Cocoa_SetWindowResizable(_THIS, SDL_Window * window, SDL_bool resizable)
  1760 { @autoreleasepool
  1761 {
  1762     /* Don't set this if we're in a space!
  1763      * The window will get permanently stuck if resizable is false.
  1764      * -flibit
  1765      */
  1766     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1767     Cocoa_WindowListener *listener = data->listener;
  1768     if (![listener isInFullscreenSpace]) {
  1769         SetWindowStyle(window, GetWindowStyle(window));
  1770     }
  1771 }}
  1772 
  1773 void
  1774 Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
  1775 { @autoreleasepool
  1776 {
  1777     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1778     NSWindow *nswindow = data->nswindow;
  1779     NSRect rect;
  1780 
  1781     /* The view responder chain gets messed with during setStyleMask */
  1782     if ([[nswindow contentView] nextResponder] == data->listener) {
  1783         [[nswindow contentView] setNextResponder:nil];
  1784     }
  1785 
  1786     if (fullscreen) {
  1787         SDL_Rect bounds;
  1788 
  1789         Cocoa_GetDisplayBounds(_this, display, &bounds);
  1790         rect.origin.x = bounds.x;
  1791         rect.origin.y = bounds.y;
  1792         rect.size.width = bounds.w;
  1793         rect.size.height = bounds.h;
  1794         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1795 
  1796         /* Hack to fix origin on Mac OS X 10.4 */
  1797         NSRect screenRect = [[nswindow screen] frame];
  1798         if (screenRect.size.height >= 1.0f) {
  1799             rect.origin.y += (screenRect.size.height - rect.size.height);
  1800         }
  1801 
  1802         [nswindow setStyleMask:NSWindowStyleMaskBorderless];
  1803     } else {
  1804         rect.origin.x = window->windowed.x;
  1805         rect.origin.y = window->windowed.y;
  1806         rect.size.width = window->windowed.w;
  1807         rect.size.height = window->windowed.h;
  1808         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1809 
  1810         /* The window is not meant to be fullscreen, but its flags might have a
  1811          * fullscreen bit set if it's scheduled to go fullscreen immediately
  1812          * after. Always using the windowed mode style here works around bugs in
  1813          * macOS 10.15 where the window doesn't properly restore the windowed
  1814          * mode decorations after exiting fullscreen-desktop, when the window
  1815          * was created as fullscreen-desktop. */
  1816         [nswindow setStyleMask:GetWindowWindowedStyle(window)];
  1817 
  1818         /* Hack to restore window decorations on Mac OS X 10.10 */
  1819         NSRect frameRect = [nswindow frame];
  1820         [nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
  1821         [nswindow setFrame:frameRect display:NO];
  1822     }
  1823 
  1824     /* The view responder chain gets messed with during setStyleMask */
  1825     if ([[nswindow contentView] nextResponder] != data->listener) {
  1826         [[nswindow contentView] setNextResponder:data->listener];
  1827     }
  1828 
  1829     s_moveHack = 0;
  1830     [nswindow setContentSize:rect.size];
  1831     [nswindow setFrameOrigin:rect.origin];
  1832     s_moveHack = SDL_GetTicks();
  1833 
  1834     /* When the window style changes the title is cleared */
  1835     if (!fullscreen) {
  1836         Cocoa_SetWindowTitle(_this, window);
  1837     }
  1838 
  1839     if (SDL_ShouldAllowTopmost() && fullscreen) {
  1840         /* OpenGL is rendering to the window, so make it visible! */
  1841         [nswindow setLevel:CGShieldingWindowLevel()];
  1842     } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
  1843         [nswindow setLevel:NSFloatingWindowLevel];
  1844     } else {
  1845         [nswindow setLevel:kCGNormalWindowLevel];
  1846     }
  1847 
  1848     if ([nswindow isVisible] || fullscreen) {
  1849         [data->listener pauseVisibleObservation];
  1850         [nswindow makeKeyAndOrderFront:nil];
  1851         [data->listener resumeVisibleObservation];
  1852     }
  1853 
  1854     ScheduleContextUpdates(data);
  1855 }}
  1856 
  1857 int
  1858 Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
  1859 {
  1860     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1861     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1862     const uint32_t tableSize = 256;
  1863     CGGammaValue redTable[tableSize];
  1864     CGGammaValue greenTable[tableSize];
  1865     CGGammaValue blueTable[tableSize];
  1866     uint32_t i;
  1867     float inv65535 = 1.0f / 65535.0f;
  1868 
  1869     /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
  1870     for (i = 0; i < 256; i++) {
  1871         redTable[i] = ramp[0*256+i] * inv65535;
  1872         greenTable[i] = ramp[1*256+i] * inv65535;
  1873         blueTable[i] = ramp[2*256+i] * inv65535;
  1874     }
  1875 
  1876     if (CGSetDisplayTransferByTable(display_id, tableSize,
  1877                                     redTable, greenTable, blueTable) != CGDisplayNoErr) {
  1878         return SDL_SetError("CGSetDisplayTransferByTable()");
  1879     }
  1880     return 0;
  1881 }
  1882 
  1883 int
  1884 Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
  1885 {
  1886     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1887     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1888     const uint32_t tableSize = 256;
  1889     CGGammaValue redTable[tableSize];
  1890     CGGammaValue greenTable[tableSize];
  1891     CGGammaValue blueTable[tableSize];
  1892     uint32_t i, tableCopied;
  1893 
  1894     if (CGGetDisplayTransferByTable(display_id, tableSize,
  1895                                     redTable, greenTable, blueTable, &tableCopied) != CGDisplayNoErr) {
  1896         return SDL_SetError("CGGetDisplayTransferByTable()");
  1897     }
  1898 
  1899     for (i = 0; i < tableCopied; i++) {
  1900         ramp[0*256+i] = (Uint16)(redTable[i] * 65535.0f);
  1901         ramp[1*256+i] = (Uint16)(greenTable[i] * 65535.0f);
  1902         ramp[2*256+i] = (Uint16)(blueTable[i] * 65535.0f);
  1903     }
  1904     return 0;
  1905 }
  1906 
  1907 void
  1908 Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
  1909 {
  1910     SDL_Mouse *mouse = SDL_GetMouse();
  1911     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1912 
  1913     /* Enable or disable the event tap as necessary */
  1914     Cocoa_EnableMouseEventTap(mouse->driverdata, grabbed);
  1915 
  1916     /* Move the cursor to the nearest point in the window */
  1917     if (grabbed && data && ![data->listener isMoving]) {
  1918         int x, y;
  1919         CGPoint cgpoint;
  1920 
  1921         SDL_GetMouseState(&x, &y);
  1922         cgpoint.x = window->x + x;
  1923         cgpoint.y = window->y + y;
  1924 
  1925         Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
  1926 
  1927         DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
  1928         CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
  1929     }
  1930 
  1931     if ( data && (window->flags & SDL_WINDOW_FULLSCREEN) ) {
  1932         if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS)
  1933             && ![data->listener isInFullscreenSpace]) {
  1934             /* OpenGL is rendering to the window, so make it visible! */
  1935             /* Doing this in 10.11 while in a Space breaks things (bug #3152) */
  1936             [data->nswindow setLevel:CGShieldingWindowLevel()];
  1937         } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
  1938             [data->nswindow setLevel:NSFloatingWindowLevel];
  1939         } else {
  1940             [data->nswindow setLevel:kCGNormalWindowLevel];
  1941         }
  1942     }
  1943 }
  1944 
  1945 void
  1946 Cocoa_DestroyWindow(_THIS, SDL_Window * window)
  1947 { @autoreleasepool
  1948 {
  1949     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1950 
  1951     if (data) {
  1952         if ([data->listener isInFullscreenSpace]) {
  1953             [NSMenu setMenuBarVisible:YES];
  1954         }
  1955         [data->listener close];
  1956         [data->listener release];
  1957         if (data->created) {
  1958             /* Release the content view to avoid further updateLayer callbacks */
  1959             [data->nswindow setContentView:nil];
  1960             [data->nswindow close];
  1961         }
  1962 
  1963         NSArray *contexts = [[data->nscontexts copy] autorelease];
  1964         for (SDLOpenGLContext *context in contexts) {
  1965             /* Calling setWindow:NULL causes the context to remove itself from the context list. */            
  1966             [context setWindow:NULL];
  1967         }
  1968         [data->nscontexts release];
  1969 
  1970         SDL_free(data);
  1971     }
  1972     window->driverdata = NULL;
  1973 }}
  1974 
  1975 SDL_bool
  1976 Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
  1977 {
  1978     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1979 
  1980     if (info->version.major <= SDL_MAJOR_VERSION) {
  1981         info->subsystem = SDL_SYSWM_COCOA;
  1982         info->info.cocoa.window = nswindow;
  1983         return SDL_TRUE;
  1984     } else {
  1985         SDL_SetError("Application not compiled with SDL %d.%d",
  1986                      SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
  1987         return SDL_FALSE;
  1988     }
  1989 }
  1990 
  1991 SDL_bool
  1992 Cocoa_IsWindowInFullscreenSpace(SDL_Window * window)
  1993 {
  1994     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1995 
  1996     if ([data->listener isInFullscreenSpace]) {
  1997         return SDL_TRUE;
  1998     } else {
  1999         return SDL_FALSE;
  2000     }
  2001 }
  2002 
  2003 SDL_bool
  2004 Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state)
  2005 { @autoreleasepool
  2006 {
  2007     SDL_bool succeeded = SDL_FALSE;
  2008     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  2009 
  2010     if ([data->listener setFullscreenSpace:(state ? YES : NO)]) {
  2011         const int maxattempts = 3;
  2012         int attempt = 0;
  2013         while (++attempt <= maxattempts) {
  2014             /* Wait for the transition to complete, so application changes
  2015              take effect properly (e.g. setting the window size, etc.)
  2016              */
  2017             const int limit = 10000;
  2018             int count = 0;
  2019             while ([data->listener isInFullscreenSpaceTransition]) {
  2020                 if ( ++count == limit ) {
  2021                     /* Uh oh, transition isn't completing. Should we assert? */
  2022                     break;
  2023                 }
  2024                 SDL_Delay(1);
  2025                 SDL_PumpEvents();
  2026             }
  2027             if ([data->listener isInFullscreenSpace] == (state ? YES : NO))
  2028                 break;
  2029             /* Try again, the last attempt was interrupted by user gestures */
  2030             if (![data->listener setFullscreenSpace:(state ? YES : NO)])
  2031                 break; /* ??? */
  2032         }
  2033         /* Return TRUE to prevent non-space fullscreen logic from running */
  2034         succeeded = SDL_TRUE;
  2035     }
  2036 
  2037     return succeeded;
  2038 }}
  2039 
  2040 int
  2041 Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
  2042 {
  2043     return 0;  /* just succeed, the real work is done elsewhere. */
  2044 }
  2045 
  2046 void
  2047 Cocoa_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept)
  2048 {
  2049     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  2050     if (accept) {
  2051         [data->nswindow registerForDraggedTypes:[NSArray arrayWithObject:(NSString *)kUTTypeFileURL]];
  2052     } else {
  2053         [data->nswindow unregisterDraggedTypes];
  2054     }
  2055 }
  2056 
  2057 int
  2058 Cocoa_SetWindowOpacity(_THIS, SDL_Window * window, float opacity)
  2059 {
  2060     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  2061     [data->nswindow setAlphaValue:opacity];
  2062     return 0;
  2063 }
  2064 
  2065 #endif /* SDL_VIDEO_DRIVER_COCOA */
  2066 
  2067 /* vi: set ts=4 sw=4 expandtab: */