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