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