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