1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2020 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_cocoamousetap.h"
42#include "SDL_cocoaopengl.h"
43#include "SDL_cocoaopengles.h"
44#include "SDL_assert.h"
45
46/* #define DEBUG_COCOAWINDOW */
47
48#ifdef DEBUG_COCOAWINDOW
49#define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
50#else
51#define DLog(...) do { } while (0)
52#endif
53
54
55#define FULLSCREEN_MASK (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN)
56
57#ifndef MAC_OS_X_VERSION_10_12
58#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
59#endif
60
61@interface SDLWindow : NSWindow <NSDraggingDestination>
62/* These are needed for borderless/fullscreen windows */
63- (BOOL)canBecomeKeyWindow;
64- (BOOL)canBecomeMainWindow;
65- (void)sendEvent:(NSEvent *)event;
66- (void)doCommandBySelector:(SEL)aSelector;
67
68/* Handle drag-and-drop of files onto the SDL window. */
69- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
70- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
71- (BOOL)wantsPeriodicDraggingUpdates;
72- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
73
74- (SDL_Window*)findSDLWindow;
75@end
76
77@implementation SDLWindow
78
79- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
80{
81    /* Only allow using the macOS native fullscreen toggle menubar item if the
82     * window is resizable and not in a SDL fullscreen mode.
83     */
84    if ([menuItem action] == @selector(toggleFullScreen:)) {
85        SDL_Window *window = [self findSDLWindow];
86        if (window == NULL) {
87            return NO;
88        } else if ((window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0) {
89            return NO;
90        } else if ((window->flags & SDL_WINDOW_RESIZABLE) == 0) {
91            return NO;
92        }
93    }
94    return [super validateMenuItem:menuItem];
95}
96
97- (BOOL)canBecomeKeyWindow
98{
99    return YES;
100}
101
102- (BOOL)canBecomeMainWindow
103{
104    return YES;
105}
106
107- (void)sendEvent:(NSEvent *)event
108{
109    [super sendEvent:event];
110
111    if ([event type] != NSEventTypeLeftMouseUp) {
112        return;
113    }
114
115    id delegate = [self delegate];
116    if (![delegate isKindOfClass:[Cocoa_WindowListener class]]) {
117        return;
118    }
119
120    if ([delegate isMoving]) {
121        [delegate windowDidFinishMoving];
122    }
123}
124
125/* We'll respond to selectors by doing nothing so we don't beep.
126 * The escape key gets converted to a "cancel" selector, etc.
127 */
128- (void)doCommandBySelector:(SEL)aSelector
129{
130    /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
131}
132
133- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
134{
135    if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) {
136        return NSDragOperationGeneric;
137    }
138
139    return NSDragOperationNone; /* no idea what to do with this, reject it. */
140}
141
142- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
143{ @autoreleasepool
144{
145    NSPasteboard *pasteboard = [sender draggingPasteboard];
146    NSArray *types = [NSArray arrayWithObject:NSFilenamesPboardType];
147    NSString *desiredType = [pasteboard availableTypeFromArray:types];
148    SDL_Window *sdlwindow = [self findSDLWindow];
149
150    if (desiredType == nil) {
151        return NO;  /* can't accept anything that's being dropped here. */
152    }
153
154    NSData *data = [pasteboard dataForType:desiredType];
155    if (data == nil) {
156        return NO;
157    }
158
159    SDL_assert([desiredType isEqualToString:NSFilenamesPboardType]);
160    NSArray *array = [pasteboard propertyListForType:@"NSFilenamesPboardType"];
161
162    for (NSString *path in array) {
163        NSURL *fileURL = [NSURL fileURLWithPath:path];
164        NSNumber *isAlias = nil;
165
166        [fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil];
167
168        /* If the URL is an alias, resolve it. */
169        if ([isAlias boolValue]) {
170            NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI;
171            NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil];
172            if (bookmark != nil) {
173                NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark
174                                                               options:opts
175                                                         relativeToURL:nil
176                                                   bookmarkDataIsStale:nil
177                                                                 error:nil];
178
179                if (resolvedURL != nil) {
180                    fileURL = resolvedURL;
181                }
182            }
183        }
184
185        if (!SDL_SendDropFile(sdlwindow, [[fileURL path] UTF8String])) {
186            return NO;
187        }
188    }
189
190    SDL_SendDropComplete(sdlwindow);
191    return YES;
192}}
193
194- (BOOL)wantsPeriodicDraggingUpdates
195{
196    return NO;
197}
198
199- (SDL_Window*)findSDLWindow
200{
201    SDL_Window *sdlwindow = NULL;
202    SDL_VideoDevice *_this = SDL_GetVideoDevice();
203
204    /* !!! FIXME: is there a better way to do this? */
205    if (_this) {
206        for (sdlwindow = _this->windows; sdlwindow; sdlwindow = sdlwindow->next) {
207            NSWindow *nswindow = ((SDL_WindowData *) sdlwindow->driverdata)->nswindow;
208            if (nswindow == self) {
209                break;
210            }
211        }
212    }
213
214    return sdlwindow;
215}
216
217@end
218
219
220static Uint32 s_moveHack;
221
222static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
223{
224    r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height;
225}
226
227static void
228ScheduleContextUpdates(SDL_WindowData *data)
229{
230    if (!data || !data->nscontexts) {
231        return;
232    }
233
234    /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
235    #ifdef __clang__
236    #pragma clang diagnostic push
237    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
238    #endif
239
240    NSOpenGLContext *currentContext = [NSOpenGLContext currentContext];
241    NSMutableArray *contexts = data->nscontexts;
242    @synchronized (contexts) {
243        for (SDLOpenGLContext *context in contexts) {
244            if (context == currentContext) {
245                [context update];
246            } else {
247                [context scheduleUpdate];
248            }
249        }
250    }
251
252    #ifdef __clang__
253    #pragma clang diagnostic pop
254    #endif
255}
256
257/* !!! FIXME: this should use a hint callback. */
258static int
259GetHintCtrlClickEmulateRightClick()
260{
261    return SDL_GetHintBoolean(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, SDL_FALSE);
262}
263
264static NSUInteger
265GetWindowWindowedStyle(SDL_Window * window)
266{
267    NSUInteger style = 0;
268
269    if (window->flags & SDL_WINDOW_BORDERLESS) {
270        style = NSWindowStyleMaskBorderless;
271    } else {
272        style = (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable);
273    }
274    if (window->flags & SDL_WINDOW_RESIZABLE) {
275        style |= NSWindowStyleMaskResizable;
276    }
277    return style;
278}
279
280static NSUInteger
281GetWindowStyle(SDL_Window * window)
282{
283    NSUInteger style = 0;
284
285    if (window->flags & SDL_WINDOW_FULLSCREEN) {
286        style = NSWindowStyleMaskBorderless;
287    } else {
288        style = GetWindowWindowedStyle(window);
289    }
290    return style;
291}
292
293static SDL_bool
294SetWindowStyle(SDL_Window * window, NSUInteger style)
295{
296    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
297    NSWindow *nswindow = data->nswindow;
298
299    /* The view responder chain gets messed with during setStyleMask */
300    if ([data->sdlContentView nextResponder] == data->listener) {
301        [data->sdlContentView setNextResponder:nil];
302    }
303
304    [nswindow setStyleMask:style];
305
306    /* The view responder chain gets messed with during setStyleMask */
307    if ([data->sdlContentView nextResponder] != data->listener) {
308        [data->sdlContentView setNextResponder:data->listener];
309    }
310
311    return SDL_TRUE;
312}
313
314
315@implementation Cocoa_WindowListener
316
317- (void)listen:(SDL_WindowData *)data
318{
319    NSNotificationCenter *center;
320    NSWindow *window = data->nswindow;
321    NSView *view = data->sdlContentView;
322
323    _data = data;
324    observingVisible = YES;
325    wasCtrlLeft = NO;
326    wasVisible = [window isVisible];
327    isFullscreenSpace = NO;
328    inFullscreenTransition = NO;
329    pendingWindowOperation = PENDING_OPERATION_NONE;
330    isMoving = NO;
331    isDragAreaRunning = NO;
332
333    center = [NSNotificationCenter defaultCenter];
334
335    if ([window delegate] != nil) {
336        [center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window];
337        [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window];
338        [center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window];
339        [center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window];
340        [center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window];
341        [center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window];
342        [center addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window];
343        [center addObserver:self selector:@selector(windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window];
344        [center addObserver:self selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window];
345        [center addObserver:self selector:@selector(windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window];
346        [center addObserver:self selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window];
347        [center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window];
348        [center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
349        [center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
350    } else {
351        [window setDelegate:self];
352    }
353
354    /* Haven't found a delegate / notification that triggers when the window is
355     * ordered out (is not visible any more). You can be ordered out without
356     * minimizing, so DidMiniaturize doesn't work. (e.g. -[NSWindow orderOut:])
357     */
358    [window addObserver:self
359             forKeyPath:@"visible"
360                options:NSKeyValueObservingOptionNew
361                context:NULL];
362
363    [window setNextResponder:self];
364    [window setAcceptsMouseMovedEvents:YES];
365
366    [view setNextResponder:self];
367
368    [view setAcceptsTouchEvents:YES];
369}
370
371- (void)observeValueForKeyPath:(NSString *)keyPath
372                      ofObject:(id)object
373                        change:(NSDictionary *)change
374                       context:(void *)context
375{
376    if (!observingVisible) {
377        return;
378    }
379
380    if (object == _data->nswindow && [keyPath isEqualToString:@"visible"]) {
381        int newVisibility = [[change objectForKey:@"new"] intValue];
382        if (newVisibility) {
383            SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
384        } else {
385            SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
386        }
387    }
388}
389
390-(void) pauseVisibleObservation
391{
392    observingVisible = NO;
393    wasVisible = [_data->nswindow isVisible];
394}
395
396-(void) resumeVisibleObservation
397{
398    BOOL isVisible = [_data->nswindow isVisible];
399    observingVisible = YES;
400    if (wasVisible != isVisible) {
401        if (isVisible) {
402            SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_SHOWN, 0, 0);
403        } else {
404            SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
405        }
406
407        wasVisible = isVisible;
408    }
409}
410
411-(BOOL) setFullscreenSpace:(BOOL) state
412{
413    SDL_Window *window = _data->window;
414    NSWindow *nswindow = _data->nswindow;
415    SDL_VideoData *videodata = ((SDL_WindowData *) window->driverdata)->videodata;
416
417    if (!videodata->allow_spaces) {
418        return NO;  /* Spaces are forcibly disabled. */
419    } else if (state && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
420        return NO;  /* we only allow you to make a Space on FULLSCREEN_DESKTOP windows. */
421    } else if (!state && ((window->last_fullscreen_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP)) {
422        return NO;  /* we only handle leaving the Space on windows that were previously FULLSCREEN_DESKTOP. */
423    } else if (state == isFullscreenSpace) {
424        return YES;  /* already there. */
425    }
426
427    if (inFullscreenTransition) {
428        if (state) {
429            [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
430        } else {
431            [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
432        }
433        return YES;
434    }
435    inFullscreenTransition = YES;
436
437    /* you need to be FullScreenPrimary, or toggleFullScreen doesn't work. Unset it again in windowDidExitFullScreen. */
438    [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
439    [nswindow performSelectorOnMainThread: @selector(toggleFullScreen:) withObject:nswindow waitUntilDone:NO];
440    return YES;
441}
442
443-(BOOL) isInFullscreenSpace
444{
445    return isFullscreenSpace;
446}
447
448-(BOOL) isInFullscreenSpaceTransition
449{
450    return inFullscreenTransition;
451}
452
453-(void) addPendingWindowOperation:(PendingWindowOperation) operation
454{
455    pendingWindowOperation = operation;
456}
457
458- (void)close
459{
460    NSNotificationCenter *center;
461    NSWindow *window = _data->nswindow;
462    NSView *view = [window contentView];
463
464    center = [NSNotificationCenter defaultCenter];
465
466    if ([window delegate] != self) {
467        [center removeObserver:self name:NSWindowDidExposeNotification object:window];
468        [center removeObserver:self name:NSWindowDidMoveNotification object:window];
469        [center removeObserver:self name:NSWindowDidResizeNotification object:window];
470        [center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window];
471        [center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window];
472        [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window];
473        [center removeObserver:self name:NSWindowDidResignKeyNotification object:window];
474        [center removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window];
475        [center removeObserver:self name:NSWindowWillEnterFullScreenNotification object:window];
476        [center removeObserver:self name:NSWindowDidEnterFullScreenNotification object:window];
477        [center removeObserver:self name:NSWindowWillExitFullScreenNotification object:window];
478        [center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window];
479        [center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
480        [center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
481    } else {
482        [window setDelegate:nil];
483    }
484
485    [window removeObserver:self forKeyPath:@"visible"];
486
487    if ([window nextResponder] == self) {
488        [window setNextResponder:nil];
489    }
490    if ([view nextResponder] == self) {
491        [view setNextResponder:nil];
492    }
493}
494
495- (BOOL)isMoving
496{
497    return isMoving;
498}
499
500-(void) setPendingMoveX:(int)x Y:(int)y
501{
502    pendingWindowWarpX = x;
503    pendingWindowWarpY = y;
504}
505
506- (void)windowDidFinishMoving
507{
508    if ([self isMoving]) {
509        isMoving = NO;
510
511        SDL_Mouse *mouse = SDL_GetMouse();
512        if (pendingWindowWarpX != INT_MAX && pendingWindowWarpY != INT_MAX) {
513            mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY);
514            pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
515        }
516        if (mouse->relative_mode && !mouse->relative_mode_warp && mouse->focus == _data->window) {
517            mouse->SetRelativeMouseMode(SDL_TRUE);
518        }
519    }
520}
521
522- (BOOL)windowShouldClose:(id)sender
523{
524    SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_CLOSE, 0, 0);
525    return NO;
526}
527
528- (void)windowDidExpose:(NSNotification *)aNotification
529{
530    SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_EXPOSED, 0, 0);
531}
532
533- (void)windowWillMove:(NSNotification *)aNotification
534{
535    if ([_data->nswindow isKindOfClass:[SDLWindow class]]) {
536        pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
537        isMoving = YES;
538    }
539}
540
541- (void)windowDidMove:(NSNotification *)aNotification
542{
543    int x, y;
544    SDL_Window *window = _data->window;
545    NSWindow *nswindow = _data->nswindow;
546    BOOL fullscreen = window->flags & FULLSCREEN_MASK;
547    NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
548    ConvertNSRect([nswindow screen], fullscreen, &rect);
549
550    if (inFullscreenTransition) {
551        /* We'll take care of this at the end of the transition */
552        return;
553    }
554
555    if (s_moveHack) {
556        SDL_bool blockMove = ((SDL_GetTicks() - s_moveHack) < 500);
557
558        s_moveHack = 0;
559
560        if (blockMove) {
561            /* Cocoa is adjusting the window in response to a mode change */
562            rect.origin.x = window->x;
563            rect.origin.y = window->y;
564            ConvertNSRect([nswindow screen], fullscreen, &rect);
565            [nswindow setFrameOrigin:rect.origin];
566            return;
567        }
568    }
569
570    x = (int)rect.origin.x;
571    y = (int)rect.origin.y;
572
573    ScheduleContextUpdates(_data);
574
575    SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
576}
577
578- (void)windowDidResize:(NSNotification *)aNotification
579{
580    if (inFullscreenTransition) {
581        /* We'll take care of this at the end of the transition */
582        return;
583    }
584
585    SDL_Window *window = _data->window;
586    NSWindow *nswindow = _data->nswindow;
587    int x, y, w, h;
588    NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
589    ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
590    x = (int)rect.origin.x;
591    y = (int)rect.origin.y;
592    w = (int)rect.size.width;
593    h = (int)rect.size.height;
594
595    if (SDL_IsShapedWindow(window)) {
596        Cocoa_ResizeWindowShape(window);
597    }
598
599    ScheduleContextUpdates(_data);
600
601    /* The window can move during a resize event, such as when maximizing
602       or resizing from a corner */
603    SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MOVED, x, y);
604    SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
605
606    const BOOL zoomed = [nswindow isZoomed];
607    if (!zoomed) {
608        SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
609    } else if (zoomed) {
610        SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0);
611    }
612}
613
614- (void)windowDidMiniaturize:(NSNotification *)aNotification
615{
616    SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
617}
618
619- (void)windowDidDeminiaturize:(NSNotification *)aNotification
620{
621    SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_RESTORED, 0, 0);
622}
623
624- (void)windowDidBecomeKey:(NSNotification *)aNotification
625{
626    SDL_Window *window = _data->window;
627    SDL_Mouse *mouse = SDL_GetMouse();
628
629    /* We're going to get keyboard events, since we're key. */
630    /* This needs to be done before restoring the relative mouse mode. */
631    SDL_SetKeyboardFocus(window);
632
633    if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMoving]) {
634        mouse->SetRelativeMouseMode(SDL_TRUE);
635    }
636
637    /* If we just gained focus we need the updated mouse position */
638    if (!mouse->relative_mode) {
639        NSPoint point;
640        int x, y;
641
642        point = [_data->nswindow mouseLocationOutsideOfEventStream];
643        x = (int)point.x;
644        y = (int)(window->h - point.y);
645
646        if (x >= 0 && x < window->w && y >= 0 && y < window->h) {
647            SDL_SendMouseMotion(window, mouse->mouseID, 0, x, y);
648        }
649    }
650
651    /* Check to see if someone updated the clipboard */
652    Cocoa_CheckClipboardUpdate(_data->videodata);
653
654    if ((isFullscreenSpace) && ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)) {
655        [NSMenu setMenuBarVisible:NO];
656    }
657
658    const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock;
659    _data->videodata->modifierFlags = (_data->videodata->modifierFlags & ~NSEventModifierFlagCapsLock) | newflags;
660    SDL_ToggleModState(KMOD_CAPS, newflags != 0);
661}
662
663- (void)windowDidResignKey:(NSNotification *)aNotification
664{
665    SDL_Mouse *mouse = SDL_GetMouse();
666    if (mouse->relative_mode && !mouse->relative_mode_warp) {
667        mouse->SetRelativeMouseMode(SDL_FALSE);
668    }
669
670    /* Some other window will get mouse events, since we're not key. */
671    if (SDL_GetMouseFocus() == _data->window) {
672        SDL_SetMouseFocus(NULL);
673    }
674
675    /* Some other window will get keyboard events, since we're not key. */
676    if (SDL_GetKeyboardFocus() == _data->window) {
677        SDL_SetKeyboardFocus(NULL);
678    }
679
680    if (isFullscreenSpace) {
681        [NSMenu setMenuBarVisible:YES];
682    }
683}
684
685- (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
686{
687    NSNumber *oldscale = [[aNotification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey];
688
689    if (inFullscreenTransition) {
690        return;
691    }
692
693    if ([oldscale doubleValue] != [_data->nswindow backingScaleFactor]) {
694        /* Force a resize event when the backing scale factor changes. */
695        _data->window->w = 0;
696        _data->window->h = 0;
697        [self windowDidResize:aNotification];
698    }
699}
700
701- (void)windowWillEnterFullScreen:(NSNotification *)aNotification
702{
703    SDL_Window *window = _data->window;
704
705    SetWindowStyle(window, (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable));
706
707    isFullscreenSpace = YES;
708    inFullscreenTransition = YES;
709}
710
711- (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
712{
713    SDL_Window *window = _data->window;
714
715    if (window->is_destroying) {
716        return;
717    }
718
719    SetWindowStyle(window, GetWindowStyle(window));
720
721    isFullscreenSpace = NO;
722    inFullscreenTransition = NO;
723
724    [self windowDidExitFullScreen:nil];
725}
726
727- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
728{
729    SDL_Window *window = _data->window;
730    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
731    NSWindow *nswindow = data->nswindow;
732
733    inFullscreenTransition = NO;
734
735    if (pendingWindowOperation == PENDING_OPERATION_LEAVE_FULLSCREEN) {
736        pendingWindowOperation = PENDING_OPERATION_NONE;
737        [self setFullscreenSpace:NO];
738    } else {
739        /* Unset the resizable flag.
740           This is a workaround for https://bugzilla.libsdl.org/show_bug.cgi?id=3697
741         */
742        SetWindowStyle(window, [nswindow styleMask] & (~NSWindowStyleMaskResizable));
743
744        if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
745            [NSMenu setMenuBarVisible:NO];
746        }
747
748        pendingWindowOperation = PENDING_OPERATION_NONE;
749        /* Force the size change event in case it was delivered earlier
750           while the window was still animating into place.
751         */
752        window->w = 0;
753        window->h = 0;
754        [self windowDidMove:aNotification];
755        [self windowDidResize:aNotification];
756    }
757}
758
759- (void)windowWillExitFullScreen:(NSNotification *)aNotification
760{
761    SDL_Window *window = _data->window;
762
763    isFullscreenSpace = NO;
764    inFullscreenTransition = YES;
765
766    /* As of macOS 10.11, the window seems to need to be resizable when exiting
767       a Space, in order for it to resize back to its windowed-mode size.
768       As of macOS 10.15, the window decorations can go missing sometimes after
769       certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows
770       sometimes. Making sure the style mask always uses the windowed mode style
771       when returning to windowed mode from a space (instead of using a pending
772       fullscreen mode style mask) seems to work around that issue.
773     */
774    SetWindowStyle(window, GetWindowWindowedStyle(window) | NSWindowStyleMaskResizable);
775}
776
777- (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
778{
779    SDL_Window *window = _data->window;
780
781    if (window->is_destroying) {
782        return;
783    }
784
785    SetWindowStyle(window, (NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable));
786
787    isFullscreenSpace = YES;
788    inFullscreenTransition = NO;
789
790    [self windowDidEnterFullScreen:nil];
791}
792
793- (void)windowDidExitFullScreen:(NSNotification *)aNotification
794{
795    SDL_Window *window = _data->window;
796    NSWindow *nswindow = _data->nswindow;
797    NSButton *button = nil;
798
799    inFullscreenTransition = NO;
800
801    /* As of macOS 10.15, the window decorations can go missing sometimes after
802       certain fullscreen-desktop->exlusive-fullscreen->windowed mode flows
803       sometimes. Making sure the style mask always uses the windowed mode style
804       when returning to windowed mode from a space (instead of using a pending
805       fullscreen mode style mask) seems to work around that issue.
806     */
807    SetWindowStyle(window, GetWindowWindowedStyle(window));
808
809    if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
810        [nswindow setLevel:NSFloatingWindowLevel];
811    } else {
812        [nswindow setLevel:kCGNormalWindowLevel];
813    }
814
815    if (pendingWindowOperation == PENDING_OPERATION_ENTER_FULLSCREEN) {
816        pendingWindowOperation = PENDING_OPERATION_NONE;
817        [self setFullscreenSpace:YES];
818    } else if (pendingWindowOperation == PENDING_OPERATION_MINIMIZE) {
819        pendingWindowOperation = PENDING_OPERATION_NONE;
820        [nswindow miniaturize:nil];
821    } else {
822        /* Adjust the fullscreen toggle button and readd menu now that we're here. */
823        if (window->flags & SDL_WINDOW_RESIZABLE) {
824            /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
825            [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
826        } else {
827            [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged];
828        }
829        [NSMenu setMenuBarVisible:YES];
830
831        pendingWindowOperation = PENDING_OPERATION_NONE;
832
833#if 0
834/* This fixed bug 3719, which is that changing window size while fullscreen
835   doesn't take effect when leaving fullscreen, but introduces bug 3809,
836   which is that a maximized window doesn't go back to normal size when
837   restored, so this code is disabled until we can properly handle the
838   beginning and end of maximize and restore.
839 */
840        /* Restore windowed size and position in case it changed while fullscreen */
841        {
842            NSRect rect;
843            rect.origin.x = window->windowed.x;
844            rect.origin.y = window->windowed.y;
845            rect.size.width = window->windowed.w;
846            rect.size.height = window->windowed.h;
847            ConvertNSRect([nswindow screen], NO, &rect);
848
849            s_moveHack = 0;
850            [nswindow setContentSize:rect.size];
851            [nswindow setFrameOrigin:rect.origin];
852            s_moveHack = SDL_GetTicks();
853        }
854#endif /* 0 */
855
856        /* Force the size change event in case it was delivered earlier
857           while the window was still animating into place.
858         */
859        window->w = 0;
860        window->h = 0;
861        [self windowDidMove:aNotification];
862        [self windowDidResize:aNotification];
863
864        /* FIXME: Why does the window get hidden? */
865        if (window->flags & SDL_WINDOW_SHOWN) {
866            Cocoa_ShowWindow(SDL_GetVideoDevice(), window);
867        }
868    }
869
870    /* There's some state that isn't quite back to normal when
871        windowDidExitFullScreen triggers. For example, the minimize button on
872        the titlebar doesn't actually enable for another 200 milliseconds or
873        so on this MacBook. Camp here and wait for that to happen before
874        going on, in case we're exiting fullscreen to minimize, which need
875        that window state to be normal before it will work. */
876    button = [nswindow standardWindowButton:NSWindowMiniaturizeButton];
877    if (button) {
878        int iterations = 0;
879        while (![button isEnabled] && (iterations < 100)) {
880            SDL_Delay(10);
881            SDL_PumpEvents();
882            iterations++;
883        }
884    }
885}
886
887-(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
888{
889    if ((_data->window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) {
890        return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
891    } else {
892        return proposedOptions;
893    }
894}
895
896/* We'll respond to key events by mostly doing nothing so we don't beep.
897 * We could handle key messages here, but we lose some in the NSApp dispatch,
898 * where they get converted to action messages, etc.
899 */
900- (void)flagsChanged:(NSEvent *)theEvent
901{
902    /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
903
904    /* Catch capslock in here as a special case:
905       https://developer.apple.com/library/archive/qa/qa1519/_index.html
906       Note that technote's check of keyCode doesn't work. At least on the
907       10.15 beta, capslock comes through here as keycode 255, but it's safe
908       to send duplicate key events; SDL filters them out quickly in
909       SDL_SendKeyboardKey(). */
910
911    /* Also note that SDL_SendKeyboardKey expects all capslock events to be
912       keypresses; it won't toggle the mod state if you send a keyrelease.  */
913    const SDL_bool osenabled = ([theEvent modifierFlags] & NSEventModifierFlagCapsLock) ? SDL_TRUE : SDL_FALSE;
914    const SDL_bool sdlenabled = (SDL_GetModState() & KMOD_CAPS) ? SDL_TRUE : SDL_FALSE;
915    if (osenabled ^ sdlenabled) {
916        SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
917        SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
918    }
919}
920- (void)keyDown:(NSEvent *)theEvent
921{
922    /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
923}
924- (void)keyUp:(NSEvent *)theEvent
925{
926    /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
927}
928
929/* We'll respond to selectors by doing nothing so we don't beep.
930 * The escape key gets converted to a "cancel" selector, etc.
931 */
932- (void)doCommandBySelector:(SEL)aSelector
933{
934    /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
935}
936
937- (BOOL)processHitTest:(NSEvent *)theEvent
938{
939    SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
940
941    if (_data->window->hit_test) {  /* if no hit-test, skip this. */
942        const NSPoint location = [theEvent locationInWindow];
943        const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
944        const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
945        if (rc == SDL_HITTEST_DRAGGABLE) {
946            if (!isDragAreaRunning) {
947                isDragAreaRunning = YES;
948                [_data->nswindow setMovableByWindowBackground:YES];
949            }
950            return YES;  /* dragging! */
951        }
952    }
953
954    if (isDragAreaRunning) {
955        isDragAreaRunning = NO;
956        [_data->nswindow setMovableByWindowBackground:NO];
957        return YES;  /* was dragging, drop event. */
958    }
959
960    return NO;  /* not a special area, carry on. */
961}
962
963- (void)mouseDown:(NSEvent *)theEvent
964{
965    const SDL_Mouse *mouse = SDL_GetMouse();
966    if (!mouse) {
967        return;
968    }
969
970    const SDL_MouseID mouseID = mouse->mouseID;
971    int button;
972    int clicks;
973
974    /* Ignore events that aren't inside the client area (i.e. title bar.) */
975    if ([theEvent window]) {
976        NSRect windowRect = [[[theEvent window] contentView] frame];
977        if (!NSMouseInRect([theEvent locationInWindow], windowRect, NO)) {
978            return;
979        }
980    }
981
982    if ([self processHitTest:theEvent]) {
983        SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
984        return;  /* dragging, drop event. */
985    }
986
987    switch ([theEvent buttonNumber]) {
988    case 0:
989        if (([theEvent modifierFlags] & NSEventModifierFlagControl) &&
990            GetHintCtrlClickEmulateRightClick()) {
991            wasCtrlLeft = YES;
992            button = SDL_BUTTON_RIGHT;
993        } else {
994            wasCtrlLeft = NO;
995            button = SDL_BUTTON_LEFT;
996        }
997        break;
998    case 1:
999        button = SDL_BUTTON_RIGHT;
1000        break;
1001    case 2:
1002        button = SDL_BUTTON_MIDDLE;
1003        break;
1004    default:
1005        button = (int) [theEvent buttonNumber] + 1;
1006        break;
1007    }
1008
1009    clicks = (int) [theEvent clickCount];
1010
1011    SDL_SendMouseButtonClicks(_data->window, mouseID, SDL_PRESSED, button, clicks);
1012}
1013
1014- (void)rightMouseDown:(NSEvent *)theEvent
1015{
1016    [self mouseDown:theEvent];
1017}
1018
1019- (void)otherMouseDown:(NSEvent *)theEvent
1020{
1021    [self mouseDown:theEvent];
1022}
1023
1024- (void)mouseUp:(NSEvent *)theEvent
1025{
1026    const SDL_Mouse *mouse = SDL_GetMouse();
1027    if (!mouse) {
1028        return;
1029    }
1030
1031    const SDL_MouseID mouseID = mouse->mouseID;
1032    int button;
1033    int clicks;
1034
1035    if ([self processHitTest:theEvent]) {
1036        SDL_SendWindowEvent(_data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
1037        return;  /* stopped dragging, drop event. */
1038    }
1039
1040    switch ([theEvent buttonNumber]) {
1041    case 0:
1042        if (wasCtrlLeft) {
1043            button = SDL_BUTTON_RIGHT;
1044            wasCtrlLeft = NO;
1045        } else {
1046            button = SDL_BUTTON_LEFT;
1047        }
1048        break;
1049    case 1:
1050        button = SDL_BUTTON_RIGHT;
1051        break;
1052    case 2:
1053        button = SDL_BUTTON_MIDDLE;
1054        break;
1055    default:
1056        button = (int) [theEvent buttonNumber] + 1;
1057        break;
1058    }
1059
1060    clicks = (int) [theEvent clickCount];
1061
1062    SDL_SendMouseButtonClicks(_data->window, mouseID, SDL_RELEASED, button, clicks);
1063}
1064
1065- (void)rightMouseUp:(NSEvent *)theEvent
1066{
1067    [self mouseUp:theEvent];
1068}
1069
1070- (void)otherMouseUp:(NSEvent *)theEvent
1071{
1072    [self mouseUp:theEvent];
1073}
1074
1075- (void)mouseMoved:(NSEvent *)theEvent
1076{
1077    SDL_Mouse *mouse = SDL_GetMouse();
1078    if (!mouse) {
1079        return;
1080    }
1081
1082    const SDL_MouseID mouseID = mouse->mouseID;
1083    SDL_Window *window = _data->window;
1084    NSPoint point;
1085    int x, y;
1086
1087    if ([self processHitTest:theEvent]) {
1088        SDL_SendWindowEvent(window, SDL_WINDOWEVENT_HIT_TEST, 0, 0);
1089        return;  /* dragging, drop event. */
1090    }
1091
1092    if (mouse->relative_mode) {
1093        return;
1094    }
1095
1096    point = [theEvent locationInWindow];
1097    x = (int)point.x;
1098    y = (int)(window->h - point.y);
1099
1100    if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
1101        if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
1102            if (x < 0) {
1103                x = 0;
1104            } else if (x >= window->w) {
1105                x = window->w - 1;
1106            }
1107            if (y < 0) {
1108                y = 0;
1109            } else if (y >= window->h) {
1110                y = window->h - 1;
1111            }
1112
1113#if !SDL_MAC_NO_SANDBOX
1114            CGPoint cgpoint;
1115
1116            /* When SDL_MAC_NO_SANDBOX is set, this is handled by
1117             * SDL_cocoamousetap.m.
1118             */
1119
1120            cgpoint.x = window->x + x;
1121            cgpoint.y = window->y + y;
1122
1123            CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
1124            CGAssociateMouseAndMouseCursorPosition(YES);
1125
1126            Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
1127#endif
1128        }
1129    }
1130
1131    SDL_SendMouseMotion(window, mouseID, 0, x, y);
1132}
1133
1134- (void)mouseDragged:(NSEvent *)theEvent
1135{
1136    [self mouseMoved:theEvent];
1137}
1138
1139- (void)rightMouseDragged:(NSEvent *)theEvent
1140{
1141    [self mouseMoved:theEvent];
1142}
1143
1144- (void)otherMouseDragged:(NSEvent *)theEvent
1145{
1146    [self mouseMoved:theEvent];
1147}
1148
1149- (void)scrollWheel:(NSEvent *)theEvent
1150{
1151    Cocoa_HandleMouseWheel(_data->window, theEvent);
1152}
1153
1154- (void)touchesBeganWithEvent:(NSEvent *) theEvent
1155{
1156    /* probably a MacBook trackpad; make this look like a synthesized event.
1157       This is backwards from reality, but better matches user expectations. */
1158    const BOOL istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent);
1159
1160    NSSet *touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil];
1161    const SDL_TouchID touchID = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[[touches anyObject] device];
1162    int existingTouchCount = 0;
1163
1164    for (NSTouch* touch in touches) {
1165        if ([touch phase] != NSTouchPhaseBegan) {
1166            existingTouchCount++;
1167        }
1168    }
1169    if (existingTouchCount == 0) {
1170        int numFingers = SDL_GetNumTouchFingers(touchID);
1171        DLog("Reset Lost Fingers: %d", numFingers);
1172        for (--numFingers; numFingers >= 0; --numFingers) {
1173            SDL_Finger* finger = SDL_GetTouchFinger(touchID, numFingers);
1174            /* trackpad touches have no window. If we really wanted one we could
1175             * use the window that has mouse or keyboard focus.
1176             * Sending a null window currently also prevents synthetic mouse
1177             * events from being generated from touch events.
1178             */
1179            SDL_Window *window = NULL;
1180            SDL_SendTouch(touchID, finger->id, window, SDL_FALSE, 0, 0, 0);
1181        }
1182    }
1183
1184    DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount);
1185    [self handleTouches:NSTouchPhaseBegan withEvent:theEvent];
1186}
1187
1188- (void)touchesMovedWithEvent:(NSEvent *) theEvent
1189{
1190    [self handleTouches:NSTouchPhaseMoved withEvent:theEvent];
1191}
1192
1193- (void)touchesEndedWithEvent:(NSEvent *) theEvent
1194{
1195    [self handleTouches:NSTouchPhaseEnded withEvent:theEvent];
1196}
1197
1198- (void)touchesCancelledWithEvent:(NSEvent *) theEvent
1199{
1200    [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
1201}
1202
1203- (void)handleTouches:(NSTouchPhase) phase withEvent:(NSEvent *) theEvent
1204{
1205    NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];
1206
1207    /* probably a MacBook trackpad; make this look like a synthesized event.
1208       This is backwards from reality, but better matches user expectations. */
1209    const BOOL istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent);
1210
1211    for (NSTouch *touch in touches) {
1212        const SDL_TouchID touchId = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[touch device];
1213        SDL_TouchDeviceType devtype = SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE;
1214
1215        /* trackpad touches have no window. If we really wanted one we could
1216         * use the window that has mouse or keyboard focus.
1217         * Sending a null window currently also prevents synthetic mouse events
1218         * from being generated from touch events.
1219         */
1220        SDL_Window *window = NULL;
1221
1222#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101202 /* Added in the 10.12.2 SDK. */
1223        if ([touch respondsToSelector:@selector(type)]) {
1224            /* TODO: Before implementing direct touch support here, we need to
1225             * figure out whether the OS generates mouse events from them on its
1226             * own. If it does, we should prevent SendTouch from generating
1227             * synthetic mouse events for these touches itself (while also
1228             * sending a window.) It will also need to use normalized window-
1229             * relative coordinates via [touch locationInView:].
1230             */
1231            if ([touch type] == NSTouchTypeDirect) {
1232                continue;
1233            }
1234        }
1235#endif
1236
1237        if (SDL_AddTouch(touchId, devtype, "") < 0) {
1238            return;
1239        }
1240
1241        const SDL_FingerID fingerId = (SDL_FingerID)(intptr_t)[touch identity];
1242        float x = [touch normalizedPosition].x;
1243        float y = [touch normalizedPosition].y;
1244        /* Make the origin the upper left instead of the lower left */
1245        y = 1.0f - y;
1246
1247        switch (phase) {
1248        case NSTouchPhaseBegan:
1249            SDL_SendTouch(touchId, fingerId, window, SDL_TRUE, x, y, 1.0f);
1250            break;
1251        case NSTouchPhaseEnded:
1252        case NSTouchPhaseCancelled:
1253            SDL_SendTouch(touchId, fingerId, window, SDL_FALSE, x, y, 1.0f);
1254            break;
1255        case NSTouchPhaseMoved:
1256            SDL_SendTouchMotion(touchId, fingerId, window, x, y, 1.0f);
1257            break;
1258        default:
1259            break;
1260        }
1261    }
1262}
1263
1264@end
1265
1266@interface SDLView : NSView {
1267    SDL_Window *_sdlWindow;
1268}
1269
1270- (void)setSDLWindow:(SDL_Window*)window;
1271
1272/* The default implementation doesn't pass rightMouseDown to responder chain */
1273- (void)rightMouseDown:(NSEvent *)theEvent;
1274- (BOOL)mouseDownCanMoveWindow;
1275- (void)drawRect:(NSRect)dirtyRect;
1276- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent;
1277- (BOOL)wantsUpdateLayer;
1278- (void)updateLayer;
1279@end
1280
1281@implementation SDLView
1282
1283- (void)setSDLWindow:(SDL_Window*)window
1284{
1285    _sdlWindow = window;
1286}
1287
1288/* this is used on older macOS revisions, and newer ones which emulate old
1289   NSOpenGLContext behaviour while still using a layer under the hood. 10.8 and
1290   later use updateLayer, up until 10.14.2 or so, which uses drawRect without
1291   a GraphicsContext and with a layer active instead (for OpenGL contexts). */
1292- (void)drawRect:(NSRect)dirtyRect
1293{
1294    /* Force the graphics context to clear to black so we don't get a flash of
1295       white until the app is ready to draw. In practice on modern macOS, this
1296       only gets called for window creation and other extraordinary events. */
1297    if ([NSGraphicsContext currentContext]) {
1298        [[NSColor blackColor] setFill];
1299        NSRectFill(dirtyRect);
1300    } else if (self.layer) {
1301        self.layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
1302    }
1303
1304    SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
1305}
1306
1307- (BOOL)wantsUpdateLayer
1308{
1309    return YES;
1310}
1311
1312/* This is also called when a Metal layer is active. */
1313- (void)updateLayer
1314{
1315    /* Force the graphics context to clear to black so we don't get a flash of
1316       white until the app is ready to draw. In practice on modern macOS, this
1317       only gets called for window creation and other extraordinary events. */
1318    self.layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
1319    ScheduleContextUpdates((SDL_WindowData *) _sdlWindow->driverdata);
1320    SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
1321}
1322
1323- (void)rightMouseDown:(NSEvent *)theEvent
1324{
1325    [[self nextResponder] rightMouseDown:theEvent];
1326}
1327
1328- (BOOL)mouseDownCanMoveWindow
1329{
1330    /* Always say YES, but this doesn't do anything until we call
1331       -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
1332       during mouse events when we're using a drag area. */
1333    return YES;
1334}
1335
1336- (void)resetCursorRects
1337{
1338    [super resetCursorRects];
1339    SDL_Mouse *mouse = SDL_GetMouse();
1340
1341    if (mouse->cursor_shown && mouse->cur_cursor && !mouse->relative_mode) {
1342        [self addCursorRect:[self bounds]
1343                     cursor:mouse->cur_cursor->driverdata];
1344    } else {
1345        [self addCursorRect:[self bounds]
1346                     cursor:[NSCursor invisibleCursor]];
1347    }
1348}
1349
1350- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
1351{
1352    if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) {
1353        return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE);
1354    } else {
1355        return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", SDL_FALSE);
1356    }
1357}
1358@end
1359
1360static int
1361SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, NSView *nsview, SDL_bool created)
1362{ @autoreleasepool
1363{
1364    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
1365    SDL_WindowData *data;
1366
1367    /* Allocate the window data */
1368    window->driverdata = data = (SDL_WindowData *) SDL_calloc(1, sizeof(*data));
1369    if (!data) {
1370        return SDL_OutOfMemory();
1371    }
1372    data->window = window;
1373    data->nswindow = nswindow;
1374    data->created = created;
1375    data->videodata = videodata;
1376    data->nscontexts = [[NSMutableArray alloc] init];
1377    data->sdlContentView = nsview;
1378
1379    /* Create an event listener for the window */
1380    data->listener = [[Cocoa_WindowListener alloc] init];
1381
1382    /* Fill in the SDL window with the window data */
1383    {
1384        NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
1385        ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
1386        window->x = (int)rect.origin.x;
1387        window->y = (int)rect.origin.y;
1388        window->w = (int)rect.size.width;
1389        window->h = (int)rect.size.height;
1390    }
1391
1392    /* Set up the listener after we create the view */
1393    [data->listener listen:data];
1394
1395    if ([nswindow isVisible]) {
1396        window->flags |= SDL_WINDOW_SHOWN;
1397    } else {
1398        window->flags &= ~SDL_WINDOW_SHOWN;
1399    }
1400
1401    {
1402        unsigned long style = [nswindow styleMask];
1403
1404        if (style == NSWindowStyleMaskBorderless) {
1405            window->flags |= SDL_WINDOW_BORDERLESS;
1406        } else {
1407            window->flags &= ~SDL_WINDOW_BORDERLESS;
1408        }
1409        if (style & NSWindowStyleMaskResizable) {
1410            window->flags |= SDL_WINDOW_RESIZABLE;
1411        } else {
1412            window->flags &= ~SDL_WINDOW_RESIZABLE;
1413        }
1414    }
1415
1416    /* isZoomed always returns true if the window is not resizable */
1417    if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
1418        window->flags |= SDL_WINDOW_MAXIMIZED;
1419    } else {
1420        window->flags &= ~SDL_WINDOW_MAXIMIZED;
1421    }
1422
1423    if ([nswindow isMiniaturized]) {
1424        window->flags |= SDL_WINDOW_MINIMIZED;
1425    } else {
1426        window->flags &= ~SDL_WINDOW_MINIMIZED;
1427    }
1428
1429    if ([nswindow isKeyWindow]) {
1430        window->flags |= SDL_WINDOW_INPUT_FOCUS;
1431        SDL_SetKeyboardFocus(data->window);
1432    }
1433
1434    /* Prevents the window's "window device" from being destroyed when it is
1435     * hidden. See http://www.mikeash.com/pyblog/nsopenglcontext-and-one-shot.html
1436     */
1437    [nswindow setOneShot:NO];
1438
1439    /* All done! */
1440    window->driverdata = data;
1441    return 0;
1442}}
1443
1444int
1445Cocoa_CreateWindow(_THIS, SDL_Window * window)
1446{ @autoreleasepool
1447{
1448    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
1449    NSWindow *nswindow;
1450    SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
1451    NSRect rect;
1452    SDL_Rect bounds;
1453    NSUInteger style;
1454    NSArray *screens = [NSScreen screens];
1455
1456    Cocoa_GetDisplayBounds(_this, display, &bounds);
1457    rect.origin.x = window->x;
1458    rect.origin.y = window->y;
1459    rect.size.width = window->w;
1460    rect.size.height = window->h;
1461    ConvertNSRect([screens objectAtIndex:0], (window->flags & FULLSCREEN_MASK), &rect);
1462
1463    style = GetWindowStyle(window);
1464
1465    /* Figure out which screen to place this window */
1466    NSScreen *screen = nil;
1467    for (NSScreen *candidate in screens) {
1468        NSRect screenRect = [candidate frame];
1469        if (rect.origin.x >= screenRect.origin.x &&
1470            rect.origin.x < screenRect.origin.x + screenRect.size.width &&
1471            rect.origin.y >= screenRect.origin.y &&
1472            rect.origin.y < screenRect.origin.y + screenRect.size.height) {
1473            screen = candidate;
1474            rect.origin.x -= screenRect.origin.x;
1475            rect.origin.y -= screenRect.origin.y;
1476        }
1477    }
1478
1479    @try {
1480        nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
1481    }
1482    @catch (NSException *e) {
1483        return SDL_SetError("%s", [[e reason] UTF8String]);
1484    }
1485
1486#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 /* Added in the 10.12.0 SDK. */
1487    /* By default, don't allow users to make our window tabbed in 10.12 or later */
1488    if ([nswindow respondsToSelector:@selector(setTabbingMode:)]) {
1489        [nswindow setTabbingMode:NSWindowTabbingModeDisallowed];
1490    }
1491#endif
1492
1493    if (videodata->allow_spaces) {
1494        SDL_assert(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6);
1495        SDL_assert([nswindow respondsToSelector:@selector(toggleFullScreen:)]);
1496        /* we put FULLSCREEN_DESKTOP windows in their own Space, without a toggle button or menubar, later */
1497        if (window->flags & SDL_WINDOW_RESIZABLE) {
1498            /* resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. */
1499            [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
1500        }
1501    }
1502
1503    if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
1504        [nswindow setLevel:NSFloatingWindowLevel];
1505    }
1506
1507    /* Create a default view for this window */
1508    rect = [nswindow contentRectForFrameRect:[nswindow frame]];
1509    SDLView *contentView = [[SDLView alloc] initWithFrame:rect];
1510    [contentView setSDLWindow:window];
1511
1512    /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
1513    #ifdef __clang__
1514    #pragma clang diagnostic push
1515    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1516    #endif
1517    /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when
1518     * the NSHighResolutionCapable boolean is set in Info.plist. */
1519    if ([contentView respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
1520        BOOL highdpi = (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
1521        [contentView setWantsBestResolutionOpenGLSurface:highdpi];
1522    }
1523    #ifdef __clang__
1524    #pragma clang diagnostic pop
1525    #endif
1526
1527#if SDL_VIDEO_OPENGL_ES2
1528#if SDL_VIDEO_OPENGL_EGL
1529    if ((window->flags & SDL_WINDOW_OPENGL) &&
1530        _this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
1531        [contentView setWantsLayer:TRUE];
1532    }
1533#endif /* SDL_VIDEO_OPENGL_EGL */
1534#endif /* SDL_VIDEO_OPENGL_ES2 */
1535    [nswindow setContentView:contentView];
1536    [contentView release];
1537
1538    if (SetupWindowData(_this, window, nswindow, contentView, SDL_TRUE) < 0) {
1539        [nswindow release];
1540        return -1;
1541    }
1542
1543    if (!(window->flags & SDL_WINDOW_OPENGL)) {
1544        return 0;
1545    }
1546
1547    /* The rest of this macro mess is for OpenGL or OpenGL ES windows */
1548#if SDL_VIDEO_OPENGL_ES2
1549    if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
1550#if SDL_VIDEO_OPENGL_EGL
1551        if (Cocoa_GLES_SetupWindow(_this, window) < 0) {
1552            Cocoa_DestroyWindow(_this, window);
1553            return -1;
1554        }
1555        return 0;
1556#else
1557        return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
1558#endif /* SDL_VIDEO_OPENGL_EGL */
1559    }
1560#endif /* SDL_VIDEO_OPENGL_ES2 */
1561    return 0;
1562}}
1563
1564int
1565Cocoa_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
1566{ @autoreleasepool
1567{
1568    NSView* nsview = nil;
1569    NSWindow *nswindow = nil;
1570
1571    if ([(id)data isKindOfClass:[NSWindow class]]) {
1572      nswindow = (NSWindow*)data;
1573      nsview = [nswindow contentView];
1574    } else if ([(id)data isKindOfClass:[NSView class]]) {
1575      nsview = (NSView*)data;
1576      nswindow = [nsview window];
1577    } else {
1578      SDL_assert(false);
1579    }
1580
1581    NSString *title;
1582
1583    /* Query the title from the existing window */
1584    title = [nswindow title];
1585    if (title) {
1586        window->title = SDL_strdup([title UTF8String]);
1587    }
1588
1589    /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
1590    #ifdef __clang__
1591    #pragma clang diagnostic push
1592    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1593    #endif
1594    /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when
1595     * the NSHighResolutionCapable boolean is set in Info.plist. */
1596    if ([nsview respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
1597        BOOL highdpi = (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
1598        [nsview setWantsBestResolutionOpenGLSurface:highdpi];
1599    }
1600    #ifdef __clang__
1601    #pragma clang diagnostic pop
1602    #endif
1603
1604    return SetupWindowData(_this, window, nswindow, nsview, SDL_FALSE);
1605}}
1606
1607void
1608Cocoa_SetWindowTitle(_THIS, SDL_Window * window)
1609{ @autoreleasepool
1610{
1611    const char *title = window->title ? window->title : "";
1612    NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
1613    NSString *string = [[NSString alloc] initWithUTF8String:title];
1614    [nswindow setTitle:string];
1615    [string release];
1616}}
1617
1618void
1619Cocoa_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
1620{ @autoreleasepool
1621{
1622    NSImage *nsimage = Cocoa_CreateImage(icon);
1623
1624    if (nsimage) {
1625        [NSApp setApplicationIconImage:nsimage];
1626    }
1627}}
1628
1629void
1630Cocoa_SetWindowPosition(_THIS, SDL_Window * window)
1631{ @autoreleasepool
1632{
1633    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
1634    NSWindow *nswindow = windata->nswindow;
1635    NSRect rect;
1636    Uint32 moveHack;
1637
1638    rect.origin.x = window->x;
1639    rect.origin.y = window->y;
1640    rect.size.width = window->w;
1641    rect.size.height = window->h;
1642    ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
1643
1644    moveHack = s_moveHack;
1645    s_moveHack = 0;
1646    [nswindow setFrameOrigin:rect.origin];
1647    s_moveHack = moveHack;
1648
1649    ScheduleContextUpdates(windata);
1650}}
1651
1652void
1653Cocoa_SetWindowSize(_THIS, SDL_Window * window)
1654{ @autoreleasepool
1655{
1656    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
1657    NSWindow *nswindow = windata->nswindow;
1658    NSRect rect;
1659    Uint32 moveHack;
1660
1661    /* Cocoa will resize the window from the bottom-left rather than the
1662     * top-left when -[nswindow setContentSize:] is used, so we must set the
1663     * entire frame based on the new size, in order to preserve the position.
1664     */
1665    rect.origin.x = window->x;
1666    rect.origin.y = window->y;
1667    rect.size.width = window->w;
1668    rect.size.height = window->h;
1669    ConvertNSRect([nswindow screen], (window->flags & FULLSCREEN_MASK), &rect);
1670
1671    moveHack = s_moveHack;
1672    s_moveHack = 0;
1673    [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES];
1674    s_moveHack = moveHack;
1675
1676    ScheduleContextUpdates(windata);
1677}}
1678
1679void
1680Cocoa_SetWindowMinimumSize(_THIS, SDL_Window * window)
1681{ @autoreleasepool
1682{
1683    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
1684
1685    NSSize minSize;
1686    minSize.width = window->min_w;
1687    minSize.height = window->min_h;
1688
1689    [windata->nswindow setContentMinSize:minSize];
1690}}
1691
1692void
1693Cocoa_SetWindowMaximumSize(_THIS, SDL_Window * window)
1694{ @autoreleasepool
1695{
1696    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
1697
1698    NSSize maxSize;
1699    maxSize.width = window->max_w;
1700    maxSize.height = window->max_h;
1701
1702    [windata->nswindow setContentMaxSize:maxSize];
1703}}
1704
1705void
1706Cocoa_ShowWindow(_THIS, SDL_Window * window)
1707{ @autoreleasepool
1708{
1709    SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
1710    NSWindow *nswindow = windowData->nswindow;
1711
1712    if (![nswindow isMiniaturized]) {
1713        [windowData->listener pauseVisibleObservation];
1714        [nswindow makeKeyAndOrderFront:nil];
1715        [windowData->listener resumeVisibleObservation];
1716    }
1717}}
1718
1719void
1720Cocoa_HideWindow(_THIS, SDL_Window * window)
1721{ @autoreleasepool
1722{
1723    NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
1724
1725    [nswindow orderOut:nil];
1726}}
1727
1728void
1729Cocoa_RaiseWindow(_THIS, SDL_Window * window)
1730{ @autoreleasepool
1731{
1732    SDL_WindowData *windowData = ((SDL_WindowData *) window->driverdata);
1733    NSWindow *nswindow = windowData->nswindow;
1734
1735    /* makeKeyAndOrderFront: has the side-effect of deminiaturizing and showing
1736       a minimized or hidden window, so check for that before showing it.
1737     */
1738    [windowData->listener pauseVisibleObservation];
1739    if (![nswindow isMiniaturized] && [nswindow isVisible]) {
1740        [NSApp activateIgnoringOtherApps:YES];
1741        [nswindow makeKeyAndOrderFront:nil];
1742    }
1743    [windowData->listener resumeVisibleObservation];
1744}}
1745
1746void
1747Cocoa_MaximizeWindow(_THIS, SDL_Window * window)
1748{ @autoreleasepool
1749{
1750    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
1751    NSWindow *nswindow = windata->nswindow;
1752
1753    [nswindow zoom:nil];
1754
1755    ScheduleContextUpdates(windata);
1756}}
1757
1758void
1759Cocoa_MinimizeWindow(_THIS, SDL_Window * window)
1760{ @autoreleasepool
1761{
1762    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
1763    NSWindow *nswindow = data->nswindow;
1764    if ([data->listener isInFullscreenSpaceTransition]) {
1765        [data->listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
1766    } else {
1767        [nswindow miniaturize:nil];
1768    }
1769}}
1770
1771void
1772Cocoa_RestoreWindow(_THIS, SDL_Window * window)
1773{ @autoreleasepool
1774{
1775    NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
1776
1777    if ([nswindow isMiniaturized]) {
1778        [nswindow deminiaturize:nil];
1779    } else if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
1780        [nswindow zoom:nil];
1781    }
1782}}
1783
1784void
1785Cocoa_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
1786{ @autoreleasepool
1787{
1788    if (SetWindowStyle(window, GetWindowStyle(window))) {
1789        if (bordered) {
1790            Cocoa_SetWindowTitle(_this, window);  /* this got blanked out. */
1791        }
1792    }
1793}}
1794
1795void
1796Cocoa_SetWindowResizable(_THIS, SDL_Window * window, SDL_bool resizable)
1797{ @autoreleasepool
1798{
1799    /* Don't set this if we're in a space!
1800     * The window will get permanently stuck if resizable is false.
1801     * -flibit
1802     */
1803    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
1804    Cocoa_WindowListener *listener = data->listener;
1805    if (![listener isInFullscreenSpace]) {
1806        SetWindowStyle(window, GetWindowStyle(window));
1807    }
1808}}
1809
1810void
1811Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
1812{ @autoreleasepool
1813{
1814    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
1815    NSWindow *nswindow = data->nswindow;
1816    NSRect rect;
1817
1818    /* The view responder chain gets messed with during setStyleMask */
1819    if ([data->sdlContentView nextResponder] == data->listener) {
1820        [data->sdlContentView setNextResponder:nil];
1821    }
1822
1823    if (fullscreen) {
1824        SDL_Rect bounds;
1825
1826        Cocoa_GetDisplayBounds(_this, display, &bounds);
1827        rect.origin.x = bounds.x;
1828        rect.origin.y = bounds.y;
1829        rect.size.width = bounds.w;
1830        rect.size.height = bounds.h;
1831        ConvertNSRect([nswindow screen], fullscreen, &rect);
1832
1833        /* Hack to fix origin on Mac OS X 10.4
1834           This is no longer needed as of Mac OS X 10.15, according to bug 4822.
1835         */
1836        NSProcessInfo *processInfo = [NSProcessInfo processInfo];
1837#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 /* NSOperatingSystemVersion added in the 10.10 SDK */
1838        typedef struct {
1839            NSInteger majorVersion;
1840            NSInteger minorVersion;
1841            NSInteger patchVersion;
1842        } NSOperatingSystemVersion;
1843#endif
1844        NSOperatingSystemVersion version = { 10, 15, 0 };
1845        if (![processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] ||
1846            ![processInfo isOperatingSystemAtLeastVersion:version]) {
1847            NSRect screenRect = [[nswindow screen] frame];
1848            if (screenRect.size.height >= 1.0f) {
1849                rect.origin.y += (screenRect.size.height - rect.size.height);
1850            }
1851        }
1852
1853        [nswindow setStyleMask:NSWindowStyleMaskBorderless];
1854    } else {
1855        rect.origin.x = window->windowed.x;
1856        rect.origin.y = window->windowed.y;
1857        rect.size.width = window->windowed.w;
1858        rect.size.height = window->windowed.h;
1859        ConvertNSRect([nswindow screen], fullscreen, &rect);
1860
1861        /* The window is not meant to be fullscreen, but its flags might have a
1862         * fullscreen bit set if it's scheduled to go fullscreen immediately
1863         * after. Always using the windowed mode style here works around bugs in
1864         * macOS 10.15 where the window doesn't properly restore the windowed
1865         * mode decorations after exiting fullscreen-desktop, when the window
1866         * was created as fullscreen-desktop. */
1867        [nswindow setStyleMask:GetWindowWindowedStyle(window)];
1868
1869        /* Hack to restore window decorations on Mac OS X 10.10 */
1870        NSRect frameRect = [nswindow frame];
1871        [nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
1872        [nswindow setFrame:frameRect display:NO];
1873    }
1874
1875    /* The view responder chain gets messed with during setStyleMask */
1876    if ([data->sdlContentView nextResponder] != data->listener) {
1877        [data->sdlContentView setNextResponder:data->listener];
1878    }
1879
1880    s_moveHack = 0;
1881    [nswindow setContentSize:rect.size];
1882    [nswindow setFrameOrigin:rect.origin];
1883    s_moveHack = SDL_GetTicks();
1884
1885    /* When the window style changes the title is cleared */
1886    if (!fullscreen) {
1887        Cocoa_SetWindowTitle(_this, window);
1888    }
1889
1890    if (SDL_ShouldAllowTopmost() && fullscreen) {
1891        /* OpenGL is rendering to the window, so make it visible! */
1892        [nswindow setLevel:CGShieldingWindowLevel()];
1893    } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
1894        [nswindow setLevel:NSFloatingWindowLevel];
1895    } else {
1896        [nswindow setLevel:kCGNormalWindowLevel];
1897    }
1898
1899    if ([nswindow isVisible] || fullscreen) {
1900        [data->listener pauseVisibleObservation];
1901        [nswindow makeKeyAndOrderFront:nil];
1902        [data->listener resumeVisibleObservation];
1903    }
1904
1905    ScheduleContextUpdates(data);
1906}}
1907
1908int
1909Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
1910{
1911    SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
1912    CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
1913    const uint32_t tableSize = 256;
1914    CGGammaValue redTable[tableSize];
1915    CGGammaValue greenTable[tableSize];
1916    CGGammaValue blueTable[tableSize];
1917    uint32_t i;
1918    float inv65535 = 1.0f / 65535.0f;
1919
1920    /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
1921    for (i = 0; i < 256; i++) {
1922        redTable[i] = ramp[0*256+i] * inv65535;
1923        greenTable[i] = ramp[1*256+i] * inv65535;
1924        blueTable[i] = ramp[2*256+i] * inv65535;
1925    }
1926
1927    if (CGSetDisplayTransferByTable(display_id, tableSize,
1928                                    redTable, greenTable, blueTable) != CGDisplayNoErr) {
1929        return SDL_SetError("CGSetDisplayTransferByTable()");
1930    }
1931    return 0;
1932}
1933
1934int
1935Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
1936{
1937    SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
1938    CGDirectDisplayID display_id = ((SDL_DisplayData *)display->driverdata)->display;
1939    const uint32_t tableSize = 256;
1940    CGGammaValue redTable[tableSize];
1941    CGGammaValue greenTable[tableSize];
1942    CGGammaValue blueTable[tableSize];
1943    uint32_t i, tableCopied;
1944
1945    if (CGGetDisplayTransferByTable(display_id, tableSize,
1946                                    redTable, greenTable, blueTable, &tableCopied) != CGDisplayNoErr) {
1947        return SDL_SetError("CGGetDisplayTransferByTable()");
1948    }
1949
1950    for (i = 0; i < tableCopied; i++) {
1951        ramp[0*256+i] = (Uint16)(redTable[i] * 65535.0f);
1952        ramp[1*256+i] = (Uint16)(greenTable[i] * 65535.0f);
1953        ramp[2*256+i] = (Uint16)(blueTable[i] * 65535.0f);
1954    }
1955    return 0;
1956}
1957
1958void
1959Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
1960{
1961    SDL_Mouse *mouse = SDL_GetMouse();
1962    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
1963
1964    /* Enable or disable the event tap as necessary */
1965    Cocoa_EnableMouseEventTap(mouse->driverdata, grabbed);
1966
1967    /* Move the cursor to the nearest point in the window */
1968    if (grabbed && data && ![data->listener isMoving]) {
1969        int x, y;
1970        CGPoint cgpoint;
1971
1972        SDL_GetMouseState(&x, &y);
1973        cgpoint.x = window->x + x;
1974        cgpoint.y = window->y + y;
1975
1976        Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
1977
1978        DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
1979        CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
1980    }
1981
1982    if ( data && (window->flags & SDL_WINDOW_FULLSCREEN) ) {
1983        if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS)
1984            && ![data->listener isInFullscreenSpace]) {
1985            /* OpenGL is rendering to the window, so make it visible! */
1986            /* Doing this in 10.11 while in a Space breaks things (bug #3152) */
1987            [data->nswindow setLevel:CGShieldingWindowLevel()];
1988        } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
1989            [data->nswindow setLevel:NSFloatingWindowLevel];
1990        } else {
1991            [data->nswindow setLevel:kCGNormalWindowLevel];
1992        }
1993    }
1994}
1995
1996void
1997Cocoa_DestroyWindow(_THIS, SDL_Window * window)
1998{ @autoreleasepool
1999{
2000    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
2001
2002    if (data) {
2003        if ([data->listener isInFullscreenSpace]) {
2004            [NSMenu setMenuBarVisible:YES];
2005        }
2006        [data->listener close];
2007        [data->listener release];
2008        if (data->created) {
2009            /* Release the content view to avoid further updateLayer callbacks */
2010            [data->nswindow setContentView:nil];
2011            [data->nswindow close];
2012        }
2013
2014        NSArray *contexts = [[data->nscontexts copy] autorelease];
2015        for (SDLOpenGLContext *context in contexts) {
2016            /* Calling setWindow:NULL causes the context to remove itself from the context list. */
2017            [context setWindow:NULL];
2018        }
2019        [data->nscontexts release];
2020
2021        SDL_free(data);
2022    }
2023    window->driverdata = NULL;
2024}}
2025
2026SDL_bool
2027Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
2028{
2029    NSWindow *nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
2030
2031    if (info->version.major <= SDL_MAJOR_VERSION) {
2032        info->subsystem = SDL_SYSWM_COCOA;
2033        info->info.cocoa.window = nswindow;
2034        return SDL_TRUE;
2035    } else {
2036        SDL_SetError("Application not compiled with SDL %d.%d",
2037                     SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
2038        return SDL_FALSE;
2039    }
2040}
2041
2042SDL_bool
2043Cocoa_IsWindowInFullscreenSpace(SDL_Window * window)
2044{
2045    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
2046
2047    if ([data->listener isInFullscreenSpace]) {
2048        return SDL_TRUE;
2049    } else {
2050        return SDL_FALSE;
2051    }
2052}
2053
2054SDL_bool
2055Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state)
2056{ @autoreleasepool
2057{
2058    SDL_bool succeeded = SDL_FALSE;
2059    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
2060
2061    if (data->inWindowFullscreenTransition) {
2062        return SDL_FALSE;
2063    }
2064
2065    data->inWindowFullscreenTransition = SDL_TRUE;
2066    if ([data->listener setFullscreenSpace:(state ? YES : NO)]) {
2067        const int maxattempts = 3;
2068        int attempt = 0;
2069        while (++attempt <= maxattempts) {
2070            /* Wait for the transition to complete, so application changes
2071             take effect properly (e.g. setting the window size, etc.)
2072             */
2073            const int limit = 10000;
2074            int count = 0;
2075            while ([data->listener isInFullscreenSpaceTransition]) {
2076                if ( ++count == limit ) {
2077                    /* Uh oh, transition isn't completing. Should we assert? */
2078                    break;
2079                }
2080                SDL_Delay(1);
2081                SDL_PumpEvents();
2082            }
2083            if ([data->listener isInFullscreenSpace] == (state ? YES : NO))
2084                break;
2085            /* Try again, the last attempt was interrupted by user gestures */
2086            if (![data->listener setFullscreenSpace:(state ? YES : NO)])
2087                break; /* ??? */
2088        }
2089        /* Return TRUE to prevent non-space fullscreen logic from running */
2090        succeeded = SDL_TRUE;
2091    }
2092    data->inWindowFullscreenTransition = SDL_FALSE;
2093
2094    return succeeded;
2095}}
2096
2097int
2098Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
2099{
2100    return 0;  /* just succeed, the real work is done elsewhere. */
2101}
2102
2103void
2104Cocoa_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept)
2105{
2106    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
2107    if (accept) {
2108        [data->nswindow registerForDraggedTypes:[NSArray arrayWithObject:(NSString *)kUTTypeFileURL]];
2109    } else {
2110        [data->nswindow unregisterDraggedTypes];
2111    }
2112}
2113
2114int
2115Cocoa_SetWindowOpacity(_THIS, SDL_Window * window, float opacity)
2116{
2117    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
2118    [data->nswindow setAlphaValue:opacity];
2119    return 0;
2120}
2121
2122#endif /* SDL_VIDEO_DRIVER_COCOA */
2123
2124/* vi: set ts=4 sw=4 expandtab: */
2125