src/video/cocoa/SDL_cocoawindow.m
author Edward Rudd <urkle@outoforder.cc>
Sun, 23 Nov 2014 15:48:52 -0500
changeset 9236 a845edf98a80
parent 9086 c5e33f9a0d03
child 9237 2cc90bb31777
permissions -rw-r--r--
Cocoa: add in handling of "lost" touches on OS X. [bug #2635]

This scenario can occur, for example, when the 4-finger touch sequence is used to switch spaces. the SDL window does not receive the touch up events and ends up thinking there are far more fingers on the pad than there are.

So the solution here is everytime a new "touch" appears we can through and check if there are any existing known touches by the OS and if there are none, abut SDL things there are, we simply go through and cancel the SDL touches.

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