src/video/cocoa/SDL_cocoawindow.m
author Ryan C. Gordon <icculus@icculus.org>
Tue, 26 May 2015 11:01:19 -0400
changeset 9623 b381999d8944
parent 9619 b94b6d0bff0f
child 9628 065e4ddc8753
permissions -rw-r--r--
Cocoa: report SDL_WINDOWEVENT_EXPOSED events to the app (thanks, David!).

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