src/video/cocoa/SDL_cocoawindow.m
author Ryan C. Gordon <icculus@icculus.org>
Sun, 31 May 2015 00:50:30 -0400
changeset 9685 525f13ccf27f
parent 9679 7fc4a8be47a8
child 9790 d968c87f2a5d
permissions -rw-r--r--
Cocoa: ignore mouseDown events in a window's titlebar.

These events accidentally slipping in sometimes appears to be a bug (or
maybe new behavior) in 10.10, as previous versions of Mac OS X don't appear
to ever trigger this.

Thanks to Paulo Marques for pointing out the fix on the SDL mailing list!

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