src/video/cocoa/SDL_cocoawindow.m
author Sam Lantinga <slouken@libsdl.org>
Mon, 09 Nov 2015 08:54:56 -0800
changeset 9905 fcf85090f816
parent 9904 e9b49510e51b
child 9907 2048ad756970
permissions -rw-r--r--
more SDL fullscreen state tracking fixes, don't update fullscreen flags on failure to change fullscreen state
     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     [self windowDidExitFullScreen:nil];
   651 }
   652 
   653 - (void)windowDidEnterFullScreen:(NSNotification *)aNotification
   654 {
   655     SDL_Window *window = _data->window;
   656 
   657     inFullscreenTransition = NO;
   658 
   659     if (pendingWindowOperation == PENDING_OPERATION_LEAVE_FULLSCREEN) {
   660         pendingWindowOperation = PENDING_OPERATION_NONE;
   661         [self setFullscreenSpace:NO];
   662     } else {
   663         if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   664             [NSMenu setMenuBarVisible:NO];
   665         }
   666 
   667         pendingWindowOperation = PENDING_OPERATION_NONE;
   668         /* Force the size change event in case it was delivered earlier
   669            while the window was still animating into place.
   670          */
   671         window->w = 0;
   672         window->h = 0;
   673         [self windowDidResize:aNotification];
   674     }
   675 }
   676 
   677 - (void)windowWillExitFullScreen:(NSNotification *)aNotification
   678 {
   679     SDL_Window *window = _data->window;
   680 
   681     SetWindowStyle(window, GetWindowStyle(window));
   682 
   683     isFullscreenSpace = NO;
   684     inFullscreenTransition = YES;
   685 }
   686 
   687 - (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
   688 {
   689     SDL_Window *window = _data->window;
   690     
   691     SetWindowStyle(window, (NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask));
   692     
   693     isFullscreenSpace = YES;
   694     inFullscreenTransition = NO;
   695     
   696     [self windowDidEnterFullScreen:nil];
   697 }
   698 
   699 - (void)windowDidExitFullScreen:(NSNotification *)aNotification
   700 {
   701     SDL_Window *window = _data->window;
   702     NSWindow *nswindow = _data->nswindow;
   703 
   704     inFullscreenTransition = NO;
   705 
   706     [nswindow setLevel:kCGNormalWindowLevel];
   707 
   708     if (pendingWindowOperation == PENDING_OPERATION_ENTER_FULLSCREEN) {
   709         pendingWindowOperation = PENDING_OPERATION_NONE;
   710         [self setFullscreenSpace:YES];
   711     } else if (pendingWindowOperation == PENDING_OPERATION_MINIMIZE) {
   712         pendingWindowOperation = PENDING_OPERATION_NONE;
   713         [nswindow miniaturize:nil];
   714     } else {
   715         /* Adjust the fullscreen toggle button and readd menu now that we're here. */
   716         if (window->flags & SDL_WINDOW_RESIZABLE) {
   717             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
   718             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
   719         } else {
   720             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged];
   721         }
   722         [NSMenu setMenuBarVisible:YES];
   723 
   724         pendingWindowOperation = PENDING_OPERATION_NONE;
   725         /* Force the size change event in case it was delivered earlier
   726            while the window was still animating into place.
   727          */
   728         window->w = 0;
   729         window->h = 0;
   730         [self windowDidResize:aNotification];
   731 
   732         /* FIXME: Why does the window get hidden? */
   733         if (window->flags & SDL_WINDOW_SHOWN) {
   734             Cocoa_ShowWindow(SDL_GetVideoDevice(), window);
   735         }
   736     }
   737 }
   738 
   739 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
   740 {
   741     if ((_data->window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
   742         return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
   743     } else {
   744         return proposedOptions;
   745     }
   746 }
   747 
   748 
   749 /* We'll respond to key events by doing nothing so we don't beep.
   750  * We could handle key messages here, but we lose some in the NSApp dispatch,
   751  * where they get converted to action messages, etc.
   752  */
   753 - (void)flagsChanged:(NSEvent *)theEvent
   754 {
   755     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   756 }
   757 - (void)keyDown:(NSEvent *)theEvent
   758 {
   759     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   760 }
   761 - (void)keyUp:(NSEvent *)theEvent
   762 {
   763     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
   764 }
   765 
   766 /* We'll respond to selectors by doing nothing so we don't beep.
   767  * The escape key gets converted to a "cancel" selector, etc.
   768  */
   769 - (void)doCommandBySelector:(SEL)aSelector
   770 {
   771     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
   772 }
   773 
   774 - (BOOL)processHitTest:(NSEvent *)theEvent
   775 {
   776     SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
   777 
   778     if (_data->window->hit_test) {  /* if no hit-test, skip this. */
   779         const NSPoint location = [theEvent locationInWindow];
   780         const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
   781         const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
   782         if (rc == SDL_HITTEST_DRAGGABLE) {
   783             if (!isDragAreaRunning) {
   784                 isDragAreaRunning = YES;
   785                 [_data->nswindow setMovableByWindowBackground:YES];
   786             }
   787             return YES;  /* dragging! */
   788         }
   789     }
   790 
   791     if (isDragAreaRunning) {
   792         isDragAreaRunning = NO;
   793         [_data->nswindow setMovableByWindowBackground:NO];
   794         return YES;  /* was dragging, drop event. */
   795     }
   796 
   797     return NO;  /* not a special area, carry on. */
   798 }
   799 
   800 - (void)mouseDown:(NSEvent *)theEvent
   801 {
   802     int button;
   803 
   804     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   805     if ([theEvent window]) {
   806         const NSRect windowRect = [[[theEvent window] contentView] frame];
   807         if (!NSPointInRect([theEvent locationInWindow], windowRect)) {
   808             return;
   809         }
   810     }
   811 
   812     if ([self processHitTest:theEvent]) {
   813         return;  /* dragging, drop event. */
   814     }
   815 
   816     switch ([theEvent buttonNumber]) {
   817     case 0:
   818         if (([theEvent modifierFlags] & NSControlKeyMask) &&
   819 		    GetHintCtrlClickEmulateRightClick()) {
   820             wasCtrlLeft = YES;
   821             button = SDL_BUTTON_RIGHT;
   822         } else {
   823             wasCtrlLeft = NO;
   824             button = SDL_BUTTON_LEFT;
   825         }
   826         break;
   827     case 1:
   828         button = SDL_BUTTON_RIGHT;
   829         break;
   830     case 2:
   831         button = SDL_BUTTON_MIDDLE;
   832         break;
   833     default:
   834         button = [theEvent buttonNumber] + 1;
   835         break;
   836     }
   837     SDL_SendMouseButton(_data->window, 0, SDL_PRESSED, button);
   838 }
   839 
   840 - (void)rightMouseDown:(NSEvent *)theEvent
   841 {
   842     [self mouseDown:theEvent];
   843 }
   844 
   845 - (void)otherMouseDown:(NSEvent *)theEvent
   846 {
   847     [self mouseDown:theEvent];
   848 }
   849 
   850 - (void)mouseUp:(NSEvent *)theEvent
   851 {
   852     int button;
   853 
   854     if ([self processHitTest:theEvent]) {
   855         return;  /* stopped dragging, drop event. */
   856     }
   857 
   858     switch ([theEvent buttonNumber]) {
   859     case 0:
   860         if (wasCtrlLeft) {
   861             button = SDL_BUTTON_RIGHT;
   862             wasCtrlLeft = NO;
   863         } else {
   864             button = SDL_BUTTON_LEFT;
   865         }
   866         break;
   867     case 1:
   868         button = SDL_BUTTON_RIGHT;
   869         break;
   870     case 2:
   871         button = SDL_BUTTON_MIDDLE;
   872         break;
   873     default:
   874         button = [theEvent buttonNumber] + 1;
   875         break;
   876     }
   877     SDL_SendMouseButton(_data->window, 0, SDL_RELEASED, button);
   878 }
   879 
   880 - (void)rightMouseUp:(NSEvent *)theEvent
   881 {
   882     [self mouseUp:theEvent];
   883 }
   884 
   885 - (void)otherMouseUp:(NSEvent *)theEvent
   886 {
   887     [self mouseUp:theEvent];
   888 }
   889 
   890 - (void)mouseMoved:(NSEvent *)theEvent
   891 {
   892     SDL_Mouse *mouse = SDL_GetMouse();
   893     SDL_Window *window = _data->window;
   894     NSPoint point;
   895     int x, y;
   896 
   897     if ([self processHitTest:theEvent]) {
   898         return;  /* dragging, drop event. */
   899     }
   900 
   901     if (mouse->relative_mode) {
   902         return;
   903     }
   904 
   905     point = [theEvent locationInWindow];
   906     x = (int)point.x;
   907     y = (int)(window->h - point.y);
   908 
   909     if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
   910         if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
   911             if (x < 0) {
   912                 x = 0;
   913             } else if (x >= window->w) {
   914                 x = window->w - 1;
   915             }
   916             if (y < 0) {
   917                 y = 0;
   918             } else if (y >= window->h) {
   919                 y = window->h - 1;
   920             }
   921 
   922 #if !SDL_MAC_NO_SANDBOX
   923             CGPoint cgpoint;
   924 
   925             /* When SDL_MAC_NO_SANDBOX is set, this is handled by
   926              * SDL_cocoamousetap.m.
   927              */
   928 
   929             cgpoint.x = window->x + x;
   930             cgpoint.y = window->y + y;
   931 
   932             /* According to the docs, this was deprecated in 10.6, but it's still
   933              * around. The substitute requires a CGEventSource, but I'm not entirely
   934              * sure how we'd procure the right one for this event.
   935              */
   936             CGSetLocalEventsSuppressionInterval(0.0);
   937             CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
   938             CGSetLocalEventsSuppressionInterval(0.25);
   939 
   940             Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
   941 #endif
   942         }
   943     }
   944     SDL_SendMouseMotion(window, 0, 0, x, y);
   945 }
   946 
   947 - (void)mouseDragged:(NSEvent *)theEvent
   948 {
   949     [self mouseMoved:theEvent];
   950 }
   951 
   952 - (void)rightMouseDragged:(NSEvent *)theEvent
   953 {
   954     [self mouseMoved:theEvent];
   955 }
   956 
   957 - (void)otherMouseDragged:(NSEvent *)theEvent
   958 {
   959     [self mouseMoved:theEvent];
   960 }
   961 
   962 - (void)scrollWheel:(NSEvent *)theEvent
   963 {
   964     Cocoa_HandleMouseWheel(_data->window, theEvent);
   965 }
   966 
   967 - (void)touchesBeganWithEvent:(NSEvent *) theEvent
   968 {
   969     NSSet *touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil];
   970     int existingTouchCount = 0;
   971 
   972     for (NSTouch* touch in touches) {
   973         if ([touch phase] != NSTouchPhaseBegan) {
   974             existingTouchCount++;
   975         }
   976     }
   977     if (existingTouchCount == 0) {
   978         SDL_TouchID touchID = (SDL_TouchID)(intptr_t)[[touches anyObject] device];
   979         int numFingers = SDL_GetNumTouchFingers(touchID);
   980         DLog("Reset Lost Fingers: %d", numFingers);
   981         for (--numFingers; numFingers >= 0; --numFingers) {
   982             SDL_Finger* finger = SDL_GetTouchFinger(touchID, numFingers);
   983             SDL_SendTouch(touchID, finger->id, SDL_FALSE, 0, 0, 0);
   984         }
   985     }
   986 
   987     DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount);
   988     [self handleTouches:NSTouchPhaseBegan withEvent:theEvent];
   989 }
   990 
   991 - (void)touchesMovedWithEvent:(NSEvent *) theEvent
   992 {
   993     [self handleTouches:NSTouchPhaseMoved withEvent:theEvent];
   994 }
   995 
   996 - (void)touchesEndedWithEvent:(NSEvent *) theEvent
   997 {
   998     [self handleTouches:NSTouchPhaseEnded withEvent:theEvent];
   999 }
  1000 
  1001 - (void)touchesCancelledWithEvent:(NSEvent *) theEvent
  1002 {
  1003     [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
  1004 }
  1005 
  1006 - (void)handleTouches:(NSTouchPhase) phase withEvent:(NSEvent *) theEvent
  1007 {
  1008     NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];
  1009 
  1010     for (NSTouch *touch in touches) {
  1011         const SDL_TouchID touchId = (SDL_TouchID)(intptr_t)[touch device];
  1012         if (SDL_AddTouch(touchId, "") < 0) {
  1013             return;
  1014         }
  1015 
  1016         const SDL_FingerID fingerId = (SDL_FingerID)(intptr_t)[touch identity];
  1017         float x = [touch normalizedPosition].x;
  1018         float y = [touch normalizedPosition].y;
  1019         /* Make the origin the upper left instead of the lower left */
  1020         y = 1.0f - y;
  1021 
  1022         switch (phase) {
  1023         case NSTouchPhaseBegan:
  1024             SDL_SendTouch(touchId, fingerId, SDL_TRUE, x, y, 1.0f);
  1025             break;
  1026         case NSTouchPhaseEnded:
  1027         case NSTouchPhaseCancelled:
  1028             SDL_SendTouch(touchId, fingerId, SDL_FALSE, x, y, 1.0f);
  1029             break;
  1030         case NSTouchPhaseMoved:
  1031             SDL_SendTouchMotion(touchId, fingerId, x, y, 1.0f);
  1032             break;
  1033         default:
  1034             break;
  1035         }
  1036     }
  1037 }
  1038 
  1039 @end
  1040 
  1041 @interface SDLView : NSView {
  1042     SDL_Window *_sdlWindow;
  1043 }
  1044 
  1045 - (void)setSDLWindow:(SDL_Window*)window;
  1046 
  1047 /* The default implementation doesn't pass rightMouseDown to responder chain */
  1048 - (void)rightMouseDown:(NSEvent *)theEvent;
  1049 - (BOOL)mouseDownCanMoveWindow;
  1050 - (void)drawRect:(NSRect)dirtyRect;
  1051 @end
  1052 
  1053 @implementation SDLView
  1054 - (void)setSDLWindow:(SDL_Window*)window
  1055 {
  1056     _sdlWindow = window;
  1057 }
  1058 
  1059 - (void)drawRect:(NSRect)dirtyRect
  1060 {
  1061     SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
  1062 }
  1063 
  1064 - (void)rightMouseDown:(NSEvent *)theEvent
  1065 {
  1066     [[self nextResponder] rightMouseDown:theEvent];
  1067 }
  1068 
  1069 - (BOOL)mouseDownCanMoveWindow
  1070 {
  1071     /* Always say YES, but this doesn't do anything until we call
  1072        -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
  1073        during mouse events when we're using a drag area. */
  1074     return YES;
  1075 }
  1076 
  1077 - (void)resetCursorRects
  1078 {
  1079     [super resetCursorRects];
  1080     SDL_Mouse *mouse = SDL_GetMouse();
  1081 
  1082     if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) {
  1083         [self addCursorRect:[self bounds]
  1084                      cursor:mouse->cur_cursor->driverdata];
  1085     } else {
  1086         [self addCursorRect:[self bounds]
  1087                      cursor:[NSCursor invisibleCursor]];
  1088     }
  1089 }
  1090 @end
  1091 
  1092 static int
  1093 SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, SDL_bool created)
  1094 { @autoreleasepool
  1095 {
  1096     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1097     SDL_WindowData *data;
  1098 
  1099     /* Allocate the window data */
  1100     window->driverdata = data = (SDL_WindowData *) SDL_calloc(1, sizeof(*data));
  1101     if (!data) {
  1102         return SDL_OutOfMemory();
  1103     }
  1104     data->window = window;
  1105     data->nswindow = nswindow;
  1106     data->created = created;
  1107     data->videodata = videodata;
  1108     data->nscontexts = [[NSMutableArray alloc] init];
  1109 
  1110     /* Create an event listener for the window */
  1111     data->listener = [[Cocoa_WindowListener alloc] init];
  1112 
  1113     /* Fill in the SDL window with the window data */
  1114     {
  1115         NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1116         ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1117         window->x = (int)rect.origin.x;
  1118         window->y = (int)rect.origin.y;
  1119         window->w = (int)rect.size.width;
  1120         window->h = (int)rect.size.height;
  1121     }
  1122 
  1123     /* Set up the listener after we create the view */
  1124     [data->listener listen:data];
  1125 
  1126     if ([nswindow isVisible]) {
  1127         window->flags |= SDL_WINDOW_SHOWN;
  1128     } else {
  1129         window->flags &= ~SDL_WINDOW_SHOWN;
  1130     }
  1131 
  1132     {
  1133         unsigned int style = [nswindow styleMask];
  1134 
  1135         if (style == NSBorderlessWindowMask) {
  1136             window->flags |= SDL_WINDOW_BORDERLESS;
  1137         } else {
  1138             window->flags &= ~SDL_WINDOW_BORDERLESS;
  1139         }
  1140         if (style & NSResizableWindowMask) {
  1141             window->flags |= SDL_WINDOW_RESIZABLE;
  1142         } else {
  1143             window->flags &= ~SDL_WINDOW_RESIZABLE;
  1144         }
  1145     }
  1146 
  1147     /* isZoomed always returns true if the window is not resizable */
  1148     if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1149         window->flags |= SDL_WINDOW_MAXIMIZED;
  1150     } else {
  1151         window->flags &= ~SDL_WINDOW_MAXIMIZED;
  1152     }
  1153 
  1154     if ([nswindow isMiniaturized]) {
  1155         window->flags |= SDL_WINDOW_MINIMIZED;
  1156     } else {
  1157         window->flags &= ~SDL_WINDOW_MINIMIZED;
  1158     }
  1159 
  1160     if ([nswindow isKeyWindow]) {
  1161         window->flags |= SDL_WINDOW_INPUT_FOCUS;
  1162         SDL_SetKeyboardFocus(data->window);
  1163     }
  1164 
  1165     /* Prevents the window's "window device" from being destroyed when it is
  1166      * hidden. See http://www.mikeash.com/pyblog/nsopenglcontext-and-one-shot.html
  1167      */
  1168     [nswindow setOneShot:NO];
  1169 
  1170     /* All done! */
  1171     window->driverdata = data;
  1172     return 0;
  1173 }}
  1174 
  1175 int
  1176 Cocoa_CreateWindow(_THIS, SDL_Window * window)
  1177 { @autoreleasepool
  1178 {
  1179     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
  1180     NSWindow *nswindow;
  1181     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1182     NSRect rect;
  1183     SDL_Rect bounds;
  1184     unsigned int style;
  1185     NSArray *screens = [NSScreen screens];
  1186 
  1187     Cocoa_GetDisplayBounds(_this, display, &bounds);
  1188     rect.origin.x = window->x;
  1189     rect.origin.y = window->y;
  1190     rect.size.width = window->w;
  1191     rect.size.height = window->h;
  1192     ConvertNSRect([screens objectAtIndex:0], (window->flags & FULLSCREEN_MASK), &rect);
  1193 
  1194     style = GetWindowStyle(window);
  1195 
  1196     /* Figure out which screen to place this window */
  1197     NSScreen *screen = nil;
  1198     for (NSScreen *candidate in screens) {
  1199         NSRect screenRect = [candidate frame];
  1200         if (rect.origin.x >= screenRect.origin.x &&
  1201             rect.origin.x < screenRect.origin.x + screenRect.size.width &&
  1202             rect.origin.y >= screenRect.origin.y &&
  1203             rect.origin.y < screenRect.origin.y + screenRect.size.height) {
  1204             screen = candidate;
  1205             rect.origin.x -= screenRect.origin.x;
  1206             rect.origin.y -= screenRect.origin.y;
  1207         }
  1208     }
  1209 
  1210     @try {
  1211         nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
  1212     }
  1213     @catch (NSException *e) {
  1214         return SDL_SetError("%s", [[e reason] UTF8String]);
  1215     }
  1216     [nswindow setBackgroundColor:[NSColor blackColor]];
  1217 
  1218     if (videodata->allow_spaces) {
  1219         SDL_assert(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6);
  1220         SDL_assert([nswindow respondsToSelector:@selector(toggleFullScreen:)]);
  1221         /* we put FULLSCREEN_DESKTOP windows in their own Space, without a toggle button or menubar, later */
  1222         if (window->flags & SDL_WINDOW_RESIZABLE) {
  1223             /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
  1224             [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
  1225         }
  1226     }
  1227 
  1228     /* Create a default view for this window */
  1229     rect = [nswindow contentRectForFrameRect:[nswindow frame]];
  1230     SDLView *contentView = [[SDLView alloc] initWithFrame:rect];
  1231     [contentView setSDLWindow:window];
  1232 
  1233     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
  1234         if ([contentView respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
  1235             [contentView setWantsBestResolutionOpenGLSurface:YES];
  1236         }
  1237     }
  1238 
  1239     [nswindow setContentView: contentView];
  1240     [contentView release];
  1241 
  1242     /* Allow files and folders to be dragged onto the window by users */
  1243     [nswindow registerForDraggedTypes:[NSArray arrayWithObject:(NSString *)kUTTypeFileURL]];
  1244 
  1245     if (SetupWindowData(_this, window, nswindow, SDL_TRUE) < 0) {
  1246         [nswindow release];
  1247         return -1;
  1248     }
  1249     return 0;
  1250 }}
  1251 
  1252 int
  1253 Cocoa_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
  1254 { @autoreleasepool
  1255 {
  1256     NSWindow *nswindow = (NSWindow *) data;
  1257     NSString *title;
  1258 
  1259     /* Query the title from the existing window */
  1260     title = [nswindow title];
  1261     if (title) {
  1262         window->title = SDL_strdup([title UTF8String]);
  1263     }
  1264 
  1265     return SetupWindowData(_this, window, nswindow, SDL_FALSE);
  1266 }}
  1267 
  1268 void
  1269 Cocoa_SetWindowTitle(_THIS, SDL_Window * window)
  1270 { @autoreleasepool
  1271 {
  1272     const char *title = window->title ? window->title : "";
  1273     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1274     NSString *string = [[NSString alloc] initWithUTF8String:title];
  1275     [nswindow setTitle:string];
  1276     [string release];
  1277 }}
  1278 
  1279 void
  1280 Cocoa_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
  1281 { @autoreleasepool
  1282 {
  1283     NSImage *nsimage = Cocoa_CreateImage(icon);
  1284 
  1285     if (nsimage) {
  1286         [NSApp setApplicationIconImage:nsimage];
  1287     }
  1288 }}
  1289 
  1290 void
  1291 Cocoa_SetWindowPosition(_THIS, SDL_Window * window)
  1292 { @autoreleasepool
  1293 {
  1294     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1295     NSWindow *nswindow = windata->nswindow;
  1296     NSRect rect;
  1297     Uint32 moveHack;
  1298 
  1299     rect.origin.x = window->x;
  1300     rect.origin.y = window->y;
  1301     rect.size.width = window->w;
  1302     rect.size.height = window->h;
  1303     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1304 
  1305     moveHack = s_moveHack;
  1306     s_moveHack = 0;
  1307     [nswindow setFrameOrigin:rect.origin];
  1308     s_moveHack = moveHack;
  1309 
  1310     ScheduleContextUpdates(windata);
  1311 }}
  1312 
  1313 void
  1314 Cocoa_SetWindowSize(_THIS, SDL_Window * window)
  1315 { @autoreleasepool
  1316 {
  1317     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1318     NSWindow *nswindow = windata->nswindow;
  1319     NSRect rect;
  1320     Uint32 moveHack;
  1321 
  1322     /* Cocoa will resize the window from the bottom-left rather than the
  1323      * top-left when -[nswindow setContentSize:] is used, so we must set the
  1324      * entire frame based on the new size, in order to preserve the position.
  1325      */
  1326     rect.origin.x = window->x;
  1327     rect.origin.y = window->y;
  1328     rect.size.width = window->w;
  1329     rect.size.height = window->h;
  1330     ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
  1331 
  1332     moveHack = s_moveHack;
  1333     s_moveHack = 0;
  1334     [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES];
  1335     s_moveHack = moveHack;
  1336 
  1337     ScheduleContextUpdates(windata);
  1338 }}
  1339 
  1340 void
  1341 Cocoa_SetWindowMinimumSize(_THIS, SDL_Window * window)
  1342 { @autoreleasepool
  1343 {
  1344     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1345 
  1346     NSSize minSize;
  1347     minSize.width = window->min_w;
  1348     minSize.height = window->min_h;
  1349 
  1350     [windata->nswindow setContentMinSize:minSize];
  1351 }}
  1352 
  1353 void
  1354 Cocoa_SetWindowMaximumSize(_THIS, SDL_Window * window)
  1355 { @autoreleasepool
  1356 {
  1357     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1358 
  1359     NSSize maxSize;
  1360     maxSize.width = window->max_w;
  1361     maxSize.height = window->max_h;
  1362 
  1363     [windata->nswindow setContentMaxSize:maxSize];
  1364 }}
  1365 
  1366 void
  1367 Cocoa_ShowWindow(_THIS, SDL_Window * window)
  1368 { @autoreleasepool
  1369 {
  1370     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1371     NSWindow *nswindow = windowData->nswindow;
  1372 
  1373     if (![nswindow isMiniaturized]) {
  1374         [windowData->listener pauseVisibleObservation];
  1375         [nswindow makeKeyAndOrderFront:nil];
  1376         [windowData->listener resumeVisibleObservation];
  1377     }
  1378 }}
  1379 
  1380 void
  1381 Cocoa_HideWindow(_THIS, SDL_Window * window)
  1382 { @autoreleasepool
  1383 {
  1384     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1385 
  1386     [nswindow orderOut:nil];
  1387 }}
  1388 
  1389 void
  1390 Cocoa_RaiseWindow(_THIS, SDL_Window * window)
  1391 { @autoreleasepool
  1392 {
  1393     SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
  1394     NSWindow *nswindow = windowData->nswindow;
  1395 
  1396     /* makeKeyAndOrderFront: has the side-effect of deminiaturizing and showing
  1397        a minimized or hidden window, so check for that before showing it.
  1398      */
  1399     [windowData->listener pauseVisibleObservation];
  1400     if (![nswindow isMiniaturized] && [nswindow isVisible]) {
  1401         [NSApp activateIgnoringOtherApps:YES];
  1402         [nswindow makeKeyAndOrderFront:nil];
  1403     }
  1404     [windowData->listener resumeVisibleObservation];
  1405 }}
  1406 
  1407 void
  1408 Cocoa_MaximizeWindow(_THIS, SDL_Window * window)
  1409 { @autoreleasepool
  1410 {
  1411     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
  1412     NSWindow *nswindow = windata->nswindow;
  1413 
  1414     [nswindow zoom:nil];
  1415 
  1416     ScheduleContextUpdates(windata);
  1417 }}
  1418 
  1419 void
  1420 Cocoa_MinimizeWindow(_THIS, SDL_Window * window)
  1421 { @autoreleasepool
  1422 {
  1423     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1424     NSWindow *nswindow = data->nswindow;
  1425 
  1426     if ([data->listener isInFullscreenSpaceTransition]) {
  1427         [data->listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
  1428     } else {
  1429         [nswindow miniaturize:nil];
  1430     }
  1431 }}
  1432 
  1433 void
  1434 Cocoa_RestoreWindow(_THIS, SDL_Window * window)
  1435 { @autoreleasepool
  1436 {
  1437     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1438 
  1439     if ([nswindow isMiniaturized]) {
  1440         [nswindow deminiaturize:nil];
  1441     } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
  1442         [nswindow zoom:nil];
  1443     }
  1444 }}
  1445 
  1446 static NSWindow *
  1447 Cocoa_RebuildWindow(SDL_WindowData * data, NSWindow * nswindow, unsigned style)
  1448 {
  1449     if (!data->created) {
  1450         /* Don't mess with other people's windows... */
  1451         return nswindow;
  1452     }
  1453 
  1454     [data->listener close];
  1455     data->nswindow = [[SDLWindow alloc] initWithContentRect:[[nswindow contentView] frame] styleMask:style backing:NSBackingStoreBuffered defer:NO screen:[nswindow screen]];
  1456     [data->nswindow setContentView:[nswindow contentView]];
  1457     [data->nswindow registerForDraggedTypes:[NSArray arrayWithObject:(NSString *)kUTTypeFileURL]];
  1458     /* See comment in SetupWindowData. */
  1459     [data->nswindow setOneShot:NO];
  1460     [data->listener listen:data];
  1461 
  1462     [nswindow close];
  1463 
  1464     return data->nswindow;
  1465 }
  1466 
  1467 void
  1468 Cocoa_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
  1469 { @autoreleasepool
  1470 {
  1471     if (SetWindowStyle(window, GetWindowStyle(window))) {
  1472         if (bordered) {
  1473             Cocoa_SetWindowTitle(_this, window);  /* this got blanked out. */
  1474         }
  1475     }
  1476 }}
  1477 
  1478 
  1479 void
  1480 Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
  1481 { @autoreleasepool
  1482 {
  1483     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1484     NSWindow *nswindow = data->nswindow;
  1485     NSRect rect;
  1486 
  1487     /* The view responder chain gets messed with during setStyleMask */
  1488     if ([[nswindow contentView] nextResponder] == data->listener) {
  1489         [[nswindow contentView] setNextResponder:nil];
  1490     }
  1491 
  1492     if (fullscreen) {
  1493         SDL_Rect bounds;
  1494 
  1495         Cocoa_GetDisplayBounds(_this, display, &bounds);
  1496         rect.origin.x = bounds.x;
  1497         rect.origin.y = bounds.y;
  1498         rect.size.width = bounds.w;
  1499         rect.size.height = bounds.h;
  1500         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1501 
  1502         /* Hack to fix origin on Mac OS X 10.4 */
  1503         NSRect screenRect = [[nswindow screen] frame];
  1504         if (screenRect.size.height >= 1.0f) {
  1505             rect.origin.y += (screenRect.size.height - rect.size.height);
  1506         }
  1507 
  1508         if ([nswindow respondsToSelector: @selector(setStyleMask:)]) {
  1509             [nswindow performSelector: @selector(setStyleMask:) withObject: (id)NSBorderlessWindowMask];
  1510         } else {
  1511             nswindow = Cocoa_RebuildWindow(data, nswindow, NSBorderlessWindowMask);
  1512         }
  1513     } else {
  1514         rect.origin.x = window->windowed.x;
  1515         rect.origin.y = window->windowed.y;
  1516         rect.size.width = window->windowed.w;
  1517         rect.size.height = window->windowed.h;
  1518         ConvertNSRect([nswindow screen], fullscreen, &rect);
  1519 
  1520         if ([nswindow respondsToSelector: @selector(setStyleMask:)]) {
  1521             [nswindow performSelector: @selector(setStyleMask:) withObject: (id)(uintptr_t)GetWindowStyle(window)];
  1522 
  1523             /* Hack to restore window decorations on Mac OS X 10.10 */
  1524             NSRect frameRect = [nswindow frame];
  1525             [nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
  1526             [nswindow setFrame:frameRect display:NO];
  1527         } else {
  1528             nswindow = Cocoa_RebuildWindow(data, nswindow, GetWindowStyle(window));
  1529         }
  1530     }
  1531 
  1532     /* The view responder chain gets messed with during setStyleMask */
  1533     if ([[nswindow contentView] nextResponder] != data->listener) {
  1534         [[nswindow contentView] setNextResponder:data->listener];
  1535     }
  1536 
  1537     s_moveHack = 0;
  1538     [nswindow setContentSize:rect.size];
  1539     [nswindow setFrameOrigin:rect.origin];
  1540     s_moveHack = SDL_GetTicks();
  1541 
  1542     /* When the window style changes the title is cleared */
  1543     if (!fullscreen) {
  1544         Cocoa_SetWindowTitle(_this, window);
  1545     }
  1546 
  1547     if (SDL_ShouldAllowTopmost() && fullscreen) {
  1548         /* OpenGL is rendering to the window, so make it visible! */
  1549         [nswindow setLevel:CGShieldingWindowLevel()];
  1550     } else {
  1551         [nswindow setLevel:kCGNormalWindowLevel];
  1552     }
  1553 
  1554     if ([nswindow isVisible] || fullscreen) {
  1555         [data->listener pauseVisibleObservation];
  1556         [nswindow makeKeyAndOrderFront:nil];
  1557         [data->listener resumeVisibleObservation];
  1558     }
  1559 
  1560     ScheduleContextUpdates(data);
  1561 }}
  1562 
  1563 int
  1564 Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
  1565 {
  1566     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1567     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1568     const uint32_t tableSize = 256;
  1569     CGGammaValue redTable[tableSize];
  1570     CGGammaValue greenTable[tableSize];
  1571     CGGammaValue blueTable[tableSize];
  1572     uint32_t i;
  1573     float inv65535 = 1.0f / 65535.0f;
  1574 
  1575     /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
  1576     for (i = 0; i < 256; i++) {
  1577         redTable[i] = ramp[0*256+i] * inv65535;
  1578         greenTable[i] = ramp[1*256+i] * inv65535;
  1579         blueTable[i] = ramp[2*256+i] * inv65535;
  1580     }
  1581 
  1582     if (CGSetDisplayTransferByTable(display_id, tableSize,
  1583                                     redTable, greenTable, blueTable) != CGDisplayNoErr) {
  1584         return SDL_SetError("CGSetDisplayTransferByTable()");
  1585     }
  1586     return 0;
  1587 }
  1588 
  1589 int
  1590 Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
  1591 {
  1592     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
  1593     CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
  1594     const uint32_t tableSize = 256;
  1595     CGGammaValue redTable[tableSize];
  1596     CGGammaValue greenTable[tableSize];
  1597     CGGammaValue blueTable[tableSize];
  1598     uint32_t i, tableCopied;
  1599 
  1600     if (CGGetDisplayTransferByTable(display_id, tableSize,
  1601                                     redTable, greenTable, blueTable, &tableCopied) != CGDisplayNoErr) {
  1602         return SDL_SetError("CGGetDisplayTransferByTable()");
  1603     }
  1604 
  1605     for (i = 0; i < tableCopied; i++) {
  1606         ramp[0*256+i] = (Uint16)(redTable[i] * 65535.0f);
  1607         ramp[1*256+i] = (Uint16)(greenTable[i] * 65535.0f);
  1608         ramp[2*256+i] = (Uint16)(blueTable[i] * 65535.0f);
  1609     }
  1610     return 0;
  1611 }
  1612 
  1613 void
  1614 Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
  1615 {
  1616     /* Move the cursor to the nearest point in the window */
  1617     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1618     if (grabbed && data && ![data->listener isMoving]) {
  1619         int x, y;
  1620         CGPoint cgpoint;
  1621 
  1622         SDL_GetMouseState(&x, &y);
  1623         cgpoint.x = window->x + x;
  1624         cgpoint.y = window->y + y;
  1625 
  1626         Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
  1627 
  1628         DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
  1629         CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
  1630     }
  1631 
  1632     if ( data && (window->flags & SDL_WINDOW_FULLSCREEN) ) {
  1633         if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS)
  1634             && ![data->listener isInFullscreenSpace]) {
  1635             /* OpenGL is rendering to the window, so make it visible! */
  1636             /* Doing this in 10.11 while in a Space breaks things (bug #3152) */
  1637             [data->nswindow setLevel:CGShieldingWindowLevel()];
  1638         } else {
  1639             [data->nswindow setLevel:kCGNormalWindowLevel];
  1640         }
  1641     }
  1642 }
  1643 
  1644 void
  1645 Cocoa_DestroyWindow(_THIS, SDL_Window * window)
  1646 { @autoreleasepool
  1647 {
  1648     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1649 
  1650     if (data) {
  1651         [data->listener close];
  1652         [data->listener release];
  1653         if (data->created) {
  1654             [data->nswindow close];
  1655         }
  1656 
  1657         NSArray *contexts = [[data->nscontexts copy] autorelease];
  1658         for (SDLOpenGLContext *context in contexts) {
  1659             /* Calling setWindow:NULL causes the context to remove itself from the context list. */            
  1660             [context setWindow:NULL];
  1661         }
  1662         [data->nscontexts release];
  1663 
  1664         SDL_free(data);
  1665     }
  1666     window->driverdata = NULL;
  1667 }}
  1668 
  1669 SDL_bool
  1670 Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
  1671 {
  1672     NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
  1673 
  1674     if (info->version.major <= SDL_MAJOR_VERSION) {
  1675         info->subsystem = SDL_SYSWM_COCOA;
  1676         info->info.cocoa.window = nswindow;
  1677         return SDL_TRUE;
  1678     } else {
  1679         SDL_SetError("Application not compiled with SDL %d.%d\n",
  1680                      SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
  1681         return SDL_FALSE;
  1682     }
  1683 }
  1684 
  1685 SDL_bool
  1686 Cocoa_IsWindowInFullscreenSpace(SDL_Window * window)
  1687 {
  1688     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1689 
  1690     if ([data->listener isInFullscreenSpace]) {
  1691         return SDL_TRUE;
  1692     } else {
  1693         return SDL_FALSE;
  1694     }
  1695 }
  1696 
  1697 SDL_bool
  1698 Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state)
  1699 { @autoreleasepool
  1700 {
  1701     SDL_bool succeeded = SDL_FALSE;
  1702     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
  1703 
  1704     if ([data->listener setFullscreenSpace:(state ? YES : NO)]) {
  1705         const int maxattempts = 3;
  1706         int attempt = 0;
  1707         while (++attempt <= maxattempts) {
  1708             /* Wait for the transition to complete, so application changes
  1709              take effect properly (e.g. setting the window size, etc.)
  1710              */
  1711             const int limit = 10000;
  1712             int count = 0;
  1713             while ([data->listener isInFullscreenSpaceTransition]) {
  1714                 if ( ++count == limit ) {
  1715                     /* Uh oh, transition isn't completing. Should we assert? */
  1716                     break;
  1717                 }
  1718                 SDL_Delay(1);
  1719                 SDL_PumpEvents();
  1720             }
  1721             if ([data->listener isInFullscreenSpace] == (state ? YES : NO))
  1722                 break;
  1723             /* Try again, the last attempt was interrupted by user gestures */
  1724             if (![data->listener setFullscreenSpace:(state ? YES : NO)])
  1725                 break; /* ??? */
  1726         }
  1727         /* Return TRUE to prevent non-space fullscreen logic from running */
  1728         succeeded = SDL_TRUE;
  1729     }
  1730 
  1731     return succeeded;
  1732 }}
  1733 
  1734 int
  1735 Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
  1736 {
  1737     return 0;  /* just succeed, the real work is done elsewhere. */
  1738 }
  1739 
  1740 #endif /* SDL_VIDEO_DRIVER_COCOA */
  1741 
  1742 /* vi: set ts=4 sw=4 expandtab: */