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