src/video/cocoa/SDL_cocoawindow.m
author Sam Lantinga <slouken@libsdl.org>
Fri, 16 Sep 2016 22:27:58 -0700
changeset 10353 c0f4fe10d1b3
parent 10339 826c707b4e7d
child 10366 e1b52e69578b
permissions -rw-r--r--
Fixed warning with Xcode 7.3.0
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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_cocoaopengl.h"
    42 #include "SDL_assert.h"
    43 
    44 /* #define DEBUG_COCOAWINDOW */
    45 
    46 #ifdef DEBUG_COCOAWINDOW
    47 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
    48 #else
    49 #define DLog(...) do { } while (0)
    50 #endif
    51 
    52 
    53 #define FULLSCREEN_MASK (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN)
    54 
    55 
    56 @interface SDLWindow : NSWindow <NSDraggingDestination>
    57 /* These are needed for borderless/fullscreen windows */
    58 - (BOOL)canBecomeKeyWindow;
    59 - (BOOL)canBecomeMainWindow;
    60 - (void)sendEvent:(NSEvent *)event;
    61 - (void)doCommandBySelector:(SEL)aSelector;
    62 
    63 /* Handle drag-and-drop of files onto the SDL window. */
    64 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
    65 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
    66 - (BOOL)wantsPeriodicDraggingUpdates;
    67 @end
    68 
    69 @implementation SDLWindow
    70 
    71 - (BOOL)canBecomeKeyWindow
    72 {
    73     return YES;
    74 }
    75 
    76 - (BOOL)canBecomeMainWindow
    77 {
    78     return YES;
    79 }
    80 
    81 - (void)sendEvent:(NSEvent *)event
    82 {
    83     [super sendEvent:event];
    84 
    85     if ([event type] != NSLeftMouseUp) {
    86         return;
    87     }
    88 
    89     id delegate = [self delegate];
    90     if (![delegate isKindOfClass:[Cocoa_WindowListener class]]) {
    91         return;
    92     }
    93 
    94     if ([delegate isMoving]) {
    95         [delegate windowDidFinishMoving];
    96     }
    97 }
    98 
    99 /* We'll respond to selectors by doing nothing so we don't beep.
   100  * The escape key gets converted to a "cancel" selector, etc.
   101  */
   102 - (void)doCommandBySelector:(SEL)aSelector
   103 {
   104     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
   105 }
   106 
   107 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   108 {
   109     if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) {
   110         return NSDragOperationGeneric;
   111     }
   112 
   113     return NSDragOperationNone; /* no idea what to do with this, reject it. */
   114 }
   115 
   116 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   117 { @autoreleasepool
   118 {
   119     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   120     NSPasteboard *pasteboard = [sender draggingPasteboard];
   121     NSArray *types = [NSArray arrayWithObject:NSFilenamesPboardType];
   122     NSString *desiredType = [pasteboard availableTypeFromArray:types];
   123     SDL_Window *sdlwindow = nil;
   124 
   125     if (desiredType == nil) {
   126         return NO;  /* can't accept anything that's being dropped here. */
   127     }
   128 
   129     NSData *data = [pasteboard dataForType:desiredType];
   130     if (data == nil) {
   131         return NO;
   132     }
   133 
   134     SDL_assert([desiredType isEqualToString:NSFilenamesPboardType]);
   135     NSArray *array = [pasteboard propertyListForType:@"NSFilenamesPboardType"];
   136 
   137     for (NSString *path in array) {
   138         NSURL *fileURL = [NSURL fileURLWithPath:path];
   139         NSNumber *isAlias = nil;
   140 
   141         [fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil];
   142 
   143         /* If the URL is an alias, resolve it. */
   144         if ([isAlias boolValue]) {
   145             NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI;
   146             NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil];
   147             if (bookmark != nil) {
   148                 NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark
   149                                                                options:opts
   150                                                          relativeToURL:nil
   151                                                    bookmarkDataIsStale:nil
   152                                                                  error:nil];
   153 
   154                 if (resolvedURL != nil) {
   155                     fileURL = resolvedURL;
   156                 }
   157             }
   158         }
   159 
   160         /* !!! FIXME: is there a better way to do this? */
   161         if (_this) {
   162             for (sdlwindow = _this->windows; sdlwindow; sdlwindow = sdlwindow->next) {
   163                 NSWindow *nswindow = ((SDL_WindowData *) sdlwindow->driverdata)->nswindow;
   164                 if (nswindow == self) {
   165                     break;
   166                 }
   167             }
   168         }
   169 
   170         if (!SDL_SendDropFile(sdlwindow, [[fileURL path] UTF8String])) {
   171             return NO;
   172         }
   173     }
   174 
   175     SDL_SendDropComplete(sdlwindow);
   176     return YES;
   177 }}
   178 
   179 - (BOOL)wantsPeriodicDraggingUpdates
   180 {
   181     return NO;
   182 }
   183 
   184 @end
   185 
   186 
   187 static Uint32 s_moveHack;
   188 
   189 static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
   190 {
   191     r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height;
   192 }
   193 
   194 static void
   195 ScheduleContextUpdates(SDL_WindowData *data)
   196 {
   197     NSOpenGLContext *currentContext = [NSOpenGLContext currentContext];
   198     NSMutableArray *contexts = data->nscontexts;
   199     @synchronized (contexts) {
   200         for (SDLOpenGLContext *context in contexts) {
   201             if (context == currentContext) {
   202                 [context update];
   203             } else {
   204                 [context scheduleUpdate];
   205             }
   206         }
   207     }
   208 }
   209 
   210 /* !!! FIXME: this should use a hint callback. */
   211 static int
   212 GetHintCtrlClickEmulateRightClick()
   213 {
   214 	const char *hint = SDL_GetHint( SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK );
   215 	return hint != NULL && *hint != '0';
   216 }
   217 
   218 static NSUInteger
   219 GetWindowStyle(SDL_Window * window)
   220 {
   221     NSUInteger style = 0;
   222 
   223     if (window->flags & SDL_WINDOW_FULLSCREEN) {
   224         style = NSBorderlessWindowMask;
   225     } else {
   226         if (window->flags & SDL_WINDOW_BORDERLESS) {
   227             style = NSBorderlessWindowMask;
   228         } else {
   229             style = (NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask);
   230         }
   231         if (window->flags & SDL_WINDOW_RESIZABLE) {
   232             style |= NSResizableWindowMask;
   233         }
   234     }
   235     return style;
   236 }
   237 
   238 static SDL_bool
   239 SetWindowStyle(SDL_Window * window, NSUInteger style)
   240 {
   241     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   242     NSWindow *nswindow = data->nswindow;
   243 
   244     /* The view responder chain gets messed with during setStyleMask */
   245     if ([[nswindow contentView] nextResponder] == data->listener) {
   246         [[nswindow contentView] setNextResponder:nil];
   247     }
   248 
   249     [nswindow setStyleMask:style];
   250 
   251     /* The view responder chain gets messed with during setStyleMask */
   252     if ([[nswindow contentView] nextResponder] != data->listener) {
   253         [[nswindow contentView] setNextResponder:data->listener];
   254     }
   255 
   256     return SDL_TRUE;
   257 }
   258 
   259 
   260 @implementation Cocoa_WindowListener
   261 
   262 - (void)listen:(SDL_WindowData *)data
   263 {
   264     NSNotificationCenter *center;
   265     NSWindow *window = data->nswindow;
   266     NSView *view = [window contentView];
   267 
   268     _data = data;
   269     observingVisible = YES;
   270     wasCtrlLeft = NO;
   271     wasVisible = [window isVisible];
   272     isFullscreenSpace = NO;
   273     inFullscreenTransition = NO;
   274     pendingWindowOperation = PENDING_OPERATION_NONE;
   275     isMoving = NO;
   276     isDragAreaRunning = NO;
   277 
   278     center = [NSNotificationCenter defaultCenter];
   279 
   280     if ([window delegate] != nil) {
   281         [center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window];
   282         [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window];
   283         [center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window];
   284         [center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window];
   285         [center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window];
   286         [center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window];
   287         [center addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window];
   288         [center addObserver:self selector:@selector(windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window];
   289         [center addObserver:self selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window];
   290         [center addObserver:self selector:@selector(windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window];
   291         [center addObserver:self selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window];
   292         [center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window];
   293         [center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
   294         [center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
   295     } else {
   296         [window setDelegate:self];
   297     }
   298 
   299     /* Haven't found a delegate / notification that triggers when the window is
   300      * ordered out (is not visible any more). You can be ordered out without
   301      * minimizing, so DidMiniaturize doesn't work. (e.g. -[NSWindow orderOut:])
   302      */
   303     [window addObserver:self
   304              forKeyPath:@"visible"
   305                 options:NSKeyValueObservingOptionNew
   306                 context:NULL];
   307 
   308     [window setNextResponder:self];
   309     [window setAcceptsMouseMovedEvents:YES];
   310 
   311     [view setNextResponder:self];
   312 
   313     [view setAcceptsTouchEvents:YES];
   314 }
   315 
   316 - (void)observeValueForKeyPath:(NSString *)keyPath
   317                       ofObject:(id)object
   318                         change:(NSDictionary *)change
   319                        context:(void *)context
   320 {
   321     if (!observingVisible) {
   322         return;
   323     }
   324 
   325     if (object == _data->nswindow && [keyPath isEqualToString:@"visible"]) {
   326         int newVisibility = [[change objectForKey:@"new"] intValue];
   327         if (newVisibility) {
   328             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
   329         } else {
   330             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
   331         }
   332     }
   333 }
   334 
   335 -(void) pauseVisibleObservation
   336 {
   337     observingVisible = NO;
   338     wasVisible = [_data->nswindow isVisible];
   339 }
   340 
   341 -(void) resumeVisibleObservation
   342 {
   343     BOOL isVisible = [_data->nswindow isVisible];
   344     observingVisible = YES;
   345     if (wasVisible != isVisible) {
   346         if (isVisible) {
   347             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
   348         } else {
   349             SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
   350         }
   351 
   352         wasVisible = isVisible;
   353     }
   354 }
   355 
   356 -(BOOL) setFullscreenSpace:(BOOL) state
   357 {
   358     SDL_Window *window = _data->window;
   359     NSWindow *nswindow = _data->nswindow;
   360     SDL_VideoData *videodata = ((SDL_WindowData *) window->driverdata)->videodata;
   361 
   362     if (!videodata->allow_spaces) {
   363         return NO;  /* Spaces are forcibly disabled. */
   364     } else if (state && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   365         return NO;  /* we only allow you to make a Space on FULLSCREEN_DESKTOP windows. */
   366     } else if (!state && ((window->last_fullscreen_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   367         return NO;  /* we only handle leaving the Space on windows that were previously FULLSCREEN_DESKTOP. */
   368     } else if (state == isFullscreenSpace) {
   369         return YES;  /* already there. */
   370     }
   371 
   372     if (inFullscreenTransition) {
   373         if (state) {
   374             [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
   375         } else {
   376             [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
   377         }
   378         return YES;
   379     }
   380     inFullscreenTransition = YES;
   381 
   382     /* you need to be FullScreenPrimary, or toggleFullScreen doesn't work. Unset it again in windowDidExitFullScreen. */
   383     [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
   384     [nswindow performSelectorOnMainThread: @selector(toggleFullScreen:) withObject:nswindow waitUntilDone:NO];
   385     return YES;
   386 }
   387 
   388 -(BOOL) isInFullscreenSpace
   389 {
   390     return isFullscreenSpace;
   391 }
   392 
   393 -(BOOL) isInFullscreenSpaceTransition
   394 {
   395     return inFullscreenTransition;
   396 }
   397 
   398 -(void) addPendingWindowOperation:(PendingWindowOperation) operation
   399 {
   400     pendingWindowOperation = operation;
   401 }
   402 
   403 - (void)close
   404 {
   405     NSNotificationCenter *center;
   406     NSWindow *window = _data->nswindow;
   407     NSView *view = [window contentView];
   408 
   409     center = [NSNotificationCenter defaultCenter];
   410 
   411     if ([window delegate] != self) {
   412         [center removeObserver:self name:NSWindowDidExposeNotification object:window];
   413         [center removeObserver:self name:NSWindowDidMoveNotification object:window];
   414         [center removeObserver:self name:NSWindowDidResizeNotification object:window];
   415         [center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window];
   416         [center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window];
   417         [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window];
   418         [center removeObserver:self name:NSWindowDidResignKeyNotification object:window];
   419         [center removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window];
   420         [center removeObserver:self name:NSWindowWillEnterFullScreenNotification object:window];
   421         [center removeObserver:self name:NSWindowDidEnterFullScreenNotification object:window];
   422         [center removeObserver:self name:NSWindowWillExitFullScreenNotification object:window];
   423         [center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window];
   424         [center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
   425         [center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
   426     } else {
   427         [window setDelegate:nil];
   428     }
   429 
   430     [window removeObserver:self forKeyPath:@"visible"];
   431 
   432     if ([window nextResponder] == self) {
   433         [window setNextResponder:nil];
   434     }
   435     if ([view nextResponder] == self) {
   436         [view setNextResponder:nil];
   437     }
   438 }
   439 
   440 - (BOOL)isMoving
   441 {
   442     return isMoving;
   443 }
   444 
   445 -(void) setPendingMoveX:(int)x Y:(int)y
   446 {
   447     pendingWindowWarpX = x;
   448     pendingWindowWarpY = y;
   449 }
   450 
   451 - (void)windowDidFinishMoving
   452 {
   453     if ([self isMoving]) {
   454         isMoving = NO;
   455 
   456         SDL_Mouse *mouse = SDL_GetMouse();
   457         if (pendingWindowWarpX != INT_MAX && pendingWindowWarpY != INT_MAX) {
   458             mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY);
   459             pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
   460         }
   461         if (mouse->relative_mode && !mouse->relative_mode_warp && mouse->focus == _data->window) {
   462             mouse->SetRelativeMouseMode(SDL_TRUE);
   463         }
   464     }
   465 }
   466 
   467 - (BOOL)windowShouldClose:(id)sender
   468 {
   469     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_CLOSE, 0, 0);
   470     return NO;
   471 }
   472 
   473 - (void)windowDidExpose:(NSNotification *)aNotification
   474 {
   475     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_EXPOSED, 0, 0);
   476 }
   477 
   478 - (void)windowWillMove:(NSNotification *)aNotification
   479 {
   480     if ([_data->nswindow isKindOfClass:[SDLWindow class]]) {
   481         pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
   482         isMoving = YES;
   483     }
   484 }
   485 
   486 - (void)windowDidMove:(NSNotification *)aNotification
   487 {
   488     int x, y;
   489     SDL_Window *window = _data->window;
   490     NSWindow *nswindow = _data->nswindow;
   491     BOOL fullscreen = window->flags & FULLSCREEN_MASK;
   492     NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
   493     ConvertNSRect([nswindow screen], fullscreen, &rect);
   494 
   495     if (s_moveHack) {
   496         SDL_bool blockMove = ((SDL_GetTicks() - s_moveHack) < 500);
   497 
   498         s_moveHack = 0;
   499 
   500         if (blockMove) {
   501             /* Cocoa is adjusting the window in response to a mode change */
   502             rect.origin.x = window->x;
   503             rect.origin.y = window->y;
   504             ConvertNSRect([nswindow screen], fullscreen, &rect);
   505             [nswindow setFrameOrigin:rect.origin];
   506             return;
   507         }
   508     }
   509 
   510     x = (int)rect.origin.x;
   511     y = (int)rect.origin.y;
   512 
   513     ScheduleContextUpdates(_data);
   514 
   515     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
   516 }
   517 
   518 - (void)windowDidResize:(NSNotification *)aNotification
   519 {
   520     if (inFullscreenTransition) {
   521         /* We'll take care of this at the end of the transition */
   522         return;
   523     }
   524 
   525     SDL_Window *window = _data->window;
   526     NSWindow *nswindow = _data->nswindow;
   527     int x, y, w, h;
   528     NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
   529     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
   530     x = (int)rect.origin.x;
   531     y = (int)rect.origin.y;
   532     w = (int)rect.size.width;
   533     h = (int)rect.size.height;
   534 
   535     if (SDL_IsShapedWindow(window)) {
   536         Cocoa_ResizeWindowShape(window);
   537     }
   538 
   539     ScheduleContextUpdates(_data);
   540 
   541     /* The window can move during a resize event, such as when maximizing
   542        or resizing from a corner */
   543     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
   544     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
   545 
   546     const BOOL zoomed = [nswindow isZoomed];
   547     if (!zoomed) {
   548         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   549     } else if (zoomed) {
   550         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
   551     }
   552 }
   553 
   554 - (void)windowDidMiniaturize:(NSNotification *)aNotification
   555 {
   556     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   557 }
   558 
   559 - (void)windowDidDeminiaturize:(NSNotification *)aNotification
   560 {
   561     SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   562 }
   563 
   564 - (void)windowDidBecomeKey:(NSNotification *)aNotification
   565 {
   566     SDL_Window *window = _data->window;
   567     SDL_Mouse *mouse = SDL_GetMouse();
   568 
   569     /* We're going to get keyboard events, since we're key. */
   570     /* This needs to be done before restoring the relative mouse mode. */
   571     SDL_SetKeyboardFocus(window);
   572 
   573     if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMoving]) {
   574         mouse->SetRelativeMouseMode(SDL_TRUE);
   575     }
   576 
   577     /* If we just gained focus we need the updated mouse position */
   578     if (!mouse->relative_mode) {
   579         NSPoint point;
   580         int x, y;
   581 
   582         point = [_data->nswindow mouseLocationOutsideOfEventStream];
   583         x = (int)point.x;
   584         y = (int)(window->h - point.y);
   585 
   586         if (x >= 0 && x < window->w && y >= 0 && y < window->h) {
   587             SDL_SendMouseMotion(window, 0, 0, x, y);
   588         }
   589     }
   590 
   591     /* Check to see if someone updated the clipboard */
   592     Cocoa_CheckClipboardUpdate(_data->videodata);
   593 
   594     if ((isFullscreenSpace) && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)) {
   595         [NSMenu setMenuBarVisible:NO];
   596     }
   597 
   598     const unsigned int newflags = [NSEvent modifierFlags] & NSAlphaShiftKeyMask;
   599     _data->videodata->modifierFlags = (_data->videodata->modifierFlags & ~NSAlphaShiftKeyMask) | newflags;
   600     SDL_ToggleModState(KMOD_CAPS, newflags != 0);
   601 }
   602 
   603 - (void)windowDidResignKey:(NSNotification *)aNotification
   604 {
   605     SDL_Mouse *mouse = SDL_GetMouse();
   606     if (mouse->relative_mode && !mouse->relative_mode_warp) {
   607         mouse->SetRelativeMouseMode(SDL_FALSE);
   608     }
   609 
   610     /* Some other window will get mouse events, since we're not key. */
   611     if (SDL_GetMouseFocus() == _data->window) {
   612         SDL_SetMouseFocus(NULL);
   613     }
   614 
   615     /* Some other window will get keyboard events, since we're not key. */
   616     if (SDL_GetKeyboardFocus() == _data->window) {
   617         SDL_SetKeyboardFocus(NULL);
   618     }
   619 
   620     if (isFullscreenSpace) {
   621         [NSMenu setMenuBarVisible:YES];
   622     }
   623 }
   624 
   625 - (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
   626 {
   627     NSNumber *oldscale = [[aNotification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey];
   628 
   629     if (inFullscreenTransition) {
   630         return;
   631     }
   632 
   633     if ([oldscale doubleValue] != [_data->nswindow backingScaleFactor]) {
   634         /* Force a resize event when the backing scale factor changes. */
   635         _data->window->w = 0;
   636         _data->window->h = 0;
   637         [self windowDidResize:aNotification];
   638     }
   639 }
   640 
   641 - (void)windowWillEnterFullScreen:(NSNotification *)aNotification
   642 {
   643     SDL_Window *window = _data->window;
   644 
   645     SetWindowStyle(window, (NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask));
   646 
   647     isFullscreenSpace = YES;
   648     inFullscreenTransition = YES;
   649 }
   650 
   651 - (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
   652 {
   653     SDL_Window *window = _data->window;
   654 
   655     if (window->is_destroying) {
   656         return;
   657     }
   658 
   659     SetWindowStyle(window, GetWindowStyle(window));
   660 
   661     isFullscreenSpace = NO;
   662     inFullscreenTransition = NO;
   663     
   664     [self windowDidExitFullScreen:nil];
   665 }
   666 
   667 - (void)windowDidEnterFullScreen:(NSNotification *)aNotification
   668 {
   669     SDL_Window *window = _data->window;
   670 
   671     inFullscreenTransition = NO;
   672 
   673     if (pendingWindowOperation == PENDING_OPERATION_LEAVE_FULLSCREEN) {
   674         pendingWindowOperation = PENDING_OPERATION_NONE;
   675         [self setFullscreenSpace:NO];
   676     } else {
   677         if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   678             [NSMenu setMenuBarVisible:NO];
   679         }
   680 
   681         pendingWindowOperation = PENDING_OPERATION_NONE;
   682         /* Force the size change event in case it was delivered earlier
   683            while the window was still animating into place.
   684          */
   685         window->w = 0;
   686         window->h = 0;
   687         [self windowDidResize:aNotification];
   688     }
   689 }
   690 
   691 - (void)windowWillExitFullScreen:(NSNotification *)aNotification
   692 {
   693     SDL_Window *window = _data->window;
   694 
   695     /* As of OS X 10.11, the window seems to need to be resizable when exiting
   696        a Space, in order for it to resize back to its windowed-mode size.
   697      */
   698     SetWindowStyle(window, GetWindowStyle(window) | NSResizableWindowMask);
   699 
   700     isFullscreenSpace = NO;
   701     inFullscreenTransition = YES;
   702 }
   703 
   704 - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
   705 {
   706     SDL_Window *window = _data->window;
   707     
   708     if (window->is_destroying) {
   709         return;
   710     }
   711 
   712     SetWindowStyle(window, (NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask));
   713     
   714     isFullscreenSpace = YES;
   715     inFullscreenTransition = NO;
   716     
   717     [self windowDidEnterFullScreen:nil];
   718 }
   719 
   720 - (void)windowDidExitFullScreen:(NSNotification *)aNotification
   721 {
   722     SDL_Window *window = _data->window;
   723     NSWindow *nswindow = _data->nswindow;
   724 
   725     inFullscreenTransition = NO;
   726 
   727     SetWindowStyle(window, GetWindowStyle(window));
   728 
   729     [nswindow setLevel:kCGNormalWindowLevel];
   730 
   731     if (pendingWindowOperation == PENDING_OPERATION_ENTER_FULLSCREEN) {
   732         pendingWindowOperation = PENDING_OPERATION_NONE;
   733         [self setFullscreenSpace:YES];
   734     } else if (pendingWindowOperation == PENDING_OPERATION_MINIMIZE) {
   735         pendingWindowOperation = PENDING_OPERATION_NONE;
   736         [nswindow miniaturize:nil];
   737     } else {
   738         /* Adjust the fullscreen toggle button and readd menu now that we're here. */
   739         if (window->flags & SDL_WINDOW_RESIZABLE) {
   740             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
   741             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
   742         } else {
   743             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged];
   744         }
   745         [NSMenu setMenuBarVisible:YES];
   746 
   747         pendingWindowOperation = PENDING_OPERATION_NONE;
   748         /* Force the size change event in case it was delivered earlier
   749            while the window was still animating into place.
   750          */
   751         window->w = 0;
   752         window->h = 0;
   753         [self windowDidResize:aNotification];
   754 
   755         /* FIXME: Why does the window get hidden? */
   756         if (window->flags & SDL_WINDOW_SHOWN) {
   757             Cocoa_ShowWindow(SDL_GetVideoDevice(), window);
   758         }
   759     }
   760 }
   761 
   762 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
   763 {
   764     if ((_data->window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   765         return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
   766     } else {
   767         return proposedOptions;
   768     }
   769 }
   770 
   771 
   772 /* We'll respond to key events by doing nothing so we don't beep.
   773  * We could handle key messages here, but we lose some in the NSApp dispatch,
   774  * where they get converted to action messages, etc.
   775  */
   776 - (void)flagsChanged:(NSEvent *)theEvent
   777 {
   778     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   779 }
   780 - (void)keyDown:(NSEvent *)theEvent
   781 {
   782     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   783 }
   784 - (void)keyUp:(NSEvent *)theEvent
   785 {
   786     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   787 }
   788 
   789 /* We'll respond to selectors by doing nothing so we don't beep.
   790  * The escape key gets converted to a "cancel" selector, etc.
   791  */
   792 - (void)doCommandBySelector:(SEL)aSelector
   793 {
   794     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
   795 }
   796 
   797 - (BOOL)processHitTest:(NSEvent *)theEvent
   798 {
   799     SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
   800 
   801     if (_data->window->hit_test) {  /* if no hit-test, skip this. */
   802         const NSPoint location = [theEvent locationInWindow];
   803         const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
   804         const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
   805         if (rc == SDL_HITTEST_DRAGGABLE) {
   806             if (!isDragAreaRunning) {
   807                 isDragAreaRunning = YES;
   808                 [_data->nswindow setMovableByWindowBackground:YES];
   809             }
   810             return YES;  /* dragging! */
   811         }
   812     }
   813 
   814     if (isDragAreaRunning) {
   815         isDragAreaRunning = NO;
   816         [_data->nswindow setMovableByWindowBackground:NO];
   817         return YES;  /* was dragging, drop event. */
   818     }
   819 
   820     return NO;  /* not a special area, carry on. */
   821 }
   822 
   823 - (void)mouseDown:(NSEvent *)theEvent
   824 {
   825     int button;
   826 
   827     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   828     if ([theEvent window]) {
   829         NSRect windowRect = [[[theEvent window] contentView] frame];
   830         if (!NSMouseInRect([theEvent locationInWindow], windowRect, NO)) {
   831             return;
   832         }
   833     }
   834 
   835     if ([self processHitTest:theEvent]) {
   836         SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
   837         return;  /* dragging, drop event. */
   838     }
   839 
   840     switch ([theEvent buttonNumber]) {
   841     case 0:
   842         if (([theEvent modifierFlags] & NSControlKeyMask) &&
   843 		    GetHintCtrlClickEmulateRightClick()) {
   844             wasCtrlLeft = YES;
   845             button = SDL_BUTTON_RIGHT;
   846         } else {
   847             wasCtrlLeft = NO;
   848             button = SDL_BUTTON_LEFT;
   849         }
   850         break;
   851     case 1:
   852         button = SDL_BUTTON_RIGHT;
   853         break;
   854     case 2:
   855         button = SDL_BUTTON_MIDDLE;
   856         break;
   857     default:
   858         button = (int) [theEvent buttonNumber] + 1;
   859         break;
   860     }
   861     SDL_SendMouseButton(_data->window, 0, SDL_PRESSED, button);
   862 }
   863 
   864 - (void)rightMouseDown:(NSEvent *)theEvent
   865 {
   866     [self mouseDown:theEvent];
   867 }
   868 
   869 - (void)otherMouseDown:(NSEvent *)theEvent
   870 {
   871     [self mouseDown:theEvent];
   872 }
   873 
   874 - (void)mouseUp:(NSEvent *)theEvent
   875 {
   876     int button;
   877 
   878     if ([self processHitTest:theEvent]) {
   879         SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
   880         return;  /* stopped dragging, drop event. */
   881     }
   882 
   883     switch ([theEvent buttonNumber]) {
   884     case 0:
   885         if (wasCtrlLeft) {
   886             button = SDL_BUTTON_RIGHT;
   887             wasCtrlLeft = NO;
   888         } else {
   889             button = SDL_BUTTON_LEFT;
   890         }
   891         break;
   892     case 1:
   893         button = SDL_BUTTON_RIGHT;
   894         break;
   895     case 2:
   896         button = SDL_BUTTON_MIDDLE;
   897         break;
   898     default:
   899         button = (int) [theEvent buttonNumber] + 1;
   900         break;
   901     }
   902     SDL_SendMouseButton(_data->window, 0, SDL_RELEASED, button);
   903 }
   904 
   905 - (void)rightMouseUp:(NSEvent *)theEvent
   906 {
   907     [self mouseUp:theEvent];
   908 }
   909 
   910 - (void)otherMouseUp:(NSEvent *)theEvent
   911 {
   912     [self mouseUp:theEvent];
   913 }
   914 
   915 - (void)mouseMoved:(NSEvent *)theEvent
   916 {
   917     SDL_Mouse *mouse = SDL_GetMouse();
   918     SDL_Window *window = _data->window;
   919     NSPoint point;
   920     int x, y;
   921 
   922     if ([self processHitTest:theEvent]) {
   923         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
   924         return;  /* dragging, drop event. */
   925     }
   926 
   927     if (mouse->relative_mode) {
   928         return;
   929     }
   930 
   931     point = [theEvent locationInWindow];
   932     x = (int)point.x;
   933     y = (int)(window->h - point.y);
   934 
   935     if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
   936         if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
   937             if (x < 0) {
   938                 x = 0;
   939             } else if (x >= window->w) {
   940                 x = window->w - 1;
   941             }
   942             if (y < 0) {
   943                 y = 0;
   944             } else if (y >= window->h) {
   945                 y = window->h - 1;
   946             }
   947 
   948 #if !SDL_MAC_NO_SANDBOX
   949             CGPoint cgpoint;
   950 
   951             /* When SDL_MAC_NO_SANDBOX is set, this is handled by
   952              * SDL_cocoamousetap.m.
   953              */
   954 
   955             cgpoint.x = window->x + x;
   956             cgpoint.y = window->y + y;
   957 
   958             CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
   959             CGAssociateMouseAndMouseCursorPosition(YES);
   960 
   961             Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
   962 #endif
   963         }
   964     }
   965     SDL_SendMouseMotion(window, 0, 0, x, y);
   966 }
   967 
   968 - (void)mouseDragged:(NSEvent *)theEvent
   969 {
   970     [self mouseMoved:theEvent];
   971 }
   972 
   973 - (void)rightMouseDragged:(NSEvent *)theEvent
   974 {
   975     [self mouseMoved:theEvent];
   976 }
   977 
   978 - (void)otherMouseDragged:(NSEvent *)theEvent
   979 {
   980     [self mouseMoved:theEvent];
   981 }
   982 
   983 - (void)scrollWheel:(NSEvent *)theEvent
   984 {
   985     Cocoa_HandleMouseWheel(_data->window, theEvent);
   986 }
   987 
   988 - (void)touchesBeganWithEvent:(NSEvent *) theEvent
   989 {
   990     NSSet *touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil];
   991     int existingTouchCount = 0;
   992 
   993     for (NSTouch* touch in touches) {
   994         if ([touch phase] != NSTouchPhaseBegan) {
   995             existingTouchCount++;
   996         }
   997     }
   998     if (existingTouchCount == 0) {
   999         SDL_TouchID touchID = (SDL_TouchID)(intptr_t)[[touches anyObject] device];
  1000         int numFingers = SDL_GetNumTouchFingers(touchID);
  1001         DLog("Reset Lost Fingers: %d", numFingers);
  1002         for (--numFingers; numFingers >= 0; --numFingers) {
  1003             SDL_Finger* finger = SDL_GetTouchFinger(touchID, numFingers);
  1004             SDL_SendTouch(touchID, finger->id, SDL_FALSE, 0, 0, 0);
  1005         }
  1006     }
  1007 
  1008     DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount);
  1009     [self handleTouches:NSTouchPhaseBegan withEvent:theEvent];
  1010 }
  1011 
  1012 - (void)touchesMovedWithEvent:(NSEvent *) theEvent
  1013 {
  1014     [self handleTouches:NSTouchPhaseMoved withEvent:theEvent];
  1015 }
  1016 
  1017 - (void)touchesEndedWithEvent:(NSEvent *) theEvent
  1018 {
  1019     [self handleTouches:NSTouchPhaseEnded withEvent:theEvent];
  1020 }
  1021 
  1022 - (void)touchesCancelledWithEvent:(NSEvent *) theEvent
  1023 {
  1024     [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
  1025 }
  1026 
  1027 - (void)handleTouches:(NSTouchPhase) phase withEvent:(NSEvent *) theEvent
  1028 {
  1029     NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];
  1030 
  1031     for (NSTouch *touch in touches) {
  1032         const SDL_TouchID touchId = (SDL_TouchID)(intptr_t)[touch device];
  1033         if (SDL_AddTouch(touchId, "") < 0) {
  1034             return;
  1035         }
  1036 
  1037         const SDL_FingerID fingerId = (SDL_FingerID)(intptr_t)[touch identity];
  1038         float x = [touch normalizedPosition].x;
  1039         float y = [touch normalizedPosition].y;
  1040         /* Make the origin the upper left instead of the lower left */
  1041         y = 1.0f - y;
  1042 
  1043         switch (phase) {
  1044         case NSTouchPhaseBegan:
  1045             SDL_SendTouch(touchId, fingerId, SDL_TRUE, x, y, 1.0f);
  1046             break;
  1047         case NSTouchPhaseEnded:
  1048         case NSTouchPhaseCancelled:
  1049             SDL_SendTouch(touchId, fingerId, SDL_FALSE, x, y, 1.0f);
  1050             break;
  1051         case NSTouchPhaseMoved:
  1052             SDL_SendTouchMotion(touchId, fingerId, x, y, 1.0f);
  1053             break;
  1054         default:
  1055             break;
  1056         }
  1057     }
  1058 }
  1059 
  1060 @end
  1061 
  1062 @interface SDLView : NSView {
  1063     SDL_Window *_sdlWindow;
  1064 }
  1065 
  1066 - (void)setSDLWindow:(SDL_Window*)window;
  1067 
  1068 /* The default implementation doesn't pass rightMouseDown to responder chain */
  1069 - (void)rightMouseDown:(NSEvent *)theEvent;
  1070 - (BOOL)mouseDownCanMoveWindow;
  1071 - (void)drawRect:(NSRect)dirtyRect;
  1072 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent;
  1073 @end
  1074 
  1075 @implementation SDLView
  1076 - (void)setSDLWindow:(SDL_Window*)window
  1077 {
  1078     _sdlWindow = window;
  1079 }
  1080 
  1081 - (void)drawRect:(NSRect)dirtyRect
  1082 {
  1083     SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
  1084 }
  1085 
  1086 - (void)rightMouseDown:(NSEvent *)theEvent
  1087 {
  1088     [[self nextResponder] rightMouseDown:theEvent];
  1089 }
  1090 
  1091 - (BOOL)mouseDownCanMoveWindow
  1092 {
  1093     /* Always say YES, but this doesn't do anything until we call
  1094        -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
  1095        during mouse events when we're using a drag area. */
  1096     return YES;
  1097 }
  1098 
  1099 - (void)resetCursorRects
  1100 {
  1101     [super resetCursorRects];
  1102     SDL_Mouse *mouse = SDL_GetMouse();
  1103 
  1104     if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) {
  1105         [self addCursorRect:[self bounds]
  1106                      cursor:mouse->cur_cursor->driverdata];
  1107     } else {
  1108         [self addCursorRect:[self bounds]
  1109                      cursor:[NSCursor invisibleCursor]];
  1110     }
  1111 }
  1112 
  1113 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
  1114 {
  1115     const char *hint = SDL_GetHint(SDL_HINT_MAC_MOUSE_FOCUS_CLICKTHROUGH);
  1116     return hint && *hint != '0';
  1117 }
  1118 @end
  1119 
  1120 static int
  1121 SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, SDL_bool created)
  1122 { @autoreleasepool
  1123 {
  1124     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1125     SDL_WindowData *data;
  1126 
  1127     /* Allocate the window data */
  1128     window->driverdata = data = (SDL_WindowData *) SDL_calloc(1, sizeof(*data));
  1129     if (!data) {
  1130         return SDL_OutOfMemory();
  1131     }
  1132     data->window = window;
  1133     data->nswindow = nswindow;
  1134     data->created = created;
  1135     data->videodata = videodata;
  1136     data->nscontexts = [[NSMutableArray alloc] init];
  1137 
  1138     /* Create an event listener for the window */
  1139     data->listener = [[Cocoa_WindowListener alloc] init];
  1140 
  1141     /* Fill in the SDL window with the window data */
  1142     {
  1143         NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1144         ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1145         window->x = (int)rect.origin.x;
  1146         window->y = (int)rect.origin.y;
  1147         window->w = (int)rect.size.width;
  1148         window->h = (int)rect.size.height;
  1149     }
  1150 
  1151     /* Set up the listener after we create the view */
  1152     [data->listener listen:data];
  1153 
  1154     if ([nswindow isVisible]) {
  1155         window->flags |= SDL_WINDOW_SHOWN;
  1156     } else {
  1157         window->flags &= ~SDL_WINDOW_SHOWN;
  1158     }
  1159 
  1160     {
  1161         unsigned long style = [nswindow styleMask];
  1162 
  1163         if (style == NSBorderlessWindowMask) {
  1164             window->flags |= SDL_WINDOW_BORDERLESS;
  1165         } else {
  1166             window->flags &= ~SDL_WINDOW_BORDERLESS;
  1167         }
  1168         if (style & NSResizableWindowMask) {
  1169             window->flags |= SDL_WINDOW_RESIZABLE;
  1170         } else {
  1171             window->flags &= ~SDL_WINDOW_RESIZABLE;
  1172         }
  1173     }
  1174 
  1175     /* isZoomed always returns true if the window is not resizable */
  1176     if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1177         window->flags |= SDL_WINDOW_MAXIMIZED;
  1178     } else {
  1179         window->flags &= ~SDL_WINDOW_MAXIMIZED;
  1180     }
  1181 
  1182     if ([nswindow isMiniaturized]) {
  1183         window->flags |= SDL_WINDOW_MINIMIZED;
  1184     } else {
  1185         window->flags &= ~SDL_WINDOW_MINIMIZED;
  1186     }
  1187 
  1188     if ([nswindow isKeyWindow]) {
  1189         window->flags |= SDL_WINDOW_INPUT_FOCUS;
  1190         SDL_SetKeyboardFocus(data->window);
  1191     }
  1192 
  1193     /* Prevents the window's "window device" from being destroyed when it is
  1194      * hidden. See http://www.mikeash.com/pyblog/nsopenglcontext-and-one-shot.html
  1195      */
  1196     [nswindow setOneShot:NO];
  1197 
  1198     /* All done! */
  1199     window->driverdata = data;
  1200     return 0;
  1201 }}
  1202 
  1203 int
  1204 Cocoa_CreateWindow(_THIS, SDL_Window * window)
  1205 { @autoreleasepool
  1206 {
  1207     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1208     NSWindow *nswindow;
  1209     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1210     NSRect rect;
  1211     SDL_Rect bounds;
  1212     NSUInteger style;
  1213     NSArray *screens = [NSScreen screens];
  1214 
  1215     Cocoa_GetDisplayBounds(_this, display, &bounds);
  1216     rect.origin.x = window->x;
  1217     rect.origin.y = window->y;
  1218     rect.size.width = window->w;
  1219     rect.size.height = window->h;
  1220     ConvertNSRect([screens objectAtIndex:0], (window->flags & FULLSCREEN_MASK), &rect);
  1221 
  1222     style = GetWindowStyle(window);
  1223 
  1224     /* Figure out which screen to place this window */
  1225     NSScreen *screen = nil;
  1226     for (NSScreen *candidate in screens) {
  1227         NSRect screenRect = [candidate frame];
  1228         if (rect.origin.x >= screenRect.origin.x &&
  1229             rect.origin.x < screenRect.origin.x + screenRect.size.width &&
  1230             rect.origin.y >= screenRect.origin.y &&
  1231             rect.origin.y < screenRect.origin.y + screenRect.size.height) {
  1232             screen = candidate;
  1233             rect.origin.x -= screenRect.origin.x;
  1234             rect.origin.y -= screenRect.origin.y;
  1235         }
  1236     }
  1237 
  1238     @try {
  1239         nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
  1240     }
  1241     @catch (NSException *e) {
  1242         return SDL_SetError("%s", [[e reason] UTF8String]);
  1243     }
  1244     [nswindow setBackgroundColor:[NSColor blackColor]];
  1245 
  1246     if (videodata->allow_spaces) {
  1247         SDL_assert(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6);
  1248         SDL_assert([nswindow respondsToSelector:@selector(toggleFullScreen:)]);
  1249         /* we put FULLSCREEN_DESKTOP windows in their own Space, without a toggle button or menubar, later */
  1250         if (window->flags & SDL_WINDOW_RESIZABLE) {
  1251             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
  1252             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
  1253         }
  1254     }
  1255 
  1256     /* Create a default view for this window */
  1257     rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1258     SDLView *contentView = [[SDLView alloc] initWithFrame:rect];
  1259     [contentView setSDLWindow:window];
  1260 
  1261     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
  1262         if ([contentView respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
  1263             [contentView setWantsBestResolutionOpenGLSurface:YES];
  1264         }
  1265     }
  1266 
  1267     [nswindow setContentView: contentView];
  1268     [contentView release];
  1269 
  1270     /* Allow files and folders to be dragged onto the window by users */
  1271     [nswindow registerForDraggedTypes:[NSArray arrayWithObject:(NSString *)kUTTypeFileURL]];
  1272 
  1273     if (SetupWindowData(_this, window, nswindow, SDL_TRUE) < 0) {
  1274         [nswindow release];
  1275         return -1;
  1276     }
  1277     return 0;
  1278 }}
  1279 
  1280 int
  1281 Cocoa_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
  1282 { @autoreleasepool
  1283 {
  1284     NSWindow *nswindow = (NSWindow *) data;
  1285     NSString *title;
  1286 
  1287     /* Query the title from the existing window */
  1288     title = [nswindow title];
  1289     if (title) {
  1290         window->title = SDL_strdup([title UTF8String]);
  1291     }
  1292 
  1293     return SetupWindowData(_this, window, nswindow, SDL_FALSE);
  1294 }}
  1295 
  1296 void
  1297 Cocoa_SetWindowTitle(_THIS, SDL_Window * window)
  1298 { @autoreleasepool
  1299 {
  1300     const char *title = window->title ? window->title : "";
  1301     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1302     NSString *string = [[NSString alloc] initWithUTF8String:title];
  1303     [nswindow setTitle:string];
  1304     [string release];
  1305 }}
  1306 
  1307 void
  1308 Cocoa_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
  1309 { @autoreleasepool
  1310 {
  1311     NSImage *nsimage = Cocoa_CreateImage(icon);
  1312 
  1313     if (nsimage) {
  1314         [NSApp setApplicationIconImage:nsimage];
  1315     }
  1316 }}
  1317 
  1318 void
  1319 Cocoa_SetWindowPosition(_THIS, SDL_Window * window)
  1320 { @autoreleasepool
  1321 {
  1322     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1323     NSWindow *nswindow = windata->nswindow;
  1324     NSRect rect;
  1325     Uint32 moveHack;
  1326 
  1327     rect.origin.x = window->x;
  1328     rect.origin.y = window->y;
  1329     rect.size.width = window->w;
  1330     rect.size.height = window->h;
  1331     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1332 
  1333     moveHack = s_moveHack;
  1334     s_moveHack = 0;
  1335     [nswindow setFrameOrigin:rect.origin];
  1336     s_moveHack = moveHack;
  1337 
  1338     ScheduleContextUpdates(windata);
  1339 }}
  1340 
  1341 void
  1342 Cocoa_SetWindowSize(_THIS, SDL_Window * window)
  1343 { @autoreleasepool
  1344 {
  1345     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1346     NSWindow *nswindow = windata->nswindow;
  1347     NSRect rect;
  1348     Uint32 moveHack;
  1349 
  1350     /* Cocoa will resize the window from the bottom-left rather than the
  1351      * top-left when -[nswindow setContentSize:] is used, so we must set the
  1352      * entire frame based on the new size, in order to preserve the position.
  1353      */
  1354     rect.origin.x = window->x;
  1355     rect.origin.y = window->y;
  1356     rect.size.width = window->w;
  1357     rect.size.height = window->h;
  1358     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1359 
  1360     moveHack = s_moveHack;
  1361     s_moveHack = 0;
  1362     [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES];
  1363     s_moveHack = moveHack;
  1364 
  1365     ScheduleContextUpdates(windata);
  1366 }}
  1367 
  1368 void
  1369 Cocoa_SetWindowMinimumSize(_THIS, SDL_Window * window)
  1370 { @autoreleasepool
  1371 {
  1372     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1373 
  1374     NSSize minSize;
  1375     minSize.width = window->min_w;
  1376     minSize.height = window->min_h;
  1377 
  1378     [windata->nswindow setContentMinSize:minSize];
  1379 }}
  1380 
  1381 void
  1382 Cocoa_SetWindowMaximumSize(_THIS, SDL_Window * window)
  1383 { @autoreleasepool
  1384 {
  1385     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1386 
  1387     NSSize maxSize;
  1388     maxSize.width = window->max_w;
  1389     maxSize.height = window->max_h;
  1390 
  1391     [windata->nswindow setContentMaxSize:maxSize];
  1392 }}
  1393 
  1394 void
  1395 Cocoa_ShowWindow(_THIS, SDL_Window * window)
  1396 { @autoreleasepool
  1397 {
  1398     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1399     NSWindow *nswindow = windowData->nswindow;
  1400 
  1401     if (![nswindow isMiniaturized]) {
  1402         [windowData->listener pauseVisibleObservation];
  1403         [nswindow makeKeyAndOrderFront:nil];
  1404         [windowData->listener resumeVisibleObservation];
  1405     }
  1406 }}
  1407 
  1408 void
  1409 Cocoa_HideWindow(_THIS, SDL_Window * window)
  1410 { @autoreleasepool
  1411 {
  1412     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1413 
  1414     [nswindow orderOut:nil];
  1415 }}
  1416 
  1417 void
  1418 Cocoa_RaiseWindow(_THIS, SDL_Window * window)
  1419 { @autoreleasepool
  1420 {
  1421     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1422     NSWindow *nswindow = windowData->nswindow;
  1423 
  1424     /* makeKeyAndOrderFront: has the side-effect of deminiaturizing and showing
  1425        a minimized or hidden window, so check for that before showing it.
  1426      */
  1427     [windowData->listener pauseVisibleObservation];
  1428     if (![nswindow isMiniaturized] && [nswindow isVisible]) {
  1429         [NSApp activateIgnoringOtherApps:YES];
  1430         [nswindow makeKeyAndOrderFront:nil];
  1431     }
  1432     [windowData->listener resumeVisibleObservation];
  1433 }}
  1434 
  1435 void
  1436 Cocoa_MaximizeWindow(_THIS, SDL_Window * window)
  1437 { @autoreleasepool
  1438 {
  1439     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1440     NSWindow *nswindow = windata->nswindow;
  1441 
  1442     [nswindow zoom:nil];
  1443 
  1444     ScheduleContextUpdates(windata);
  1445 }}
  1446 
  1447 void
  1448 Cocoa_MinimizeWindow(_THIS, SDL_Window * window)
  1449 { @autoreleasepool
  1450 {
  1451     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1452     NSWindow *nswindow = data->nswindow;
  1453 
  1454     if ([data->listener isInFullscreenSpaceTransition]) {
  1455         [data->listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
  1456     } else {
  1457         [nswindow miniaturize:nil];
  1458     }
  1459 }}
  1460 
  1461 void
  1462 Cocoa_RestoreWindow(_THIS, SDL_Window * window)
  1463 { @autoreleasepool
  1464 {
  1465     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1466 
  1467     if ([nswindow isMiniaturized]) {
  1468         [nswindow deminiaturize:nil];
  1469     } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1470         [nswindow zoom:nil];
  1471     }
  1472 }}
  1473 
  1474 void
  1475 Cocoa_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
  1476 { @autoreleasepool
  1477 {
  1478     if (SetWindowStyle(window, GetWindowStyle(window))) {
  1479         if (bordered) {
  1480             Cocoa_SetWindowTitle(_this, window);  /* this got blanked out. */
  1481         }
  1482     }
  1483 }}
  1484 
  1485 
  1486 void
  1487 Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
  1488 { @autoreleasepool
  1489 {
  1490     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1491     NSWindow *nswindow = data->nswindow;
  1492     NSRect rect;
  1493 
  1494     /* The view responder chain gets messed with during setStyleMask */
  1495     if ([[nswindow contentView] nextResponder] == data->listener) {
  1496         [[nswindow contentView] setNextResponder:nil];
  1497     }
  1498 
  1499     if (fullscreen) {
  1500         SDL_Rect bounds;
  1501 
  1502         Cocoa_GetDisplayBounds(_this, display, &bounds);
  1503         rect.origin.x = bounds.x;
  1504         rect.origin.y = bounds.y;
  1505         rect.size.width = bounds.w;
  1506         rect.size.height = bounds.h;
  1507         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1508 
  1509         /* Hack to fix origin on Mac OS X 10.4 */
  1510         NSRect screenRect = [[nswindow screen] frame];
  1511         if (screenRect.size.height >= 1.0f) {
  1512             rect.origin.y += (screenRect.size.height - rect.size.height);
  1513         }
  1514 
  1515         [nswindow setStyleMask:NSBorderlessWindowMask];
  1516     } else {
  1517         rect.origin.x = window->windowed.x;
  1518         rect.origin.y = window->windowed.y;
  1519         rect.size.width = window->windowed.w;
  1520         rect.size.height = window->windowed.h;
  1521         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1522 
  1523         [nswindow setStyleMask:GetWindowStyle(window)];
  1524 
  1525         /* Hack to restore window decorations on Mac OS X 10.10 */
  1526         NSRect frameRect = [nswindow frame];
  1527         [nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
  1528         [nswindow setFrame:frameRect display:NO];
  1529     }
  1530 
  1531     /* The view responder chain gets messed with during setStyleMask */
  1532     if ([[nswindow contentView] nextResponder] != data->listener) {
  1533         [[nswindow contentView] setNextResponder:data->listener];
  1534     }
  1535 
  1536     s_moveHack = 0;
  1537     [nswindow setContentSize:rect.size];
  1538     [nswindow setFrameOrigin:rect.origin];
  1539     s_moveHack = SDL_GetTicks();
  1540 
  1541     /* When the window style changes the title is cleared */
  1542     if (!fullscreen) {
  1543         Cocoa_SetWindowTitle(_this, window);
  1544     }
  1545 
  1546     if (SDL_ShouldAllowTopmost() && fullscreen) {
  1547         /* OpenGL is rendering to the window, so make it visible! */
  1548         [nswindow setLevel:CGShieldingWindowLevel()];
  1549     } else {
  1550         [nswindow setLevel:kCGNormalWindowLevel];
  1551     }
  1552 
  1553     if ([nswindow isVisible] || fullscreen) {
  1554         [data->listener pauseVisibleObservation];
  1555         [nswindow makeKeyAndOrderFront:nil];
  1556         [data->listener resumeVisibleObservation];
  1557     }
  1558 
  1559     ScheduleContextUpdates(data);
  1560 }}
  1561 
  1562 int
  1563 Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
  1564 {
  1565     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1566     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1567     const uint32_t tableSize = 256;
  1568     CGGammaValue redTable[tableSize];
  1569     CGGammaValue greenTable[tableSize];
  1570     CGGammaValue blueTable[tableSize];
  1571     uint32_t i;
  1572     float inv65535 = 1.0f / 65535.0f;
  1573 
  1574     /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
  1575     for (i = 0; i < 256; i++) {
  1576         redTable[i] = ramp[0*256+i] * inv65535;
  1577         greenTable[i] = ramp[1*256+i] * inv65535;
  1578         blueTable[i] = ramp[2*256+i] * inv65535;
  1579     }
  1580 
  1581     if (CGSetDisplayTransferByTable(display_id, tableSize,
  1582                                     redTable, greenTable, blueTable) != CGDisplayNoErr) {
  1583         return SDL_SetError("CGSetDisplayTransferByTable()");
  1584     }
  1585     return 0;
  1586 }
  1587 
  1588 int
  1589 Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
  1590 {
  1591     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1592     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1593     const uint32_t tableSize = 256;
  1594     CGGammaValue redTable[tableSize];
  1595     CGGammaValue greenTable[tableSize];
  1596     CGGammaValue blueTable[tableSize];
  1597     uint32_t i, tableCopied;
  1598 
  1599     if (CGGetDisplayTransferByTable(display_id, tableSize,
  1600                                     redTable, greenTable, blueTable, &tableCopied) != CGDisplayNoErr) {
  1601         return SDL_SetError("CGGetDisplayTransferByTable()");
  1602     }
  1603 
  1604     for (i = 0; i < tableCopied; i++) {
  1605         ramp[0*256+i] = (Uint16)(redTable[i] * 65535.0f);
  1606         ramp[1*256+i] = (Uint16)(greenTable[i] * 65535.0f);
  1607         ramp[2*256+i] = (Uint16)(blueTable[i] * 65535.0f);
  1608     }
  1609     return 0;
  1610 }
  1611 
  1612 void
  1613 Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
  1614 {
  1615     /* Move the cursor to the nearest point in the window */
  1616     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1617     if (grabbed && data && ![data->listener isMoving]) {
  1618         int x, y;
  1619         CGPoint cgpoint;
  1620 
  1621         SDL_GetMouseState(&x, &y);
  1622         cgpoint.x = window->x + x;
  1623         cgpoint.y = window->y + y;
  1624 
  1625         Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
  1626 
  1627         DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
  1628         CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
  1629     }
  1630 
  1631     if ( data && (window->flags & SDL_WINDOW_FULLSCREEN) ) {
  1632         if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS)
  1633             && ![data->listener isInFullscreenSpace]) {
  1634             /* OpenGL is rendering to the window, so make it visible! */
  1635             /* Doing this in 10.11 while in a Space breaks things (bug #3152) */
  1636             [data->nswindow setLevel:CGShieldingWindowLevel()];
  1637         } else {
  1638             [data->nswindow setLevel:kCGNormalWindowLevel];
  1639         }
  1640     }
  1641 }
  1642 
  1643 void
  1644 Cocoa_DestroyWindow(_THIS, SDL_Window * window)
  1645 { @autoreleasepool
  1646 {
  1647     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1648 
  1649     if (data) {
  1650         if ([data->listener isInFullscreenSpace]) {
  1651             [NSMenu setMenuBarVisible:YES];
  1652         }
  1653         [data->listener close];
  1654         [data->listener release];
  1655         if (data->created) {
  1656             [data->nswindow close];
  1657         }
  1658 
  1659         NSArray *contexts = [[data->nscontexts copy] autorelease];
  1660         for (SDLOpenGLContext *context in contexts) {
  1661             /* Calling setWindow:NULL causes the context to remove itself from the context list. */            
  1662             [context setWindow:NULL];
  1663         }
  1664         [data->nscontexts release];
  1665 
  1666         SDL_free(data);
  1667     }
  1668     window->driverdata = NULL;
  1669 }}
  1670 
  1671 SDL_bool
  1672 Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
  1673 {
  1674     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1675 
  1676     if (info->version.major <= SDL_MAJOR_VERSION) {
  1677         info->subsystem = SDL_SYSWM_COCOA;
  1678         info->info.cocoa.window = nswindow;
  1679         return SDL_TRUE;
  1680     } else {
  1681         SDL_SetError("Application not compiled with SDL %d.%d\n",
  1682                      SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
  1683         return SDL_FALSE;
  1684     }
  1685 }
  1686 
  1687 SDL_bool
  1688 Cocoa_IsWindowInFullscreenSpace(SDL_Window * window)
  1689 {
  1690     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1691 
  1692     if ([data->listener isInFullscreenSpace]) {
  1693         return SDL_TRUE;
  1694     } else {
  1695         return SDL_FALSE;
  1696     }
  1697 }
  1698 
  1699 SDL_bool
  1700 Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state)
  1701 { @autoreleasepool
  1702 {
  1703     SDL_bool succeeded = SDL_FALSE;
  1704     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1705 
  1706     if ([data->listener setFullscreenSpace:(state ? YES : NO)]) {
  1707         const int maxattempts = 3;
  1708         int attempt = 0;
  1709         while (++attempt <= maxattempts) {
  1710             /* Wait for the transition to complete, so application changes
  1711              take effect properly (e.g. setting the window size, etc.)
  1712              */
  1713             const int limit = 10000;
  1714             int count = 0;
  1715             while ([data->listener isInFullscreenSpaceTransition]) {
  1716                 if ( ++count == limit ) {
  1717                     /* Uh oh, transition isn't completing. Should we assert? */
  1718                     break;
  1719                 }
  1720                 SDL_Delay(1);
  1721                 SDL_PumpEvents();
  1722             }
  1723             if ([data->listener isInFullscreenSpace] == (state ? YES : NO))
  1724                 break;
  1725             /* Try again, the last attempt was interrupted by user gestures */
  1726             if (![data->listener setFullscreenSpace:(state ? YES : NO)])
  1727                 break; /* ??? */
  1728         }
  1729         /* Return TRUE to prevent non-space fullscreen logic from running */
  1730         succeeded = SDL_TRUE;
  1731     }
  1732 
  1733     return succeeded;
  1734 }}
  1735 
  1736 int
  1737 Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
  1738 {
  1739     return 0;  /* just succeed, the real work is done elsewhere. */
  1740 }
  1741 
  1742 int
  1743 Cocoa_SetWindowOpacity(_THIS, SDL_Window * window, float opacity)
  1744 {
  1745     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1746     [data->nswindow setAlphaValue:opacity];
  1747     return 0;
  1748 }
  1749 
  1750 #endif /* SDL_VIDEO_DRIVER_COCOA */
  1751 
  1752 /* vi: set ts=4 sw=4 expandtab: */