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