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