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