src/video/cocoa/SDL_cocoawindow.m
author Jørgen P. Tjernø <jorgen@uberent.com>
Mon, 24 Nov 2014 11:46:20 -0800
changeset 9237 2cc90bb31777
parent 9236 a845edf98a80
child 9419 9763f689bced
permissions -rw-r--r--
Mac: Add drag & drop support.

Fixes bug https://bugzilla.libsdl.org/show_bug.cgi?id=2757

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