#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_COCOA
#include <float.h>
#include "../../events/SDL_dropevents_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_touch_c.h"
#include "../../events/SDL_windowevents_c.h"
#include "../SDL_sysvideo.h"
#include "SDL_cocoamouse.h"
#include "SDL_cocoaopengl.h"
#include "SDL_cocoaopengles.h"
#include "SDL_cocoavideo.h"
#if 0#endif
#ifdef DEBUG_COCOAWINDOW
#define DLog(fmt, ...) printf("%s: " fmt "\n", SDL_FUNCTION, ##__VA_ARGS__)
#else
#define DLog(...) \
do { \
} while (0)
#endif
#ifndef MAC_OS_X_VERSION_10_12
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
#endif
#ifndef NSAppKitVersionNumber10_13_2
#define NSAppKitVersionNumber10_13_2 1561.2
#endif
#ifndef NSAppKitVersionNumber10_14
#define NSAppKitVersionNumber10_14 1671
#endif
@implementation SDL_CocoaWindowData
@end
@interface NSScreen (SDL)
#if MAC_OS_X_VERSION_MAX_ALLOWED < 120000
@property(readonly) NSEdgeInsets safeAreaInsets;
#endif
@end
@interface NSWindow (SDL)
@property(nonatomic) NSRect mouseConfinementRect;
@end
@interface SDL3Window : NSWindow <NSDraggingDestination>
- (BOOL)canBecomeKeyWindow;
- (BOOL)canBecomeMainWindow;
- (void)sendEvent:(NSEvent *)event;
- (void)doCommandBySelector:(SEL)aSelector;
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender;
- (void)draggingExited:(id<NSDraggingInfo>)sender;
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender;
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender;
- (BOOL)wantsPeriodicDraggingUpdates;
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
- (SDL_Window *)findSDLWindow;
@end
@implementation SDL3Window
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if ([menuItem action] == @selector(toggleFullScreen:)) {
SDL_Window *window = [self findSDLWindow];
if (!window) {
return NO;
}
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if ((window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpace]) {
return NO;
} else if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
return NO;
}
}
return [super validateMenuItem:menuItem];
}
- (BOOL)canBecomeKeyWindow
{
SDL_Window *window = [self findSDLWindow];
if (window && !(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_NOT_FOCUSABLE))) {
return YES;
} else {
return NO;
}
}
- (BOOL)canBecomeMainWindow
{
SDL_Window *window = [self findSDLWindow];
if (window && !(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_NOT_FOCUSABLE)) && !SDL_WINDOW_IS_POPUP(window)) {
return YES;
} else {
return NO;
}
}
- (void)sendEvent:(NSEvent *)event
{
id delegate;
[super sendEvent:event];
if ([event type] != NSEventTypeLeftMouseUp) {
return;
}
delegate = [self delegate];
if (![delegate isKindOfClass:[SDL3Cocoa_WindowListener class]]) {
return;
}
if ([delegate isMoving]) {
[delegate windowDidFinishMoving];
}
}
- (void)doCommandBySelector:(SEL)aSelector
{
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) {
return NSDragOperationGeneric;
} else if (([sender draggingSourceOperationMask] & NSDragOperationCopy) == NSDragOperationCopy) {
return NSDragOperationCopy;
}
return NSDragOperationNone; }
- (void)draggingExited:(id<NSDraggingInfo>)sender
{
SDL_Window *sdlwindow = [self findSDLWindow];
SDL_SendDropComplete(sdlwindow);
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) {
SDL_Window *sdlwindow = [self findSDLWindow];
NSPoint point = [sender draggingLocation];
float x, y;
x = point.x;
y = (sdlwindow->h - point.y);
SDL_SendDropPosition(sdlwindow, x, y);
return NSDragOperationGeneric;
} else if (([sender draggingSourceOperationMask] & NSDragOperationCopy) == NSDragOperationCopy) {
SDL_Window *sdlwindow = [self findSDLWindow];
NSPoint point = [sender draggingLocation];
float x, y;
x = point.x;
y = (sdlwindow->h - point.y);
SDL_SendDropPosition(sdlwindow, x, y);
return NSDragOperationCopy;
}
return NSDragOperationNone; }
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
". [SDL] In performDragOperation, draggingSourceOperationMask %lx, "
"expected Generic %lx, others Copy %lx, Link %lx, Private %lx, Move %lx, Delete %lx\n",
(unsigned long)[sender draggingSourceOperationMask],
(unsigned long)NSDragOperationGeneric,
(unsigned long)NSDragOperationCopy,
(unsigned long)NSDragOperationLink,
(unsigned long)NSDragOperationPrivate,
(unsigned long)NSDragOperationMove,
(unsigned long)NSDragOperationDelete);
if ([sender draggingPasteboard]) {
SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
". [SDL] In performDragOperation, valid draggingPasteboard, "
"name [%s] '%s', changeCount %ld\n",
[[[[sender draggingPasteboard] name] className] UTF8String],
[[[[sender draggingPasteboard] name] description] UTF8String],
(long)[[sender draggingPasteboard] changeCount]);
}
@autoreleasepool {
NSPasteboard *pasteboard = [sender draggingPasteboard];
NSString *desiredType = [pasteboard availableTypeFromArray:@[ NSFilenamesPboardType, NSPasteboardTypeString ]];
SDL_Window *sdlwindow = [self findSDLWindow];
NSData *pboardData;
id pboardPlist;
NSString *pboardString;
NSPoint point;
float x, y;
for (NSString *supportedType in [pasteboard types]) {
NSString *typeString = [pasteboard stringForType:supportedType];
SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
". [SDL] In performDragOperation, Pasteboard type '%s', stringForType (%lu) '%s'\n",
[[supportedType description] UTF8String],
(unsigned long)[[typeString description] length],
[[typeString description] UTF8String]);
}
if (desiredType == nil) {
return NO; }
pboardData = [pasteboard dataForType:desiredType];
if (pboardData == nil) {
return NO;
}
SDL_assert([desiredType isEqualToString:NSFilenamesPboardType] ||
[desiredType isEqualToString:NSPasteboardTypeString]);
pboardString = [pasteboard stringForType:desiredType];
pboardPlist = [pasteboard propertyListForType:desiredType];
point = [sender draggingLocation];
x = point.x;
y = (sdlwindow->h - point.y);
if (x >= 0.0f && x < (float)sdlwindow->w && y >= 0.0f && y < (float)sdlwindow->h) {
SDL_SendDropPosition(sdlwindow, x, y);
}
if ([desiredType isEqualToString:NSFilenamesPboardType]) {
for (NSString *path in (NSArray *)pboardPlist) {
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSNumber *isAlias = nil;
[fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil];
if ([isAlias boolValue]) {
NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting |
NSURLBookmarkResolutionWithoutUI;
NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil];
if (bookmark != nil) {
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark
options:opts
relativeToURL:nil
bookmarkDataIsStale:nil
error:nil];
if (resolvedURL != nil) {
fileURL = resolvedURL;
}
}
}
SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
". [SDL] In performDragOperation, desiredType '%s', "
"Submitting DropFile as (%lu) '%s'\n",
[[desiredType description] UTF8String],
(unsigned long)[[[fileURL path] description] length],
[[[fileURL path] description] UTF8String]);
if (!SDL_SendDropFile(sdlwindow, NULL, [[[fileURL path] description] UTF8String])) {
return NO;
}
}
} else if ([desiredType isEqualToString:NSPasteboardTypeString]) {
char *buffer = SDL_strdup([[pboardString description] UTF8String]);
char *saveptr = NULL;
char *token = SDL_strtok_r(buffer, "\r\n", &saveptr);
while (token) {
SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
". [SDL] In performDragOperation, desiredType '%s', "
"Submitting DropText as (%lu) '%s'\n",
[[desiredType description] UTF8String],
SDL_strlen(token), token);
if (!SDL_SendDropText(sdlwindow, token)) {
SDL_free(buffer);
return NO;
}
token = SDL_strtok_r(NULL, "\r\n", &saveptr);
}
SDL_free(buffer);
}
SDL_SendDropComplete(sdlwindow);
return YES;
}
}
- (BOOL)wantsPeriodicDraggingUpdates
{
return NO;
}
- (SDL_Window *)findSDLWindow
{
SDL_Window *sdlwindow = NULL;
SDL_VideoDevice *_this = SDL_GetVideoDevice();
if (_this) {
for (sdlwindow = _this->windows; sdlwindow; sdlwindow = sdlwindow->next) {
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow;
if (nswindow == self) {
break;
}
}
}
return sdlwindow;
}
@end
bool b_inModeTransition;
static CGFloat SqDistanceToRect(const NSPoint *point, const NSRect *rect)
{
NSPoint edge = *point;
CGFloat left = NSMinX(*rect), right = NSMaxX(*rect);
CGFloat bottom = NSMinX(*rect), top = NSMaxY(*rect);
NSPoint delta;
if (point->x < left) {
edge.x = left;
} else if (point->x > right) {
edge.x = right;
}
if (point->y < bottom) {
edge.y = bottom;
} else if (point->y > top) {
edge.y = top;
}
delta = NSMakePoint(edge.x - point->x, edge.y - point->y);
return delta.x * delta.x + delta.y * delta.y;
}
static NSScreen *ScreenForPoint(const NSPoint *point)
{
NSScreen *screen;
for (NSScreen *candidate in [NSScreen screens]) {
if (NSPointInRect(*point, [candidate frame])) {
screen = candidate;
break;
}
}
if (!screen) {
CGFloat closest = MAXFLOAT;
for (NSScreen *candidate in [NSScreen screens]) {
NSRect screenRect = [candidate frame];
CGFloat sqdist = SqDistanceToRect(point, &screenRect);
if (sqdist < closest) {
screen = candidate;
closest = sqdist;
}
}
}
return screen;
}
bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if ([data.listener isInFullscreenSpace]) {
return true;
} else {
return false;
}
}
}
bool Cocoa_IsWindowInFullscreenSpaceTransition(SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if ([data.listener isInFullscreenSpaceTransition]) {
return true;
} else {
return false;
}
}
}
bool Cocoa_IsWindowZoomed(SDL_Window *window)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
bool zoomed = false;
if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] &&
!(window->flags & SDL_WINDOW_FULLSCREEN) && !Cocoa_IsWindowInFullscreenSpace(window)) {
bool floating = (window->x == window->floating.x &&
window->y == window->floating.y &&
window->w == window->floating.w &&
window->h == window->floating.h);
if (!floating) {
zoomed = true;
}
}
return zoomed;
}
bool Cocoa_IsShowingModalDialog(SDL_Window *window)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
return data.has_modal_dialog;
}
void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
data.has_modal_dialog = has_modal;
}
typedef enum CocoaMenuVisibility
{
COCOA_MENU_VISIBILITY_AUTO = 0,
COCOA_MENU_VISIBILITY_NEVER,
COCOA_MENU_VISIBILITY_ALWAYS
} CocoaMenuVisibility;
static CocoaMenuVisibility menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO;
static void Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_Window *window)
{
if (window && Cocoa_IsWindowInFullscreenSpace(window)) {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if ((menu_visibility_hint == COCOA_MENU_VISIBILITY_AUTO && !data.fullscreen_space_requested) ||
menu_visibility_hint == COCOA_MENU_VISIBILITY_ALWAYS) {
[NSMenu setMenuBarVisible:YES];
} else {
[NSMenu setMenuBarVisible:NO];
}
}
}
void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
if (newValue) {
if (*newValue == '0' || SDL_strcasecmp(newValue, "false") == 0) {
menu_visibility_hint = COCOA_MENU_VISIBILITY_NEVER;
} else if (*newValue == '1' || SDL_strcasecmp(newValue, "true") == 0) {
menu_visibility_hint = COCOA_MENU_VISIBILITY_ALWAYS;
} else {
menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO;
}
} else {
menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO;
}
Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_GetKeyboardFocus());
}
static NSScreen *ScreenForRect(const NSRect *rect)
{
NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect));
return ScreenForPoint(¢er);
}
static void ConvertNSRect(NSRect *r)
{
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)SDL_GetVideoDevice()->internal;
r->origin.y = videodata.mainDisplayHeight - r->origin.y - r->size.height;
}
static void ScheduleContextUpdates(SDL_CocoaWindowData *data)
{
#ifdef SDL_VIDEO_OPENGL
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
NSOpenGLContext *currentContext;
NSMutableArray *contexts;
if (!data || !data.nscontexts) {
return;
}
currentContext = [NSOpenGLContext currentContext];
contexts = data.nscontexts;
@synchronized(contexts) {
for (SDL3OpenGLContext *context in contexts) {
if (context == currentContext) {
[context update];
} else {
[context scheduleUpdate];
}
}
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif }
static bool GetHintCtrlClickEmulateRightClick(void)
{
return SDL_GetHintBoolean(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, false);
}
static NSUInteger GetWindowWindowedStyle(SDL_Window *window)
{
NSUInteger style = NSWindowStyleMaskMiniaturizable;
if (!SDL_WINDOW_IS_POPUP(window)) {
if (window->flags & SDL_WINDOW_BORDERLESS) {
style |= NSWindowStyleMaskBorderless;
} else {
style |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
}
if (window->flags & SDL_WINDOW_RESIZABLE) {
style |= NSWindowStyleMaskResizable;
}
} else {
style |= NSWindowStyleMaskBorderless;
}
return style;
}
static NSUInteger GetWindowStyle(SDL_Window *window)
{
NSUInteger style = 0;
if (window->flags & SDL_WINDOW_FULLSCREEN) {
style = NSWindowStyleMaskBorderless;
} else {
style = GetWindowWindowedStyle(window);
}
return style;
}
static bool SetWindowStyle(SDL_Window *window, NSUInteger style)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
if ([data.sdlContentView nextResponder] == data.listener) {
[data.sdlContentView setNextResponder:nil];
}
[nswindow setStyleMask:style];
if ([data.sdlContentView nextResponder] != data.listener) {
[data.sdlContentView setNextResponder:data.listener];
}
return true;
}
static bool ShouldAdjustCoordinatesForGrab(SDL_Window *window)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (!data || [data.listener isMovingOrFocusClickPending]) {
return false;
}
if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
return false;
}
if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) || (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) {
return true;
}
return false;
}
static bool AdjustCoordinatesForGrab(SDL_Window *window, float x, float y, CGPoint *adjusted)
{
if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
SDL_Rect window_rect;
SDL_Rect mouse_rect;
window_rect.x = 0;
window_rect.y = 0;
window_rect.w = window->w;
window_rect.h = window->h;
if (SDL_GetRectIntersection(&window->mouse_rect, &window_rect, &mouse_rect)) {
float left = (float)window->x + mouse_rect.x;
float right = left + mouse_rect.w - 1;
float top = (float)window->y + mouse_rect.y;
float bottom = top + mouse_rect.h - 1;
if (x < left || x > right || y < top || y > bottom) {
adjusted->x = SDL_clamp(x, left, right);
adjusted->y = SDL_clamp(y, top, bottom);
return true;
}
return false;
}
}
if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
float left = (float)window->x;
float right = left + window->w - 1;
float top = (float)window->y;
float bottom = top + window->h - 1;
if (x < left || x > right || y < top || y > bottom) {
adjusted->x = SDL_clamp(x, left, right);
adjusted->y = SDL_clamp(y, top, bottom);
return true;
}
}
return false;
}
static void Cocoa_UpdateClipCursor(SDL_Window *window)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_13_2) {
NSWindow *nswindow = data.nswindow;
SDL_Rect mouse_rect;
SDL_zero(mouse_rect);
if (ShouldAdjustCoordinatesForGrab(window)) {
SDL_Rect window_rect;
window_rect.x = 0;
window_rect.y = 0;
window_rect.w = window->w;
window_rect.h = window->h;
if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
SDL_GetRectIntersection(&window->mouse_rect, &window_rect, &mouse_rect);
}
if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0 &&
SDL_RectEmpty(&mouse_rect)) {
SDL_memcpy(&mouse_rect, &window_rect, sizeof(mouse_rect));
}
}
if (SDL_RectEmpty(&mouse_rect)) {
nswindow.mouseConfinementRect = NSZeroRect;
} else {
NSRect rect;
rect.origin.x = mouse_rect.x;
rect.origin.y = [nswindow contentLayoutRect].size.height - mouse_rect.y - mouse_rect.h;
rect.size.width = mouse_rect.w;
rect.size.height = mouse_rect.h;
nswindow.mouseConfinementRect = rect;
}
} else {
if (ShouldAdjustCoordinatesForGrab(window)) {
float x, y;
CGPoint cgpoint;
SDL_GetGlobalMouseState(&x, &y);
if (AdjustCoordinatesForGrab(window, x, y, &cgpoint)) {
Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
}
}
}
}
static SDL_Window *GetParentToplevelWindow(SDL_Window *window)
{
SDL_Window *toplevel = window;
while (SDL_WINDOW_IS_POPUP(toplevel)) {
toplevel = toplevel->parent;
}
return toplevel;
}
static void Cocoa_SetKeyboardFocus(SDL_Window *window, bool set_active_focus)
{
SDL_Window *toplevel = GetParentToplevelWindow(window);
toplevel->keyboard_focus = window;
if (set_active_focus && !window->is_hiding && !window->is_destroying) {
SDL_SetKeyboardFocus(window);
}
}
static void Cocoa_SendExposedEventIfVisible(SDL_Window *window)
{
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow;
if ([nswindow occlusionState] & NSWindowOcclusionStateVisible) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
}
}
static void Cocoa_WaitForMiniaturizable(SDL_Window *window)
{
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow;
NSButton *button = [nswindow standardWindowButton:NSWindowMiniaturizeButton];
if (button) {
int iterations = 0;
while (![button isEnabled] && (iterations < 100)) {
SDL_Delay(10);
SDL_PumpEvents();
iterations++;
}
}
}
static void Cocoa_IncrementCursorFrame(void)
{
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse->cur_cursor) {
SDL_CursorData *cdata = mouse->cur_cursor->internal;
cdata->current_frame = (cdata->current_frame + 1) % cdata->num_cursors;
SDL_Window *focus = SDL_GetMouseFocus();
if (focus) {
SDL_CocoaWindowData *_data = (__bridge SDL_CocoaWindowData *)focus->internal;
[_data.nswindow invalidateCursorRectsForView:_data.sdlContentView];
}
}
}
static NSCursor *Cocoa_GetDesiredCursor(void)
{
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse->cursor_visible && mouse->cur_cursor && !mouse->relative_mode) {
SDL_CursorData *cdata = mouse->cur_cursor->internal;
if (cdata) {
if (cdata->num_cursors > 1 && cdata->frames[cdata->current_frame].duration && !cdata->frameTimer) {
const NSTimeInterval interval = cdata->frames[cdata->current_frame].duration * 0.001;
cdata->frameTimer = [NSTimer timerWithTimeInterval:interval
repeats:NO
block:^(NSTimer *timer) {
cdata->frameTimer = nil;
Cocoa_IncrementCursorFrame();
}];
[[NSRunLoop currentRunLoop] addTimer:cdata->frameTimer forMode:NSRunLoopCommonModes];
}
return (__bridge NSCursor *)cdata->frames[cdata->current_frame].cursor;
}
}
return [NSCursor invisibleCursor];
}
@implementation SDL3Cocoa_WindowListener
- (void)listen:(SDL_CocoaWindowData *)data
{
NSNotificationCenter *center;
NSWindow *window = data.nswindow;
NSView *view = data.sdlContentView;
_data = data;
observingVisible = YES;
wasCtrlLeft = NO;
wasVisible = [window isVisible];
isFullscreenSpace = NO;
inFullscreenTransition = NO;
pendingWindowOperation = PENDING_OPERATION_NONE;
isMoving = NO;
isMiniaturizing = NO;
isDragAreaRunning = NO;
pendingWindowWarpX = pendingWindowWarpY = FLT_MAX;
liveResizeTimer = nil;
center = [NSNotificationCenter defaultCenter];
if ([window delegate] != nil) {
[center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window];
[center addObserver:self selector:@selector(windowDidChangeOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:window];
[center addObserver:self selector:@selector(windowWillStartLiveResize:) name:NSWindowWillStartLiveResizeNotification object:window];
[center addObserver:self selector:@selector(windowDidEndLiveResize:) name:NSWindowDidEndLiveResizeNotification object:window];
[center addObserver:self selector:@selector(windowWillMove:) name:NSWindowWillMoveNotification object:window];
[center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window];
[center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window];
[center addObserver:self selector:@selector(windowWillMiniaturize:) name:NSWindowWillMiniaturizeNotification object:window];
[center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window];
[center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window];
[center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window];
[center addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window];
[center addObserver:self selector:@selector(windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window];
[center addObserver:self selector:@selector(windowDidChangeScreenProfile:) name:NSWindowDidChangeScreenProfileNotification object:window];
[center addObserver:self selector:@selector(windowDidChangeScreen:) name:NSWindowDidChangeScreenNotification object:window];
[center addObserver:self selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window];
[center addObserver:self selector:@selector(windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window];
[center addObserver:self selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window];
[center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window];
[center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
[center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
} else {
[window setDelegate:self];
}
[window addObserver:self
forKeyPath:@"visible"
options:NSKeyValueObservingOptionNew
context:NULL];
[window setNextResponder:self];
[window setAcceptsMouseMovedEvents:YES];
[view setNextResponder:self];
[view setAcceptsTouchEvents:YES];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (!observingVisible) {
return;
}
if (object == _data.nswindow && [keyPath isEqualToString:@"visible"]) {
int newVisibility = [[change objectForKey:@"new"] intValue];
if (newVisibility) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
} else if (![_data.nswindow isMiniaturized]) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
}
}
}
- (void)pauseVisibleObservation
{
observingVisible = NO;
wasVisible = [_data.nswindow isVisible];
}
- (void)resumeVisibleObservation
{
BOOL isVisible = [_data.nswindow isVisible];
observingVisible = YES;
if (wasVisible != isVisible) {
if (isVisible) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
} else if (![_data.nswindow isMiniaturized]) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
}
wasVisible = isVisible;
}
}
- (BOOL)setFullscreenSpace:(BOOL)state
{
SDL_Window *window = _data.window;
NSWindow *nswindow = _data.nswindow;
SDL_CocoaVideoData *videodata = ((__bridge SDL_CocoaWindowData *)window->internal).videodata;
if (!videodata.allow_spaces) {
return NO; } else if (state && window->fullscreen_exclusive) {
return NO; } else if (!state && window->last_fullscreen_exclusive_display) {
return NO; } else if (state == isFullscreenSpace && !inFullscreenTransition) {
return YES; }
if (inFullscreenTransition) {
if (state) {
[self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
[self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
} else {
[self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
[self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
}
return YES;
}
inFullscreenTransition = YES;
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
[nswindow performSelectorOnMainThread:@selector(toggleFullScreen:) withObject:nswindow waitUntilDone:NO];
return YES;
}
- (BOOL)isInFullscreenSpace
{
return isFullscreenSpace;
}
- (BOOL)isInFullscreenSpaceTransition
{
return inFullscreenTransition;
}
- (void)clearPendingWindowOperation:(PendingWindowOperation)operation
{
pendingWindowOperation &= ~operation;
}
- (void)clearAllPendingWindowOperations
{
pendingWindowOperation = PENDING_OPERATION_NONE;
}
- (void)addPendingWindowOperation:(PendingWindowOperation)operation
{
pendingWindowOperation |= operation;
}
- (BOOL)windowOperationIsPending:(PendingWindowOperation)operation
{
return !!(pendingWindowOperation & operation);
}
- (BOOL)hasPendingWindowOperation
{
return (pendingWindowOperation & ~PENDING_OPERATION_ZOOM) != PENDING_OPERATION_NONE ||
isMiniaturizing || inFullscreenTransition;
}
- (void)close
{
NSNotificationCenter *center;
NSWindow *window = _data.nswindow;
NSView *view = [window contentView];
center = [NSNotificationCenter defaultCenter];
if ([window delegate] != self) {
[center removeObserver:self name:NSWindowDidExposeNotification object:window];
[center removeObserver:self name:NSWindowDidChangeOcclusionStateNotification object:window];
[center removeObserver:self name:NSWindowWillStartLiveResizeNotification object:window];
[center removeObserver:self name:NSWindowDidEndLiveResizeNotification object:window];
[center removeObserver:self name:NSWindowWillMoveNotification object:window];
[center removeObserver:self name:NSWindowDidMoveNotification object:window];
[center removeObserver:self name:NSWindowDidResizeNotification object:window];
[center removeObserver:self name:NSWindowWillMiniaturizeNotification object:window];
[center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window];
[center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window];
[center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window];
[center removeObserver:self name:NSWindowDidResignKeyNotification object:window];
[center removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window];
[center removeObserver:self name:NSWindowDidChangeScreenProfileNotification object:window];
[center removeObserver:self name:NSWindowDidChangeScreenNotification object:window];
[center removeObserver:self name:NSWindowWillEnterFullScreenNotification object:window];
[center removeObserver:self name:NSWindowDidEnterFullScreenNotification object:window];
[center removeObserver:self name:NSWindowWillExitFullScreenNotification object:window];
[center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window];
[center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window];
[center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window];
} else {
[window setDelegate:nil];
}
[window removeObserver:self forKeyPath:@"visible"];
if ([window nextResponder] == self) {
[window setNextResponder:nil];
}
if ([view nextResponder] == self) {
[view setNextResponder:nil];
}
}
- (BOOL)isMoving
{
return isMoving;
}
- (BOOL)isMovingOrFocusClickPending
{
return isMoving || (focusClickPending != 0);
}
- (void)setFocusClickPending:(NSInteger)button
{
focusClickPending |= (1 << button);
}
- (void)clearFocusClickPending:(NSInteger)button
{
if (focusClickPending & (1 << button)) {
focusClickPending &= ~(1 << button);
if (focusClickPending == 0) {
[self onMovingOrFocusClickPendingStateCleared];
}
}
}
- (void)updateIgnoreMouseState:(NSEvent *)theEvent
{
SDL_Window *window = _data.window;
SDL_Surface *shape = (SDL_Surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
BOOL ignoresMouseEvents = NO;
if (shape) {
NSPoint point = [theEvent locationInWindow];
NSRect windowRect = [[_data.nswindow contentView] frame];
if (NSMouseInRect(point, windowRect, NO)) {
int x = (int)SDL_roundf((point.x / (window->w - 1)) * (shape->w - 1));
int y = (int)SDL_roundf(((window->h - point.y) / (window->h - 1)) * (shape->h - 1));
Uint8 a;
if (!SDL_ReadSurfacePixel(shape, x, y, NULL, NULL, NULL, &a) || a == SDL_ALPHA_TRANSPARENT) {
ignoresMouseEvents = YES;
}
}
}
_data.nswindow.ignoresMouseEvents = ignoresMouseEvents;
}
- (void)setPendingMoveX:(float)x Y:(float)y
{
pendingWindowWarpX = x;
pendingWindowWarpY = y;
}
- (void)windowDidFinishMoving
{
if (isMoving) {
isMoving = NO;
[self onMovingOrFocusClickPendingStateCleared];
}
}
- (void)onMovingOrFocusClickPendingStateCleared
{
if (![self isMovingOrFocusClickPending]) {
SDL_Mouse *mouse = SDL_GetMouse();
if (pendingWindowWarpX != FLT_MAX && pendingWindowWarpY != FLT_MAX) {
mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY);
pendingWindowWarpX = pendingWindowWarpY = FLT_MAX;
}
if (mouse->relative_mode && mouse->focus == _data.window) {
{
float x, y;
CGPoint cgpoint;
SDL_GetMouseState(&x, &y);
cgpoint.x = _data.window->x + x;
cgpoint.y = _data.window->y + y;
Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
}
mouse->SetRelativeMouseMode(true);
} else {
Cocoa_UpdateClipCursor(_data.window);
}
}
}
- (BOOL)windowShouldClose:(id)sender
{
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
return NO;
}
- (void)windowDidExpose:(NSNotification *)aNotification
{
Cocoa_SendExposedEventIfVisible(_data.window);
}
- (void)windowDidChangeOcclusionState:(NSNotification *)aNotification
{
if ([_data.nswindow occlusionState] & NSWindowOcclusionStateVisible) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
} else {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0);
}
}
- (void)windowWillStartLiveResize:(NSNotification *)aNotification
{
const NSTimeInterval interval = 1.0 / 60.0;
liveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:interval
repeats:TRUE
block:^(NSTimer *unusedTimer)
{
SDL_OnWindowLiveResizeUpdate(_data.window);
}];
[[NSRunLoop currentRunLoop] addTimer:liveResizeTimer forMode:NSRunLoopCommonModes];
}
- (void)windowDidEndLiveResize:(NSNotification *)aNotification
{
[liveResizeTimer invalidate];
liveResizeTimer = nil;
}
- (void)windowWillMove:(NSNotification *)aNotification
{
if ([_data.nswindow isKindOfClass:[SDL3Window class]]) {
pendingWindowWarpX = pendingWindowWarpY = FLT_MAX;
isMoving = YES;
}
}
- (void)windowDidMove:(NSNotification *)aNotification
{
int x, y;
SDL_Window *window = _data.window;
NSWindow *nswindow = _data.nswindow;
NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
ConvertNSRect(&rect);
if (inFullscreenTransition || b_inModeTransition) {
return;
}
x = (int)rect.origin.x;
y = (int)rect.origin.y;
ScheduleContextUpdates(_data);
SDL_GlobalToRelativeForWindow(window, x, y, &x, &y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
}
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
{
SDL_Window *window = _data.window;
if (window->min_aspect != window->max_aspect) {
NSWindow *nswindow = _data.nswindow;
NSRect newContentRect = [nswindow contentRectForFrameRect:NSMakeRect(0, 0, frameSize.width, frameSize.height)];
NSSize newSize = newContentRect.size;
CGFloat minAspectRatio = window->min_aspect;
CGFloat maxAspectRatio = window->max_aspect;
CGFloat aspectRatio;
if (newSize.height > 0) {
aspectRatio = newSize.width / newSize.height;
if (maxAspectRatio > 0.0f && aspectRatio > maxAspectRatio) {
newSize.width = SDL_roundf(newSize.height * maxAspectRatio);
} else if (minAspectRatio > 0.0f && aspectRatio < minAspectRatio) {
newSize.height = SDL_roundf(newSize.width / minAspectRatio);
}
NSRect newFrameRect = [nswindow frameRectForContentRect:NSMakeRect(0, 0, newSize.width, newSize.height)];
frameSize = newFrameRect.size;
}
}
return frameSize;
}
- (void)windowDidResize:(NSNotification *)aNotification
{
SDL_Window *window;
NSWindow *nswindow;
NSRect rect;
int x, y, w, h;
BOOL zoomed;
if (inFullscreenTransition || b_inModeTransition) {
return;
}
if (focusClickPending) {
focusClickPending = 0;
[self onMovingOrFocusClickPendingStateCleared];
}
window = _data.window;
nswindow = _data.nswindow;
rect = [nswindow contentRectForFrameRect:[nswindow frame]];
ConvertNSRect(&rect);
x = (int)rect.origin.x;
y = (int)rect.origin.y;
w = (int)rect.size.width;
h = (int)rect.size.height;
_data.viewport = [_data.sdlContentView bounds];
if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
_data.viewport = [_data.sdlContentView convertRectToBacking:_data.viewport];
}
ScheduleContextUpdates(_data);
if ([nswindow isVisible])
{
if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] &&
!(window->flags & SDL_WINDOW_FULLSCREEN) && ![self isInFullscreenSpace]) {
zoomed = YES;
} else {
zoomed = NO;
}
if (!zoomed) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
} else {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) {
[nswindow miniaturize:nil];
}
}
}
SDL_GlobalToRelativeForWindow(window, x, y, &x, &y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h);
}
- (void)windowWillMiniaturize:(NSNotification *)aNotification
{
isMiniaturizing = YES;
Cocoa_WaitForMiniaturizable(_data.window);
}
- (void)windowDidMiniaturize:(NSNotification *)aNotification
{
if (focusClickPending) {
focusClickPending = 0;
[self onMovingOrFocusClickPendingStateCleared];
}
isMiniaturizing = NO;
[self clearPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
}
- (void)windowDidDeminiaturize:(NSNotification *)aNotification
{
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
if (Cocoa_IsWindowZoomed(_data.window)) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
}
if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) {
SDL_UpdateFullscreenMode(_data.window, true, true);
}
}
- (void)windowDidBecomeKey:(NSNotification *)aNotification
{
SDL_Window *window = _data.window;
Cocoa_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window, true);
if (!(window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) {
NSPoint point;
float x, y;
point = [_data.nswindow mouseLocationOutsideOfEventStream];
x = point.x;
y = (window->h - point.y);
if (x >= 0.0f && x < (float)window->w && y >= 0.0f && y < (float)window->h) {
SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
}
}
Cocoa_CheckClipboardUpdate(_data.videodata);
if (isFullscreenSpace && !window->fullscreen_exclusive) {
Cocoa_ToggleFullscreenSpaceMenuVisibility(window);
}
{
const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock;
_data.videodata.modifierFlags = (_data.videodata.modifierFlags & ~NSEventModifierFlagCapsLock) | newflags;
SDL_ToggleModState(SDL_KMOD_CAPS, newflags ? true : false);
}
if (!(window->flags & SDL_WINDOW_MINIMIZED) &&
[self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) {
SDL_UpdateFullscreenMode(window, true, true);
}
}
- (void)windowDidResignKey:(NSNotification *)aNotification
{
if (SDL_GetMouseFocus() == _data.window) {
SDL_SetMouseFocus(NULL);
}
if (SDL_GetKeyboardFocus() == _data.window) {
SDL_SetKeyboardFocus(NULL);
}
if (isFullscreenSpace) {
[NSMenu setMenuBarVisible:YES];
}
}
- (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
{
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)_data.window->internal;
NSView *contentView = windata.sdlContentView;
NSNumber *oldscale = [[aNotification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey];
if (inFullscreenTransition) {
return;
}
if ([oldscale doubleValue] != [_data.nswindow backingScaleFactor]) {
contentView.layer.contentsScale = [_data.nswindow backingScaleFactor];
[self windowDidResize:aNotification];
}
}
- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification
{
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
}
- (void)windowDidChangeScreen:(NSNotification *)aNotification
{
#ifdef SDL_VIDEO_OPENGL
if (_data && _data.nscontexts) {
for (SDL3OpenGLContext *context in _data.nscontexts) {
[context movedToNewScreen];
}
}
#endif }
- (void)windowWillEnterFullScreen:(NSNotification *)aNotification
{
SDL_Window *window = _data.window;
const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled;
SetWindowStyle(window, flags);
_data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED);
isFullscreenSpace = YES;
inFullscreenTransition = YES;
}
- (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
{
[self clearAllPendingWindowOperations];
}
- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
{
SDL_Window *window = _data.window;
inFullscreenTransition = NO;
isFullscreenSpace = YES;
[self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) {
[self setFullscreenSpace:NO];
} else {
Cocoa_ToggleFullscreenSpaceMenuVisibility(window);
if (!_data.in_blocking_transition) {
SDL_UpdateFullscreenMode(window, true, false);
}
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
_data.pending_position = NO;
_data.pending_size = NO;
window->w = 0;
window->h = 0;
[self windowDidMove:aNotification];
[self windowDidResize:aNotification];
Cocoa_UpdateClipCursor(window);
}
}
- (void)windowWillExitFullScreen:(NSNotification *)aNotification
{
SDL_Window *window = _data.window;
if (_data.border_toggled && !(window->flags & SDL_WINDOW_BORDERLESS)) {
const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
SetWindowStyle(window, flags);
_data.border_toggled = false;
}
isFullscreenSpace = NO;
inFullscreenTransition = YES;
}
- (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
{
[self clearAllPendingWindowOperations];
}
- (void)windowDidExitFullScreen:(NSNotification *)aNotification
{
SDL_Window *window = _data.window;
NSWindow *nswindow = _data.nswindow;
inFullscreenTransition = NO;
isFullscreenSpace = NO;
_data.fullscreen_space_requested = NO;
SetWindowStyle(window, GetWindowWindowedStyle(window));
if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
[self clearAllPendingWindowOperations];
}
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
if (!_data.in_blocking_transition) {
SDL_UpdateFullscreenMode(window, false, false);
}
if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
[nswindow setLevel:NSFloatingWindowLevel];
} else {
[nswindow setLevel:kCGNormalWindowLevel];
}
[self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) {
[self setFullscreenSpace:YES];
} else if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) {
Cocoa_WaitForMiniaturizable(_data.window);
[self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
[nswindow miniaturize:nil];
} else {
if (window->flags & SDL_WINDOW_RESIZABLE) {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
} else {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged];
}
[NSMenu setMenuBarVisible:YES];
if ([self windowOperationIsPending:PENDING_OPERATION_ZOOM]) {
[self clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
[nswindow zoom:nil];
_data.was_zoomed = !_data.was_zoomed;
}
if (!_data.was_zoomed) {
NSRect rect;
rect.origin.x = _data.pending_position ? window->pending.x : window->floating.x;
rect.origin.y = _data.pending_position ? window->pending.y : window->floating.y;
rect.size.width = _data.pending_size ? window->pending.w : window->floating.w;
rect.size.height = _data.pending_size ? window->pending.h : window->floating.h;
ConvertNSRect(&rect);
if (_data.pending_size) {
[nswindow setContentSize:rect.size];
}
if (_data.pending_position) {
[nswindow setFrameOrigin:rect.origin];
}
}
_data.pending_size = NO;
_data.pending_position = NO;
_data.was_zoomed = NO;
window->w = 0;
window->h = 0;
[self windowDidMove:aNotification];
[self windowDidResize:aNotification];
if (!(window->flags & SDL_WINDOW_HIDDEN)) {
Cocoa_ShowWindow(SDL_GetVideoDevice(), window);
}
Cocoa_UpdateClipCursor(window);
}
}
- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
{
if (_data.window->fullscreen_exclusive) {
return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
} else {
return proposedOptions;
}
}
- (void)flagsChanged:(NSEvent *)theEvent
{
const bool osenabled = ([theEvent modifierFlags] & NSEventModifierFlagCapsLock) ? true : false;
const bool sdlenabled = (SDL_GetModState() & SDL_KMOD_CAPS) ? true : false;
if (osenabled ^ sdlenabled) {
SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, SDL_SCANCODE_CAPSLOCK, true);
SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, SDL_SCANCODE_CAPSLOCK, false);
}
}
- (void)keyDown:(NSEvent *)theEvent
{
}
- (void)keyUp:(NSEvent *)theEvent
{
}
- (void)doCommandBySelector:(SEL)aSelector
{
}
- (void)updateHitTest
{
SDL_Window *window = _data.window;
BOOL draggable = NO;
if (window->hit_test) {
float x, y;
SDL_Point point;
SDL_GetGlobalMouseState(&x, &y);
point.x = (int)SDL_roundf(x - window->x);
point.y = (int)SDL_roundf(y - window->y);
if (point.x >= 0 && point.x < window->w && point.y >= 0 && point.y < window->h) {
if (window->hit_test(window, &point, window->hit_test_data) == SDL_HITTEST_DRAGGABLE) {
draggable = YES;
}
}
}
if (isDragAreaRunning != draggable) {
isDragAreaRunning = draggable;
[_data.nswindow setMovableByWindowBackground:draggable];
}
}
- (BOOL)processHitTest:(NSEvent *)theEvent
{
SDL_Window *window = _data.window;
if (window->hit_test) { const NSPoint location = [theEvent locationInWindow];
const SDL_Point point = { (int)location.x, window->h - (((int)location.y) - 1) };
const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
if (rc == SDL_HITTEST_DRAGGABLE) {
if (!isDragAreaRunning) {
isDragAreaRunning = YES;
[_data.nswindow setMovableByWindowBackground:YES];
}
return YES; } else {
if (isDragAreaRunning) {
isDragAreaRunning = NO;
[_data.nswindow setMovableByWindowBackground:NO];
return YES; }
}
}
return NO; }
static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_Window *window, Uint8 button, bool down)
{
SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID;
SDL_Window *focus = SDL_GetKeyboardFocus();
if (focus && ([theEvent window] == ((__bridge SDL_CocoaWindowData *)focus->internal).nswindow)) {
SDL_SendMouseButton(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down);
} else {
const float orig_x = mouse->x;
const float orig_y = mouse->y;
const NSPoint point = [theEvent locationInWindow];
mouse->x = (int)point.x;
mouse->y = (int)(window->h - point.y);
SDL_SendMouseButton(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down);
mouse->x = orig_x;
mouse->y = orig_y;
}
}
- (void)mouseDown:(NSEvent *)theEvent
{
if (Cocoa_HandlePenEvent(_data, theEvent)) {
return; }
SDL_Mouse *mouse = SDL_GetMouse();
int button;
if (!mouse) {
return;
}
if ([theEvent window]) {
NSRect windowRect = [[[theEvent window] contentView] frame];
if (!NSMouseInRect([theEvent locationInWindow], windowRect, NO)) {
return;
}
}
switch ([theEvent buttonNumber]) {
case 0:
if (([theEvent modifierFlags] & NSEventModifierFlagControl) &&
GetHintCtrlClickEmulateRightClick()) {
wasCtrlLeft = YES;
button = SDL_BUTTON_RIGHT;
} else {
wasCtrlLeft = NO;
button = SDL_BUTTON_LEFT;
}
break;
case 1:
button = SDL_BUTTON_RIGHT;
break;
case 2:
button = SDL_BUTTON_MIDDLE;
break;
default:
button = (int)[theEvent buttonNumber] + 1;
break;
}
if (button == SDL_BUTTON_LEFT && [self processHitTest:theEvent]) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return; }
Cocoa_SendMouseButtonClicks(mouse, theEvent, _data.window, button, true);
}
- (void)rightMouseDown:(NSEvent *)theEvent
{
[self mouseDown:theEvent];
}
- (void)otherMouseDown:(NSEvent *)theEvent
{
[self mouseDown:theEvent];
}
- (void)mouseUp:(NSEvent *)theEvent
{
if (Cocoa_HandlePenEvent(_data, theEvent)) {
return; }
SDL_Mouse *mouse = SDL_GetMouse();
int button;
if (!mouse) {
return;
}
switch ([theEvent buttonNumber]) {
case 0:
if (wasCtrlLeft) {
button = SDL_BUTTON_RIGHT;
wasCtrlLeft = NO;
} else {
button = SDL_BUTTON_LEFT;
}
break;
case 1:
button = SDL_BUTTON_RIGHT;
break;
case 2:
button = SDL_BUTTON_MIDDLE;
break;
default:
button = (int)[theEvent buttonNumber] + 1;
break;
}
if (button == SDL_BUTTON_LEFT && [self processHitTest:theEvent]) {
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return; }
Cocoa_SendMouseButtonClicks(mouse, theEvent, _data.window, button, false);
}
- (void)rightMouseUp:(NSEvent *)theEvent
{
[self mouseUp:theEvent];
}
- (void)otherMouseUp:(NSEvent *)theEvent
{
[self mouseUp:theEvent];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
if (Cocoa_HandlePenEvent(_data, theEvent)) {
return; }
SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID;
SDL_Mouse *mouse = SDL_GetMouse();
NSPoint point;
float x, y;
SDL_Window *window;
NSView *contentView;
if (!mouse) {
return;
}
if (!Cocoa_GetMouseFocus()) {
SDL_SetMouseFocus(NULL);
return;
}
window = _data.window;
contentView = _data.sdlContentView;
point = [theEvent locationInWindow];
if ([contentView mouse:[contentView convertPoint:point fromView:nil] inRect:[contentView bounds]] &&
[NSCursor currentCursor] != Cocoa_GetDesiredCursor()) {
[_data.nswindow invalidateCursorRectsForView:contentView];
}
if (window->flags & SDL_WINDOW_TRANSPARENT) {
[self updateIgnoreMouseState:theEvent];
}
if ([self processHitTest:theEvent]) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return; }
if (mouse->relative_mode) {
return;
}
x = point.x;
y = (window->h - point.y);
if (@available(macOS 26.0, *)) {
if ([_data.listener isInFullscreenSpace]) {
int posx = 0, posy = 0;
SDL_GetWindowPosition(window, &posx, &posy);
SDL_GetGlobalMouseState(&x, &y);
x -= posx;
y -= posy;
}
}
if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_13_2) {
} else {
CGPoint cgpoint;
if (ShouldAdjustCoordinatesForGrab(window) &&
AdjustCoordinatesForGrab(window, window->x + x, window->y + y, &cgpoint)) {
Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
CGAssociateMouseAndMouseCursorPosition(YES);
}
}
SDL_SendMouseMotion(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, false, x, y);
}
- (void)mouseDragged:(NSEvent *)theEvent
{
[self mouseMoved:theEvent];
}
- (void)rightMouseDragged:(NSEvent *)theEvent
{
[self mouseMoved:theEvent];
}
- (void)otherMouseDragged:(NSEvent *)theEvent
{
[self mouseMoved:theEvent];
}
- (void)scrollWheel:(NSEvent *)theEvent
{
Cocoa_HandleMouseWheel(_data.window, theEvent);
}
- (BOOL)isTouchFromTrackpad:(NSEvent *)theEvent
{
SDL_Window *window = _data.window;
SDL_CocoaVideoData *videodata = ((__bridge SDL_CocoaWindowData *)window->internal).videodata;
BOOL istrackpad = NO;
if (!videodata.trackpad_is_touch_only) {
@try {
istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent);
}
@catch (NSException *e) {
}
}
return istrackpad;
}
- (void)touchesBeganWithEvent:(NSEvent *)theEvent
{
NSSet *touches;
SDL_TouchID touchID;
int existingTouchCount;
const BOOL istrackpad = [self isTouchFromTrackpad:theEvent];
touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil];
touchID = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[[touches anyObject] device];
existingTouchCount = 0;
for (NSTouch *touch in touches) {
if ([touch phase] != NSTouchPhaseBegan) {
existingTouchCount++;
}
}
if (existingTouchCount == 0) {
int numFingers;
SDL_Finger **fingers = SDL_GetTouchFingers(touchID, &numFingers);
if (fingers) {
DLog("Reset Lost Fingers: %d", numFingers);
for (--numFingers; numFingers >= 0; --numFingers) {
const SDL_Finger *finger = fingers[numFingers];
SDL_Window *window = NULL;
SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchID, finger->id, window, SDL_EVENT_FINGER_CANCELED, 0, 0, 0);
}
SDL_free(fingers);
}
}
DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount);
[self handleTouches:NSTouchPhaseBegan withEvent:theEvent];
}
- (void)touchesMovedWithEvent:(NSEvent *)theEvent
{
[self handleTouches:NSTouchPhaseMoved withEvent:theEvent];
}
- (void)touchesEndedWithEvent:(NSEvent *)theEvent
{
[self handleTouches:NSTouchPhaseEnded withEvent:theEvent];
}
- (void)touchesCancelledWithEvent:(NSEvent *)theEvent
{
[self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
}
- (void)magnifyWithEvent:(NSEvent *)theEvent
{
switch ([theEvent phase]) {
case NSEventPhaseBegan:
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0);
break;
case NSEventPhaseChanged:
{
CGFloat scale = 1.0f + [theEvent magnification];
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, scale);
}
break;
case NSEventPhaseEnded:
case NSEventPhaseCancelled:
SDL_SendPinch(SDL_EVENT_PINCH_END, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0);
break;
default:
break;
}
}
- (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent
{
NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];
const BOOL istrackpad = [self isTouchFromTrackpad:theEvent];
SDL_FingerID fingerId;
float x, y;
for (NSTouch *touch in touches) {
const SDL_TouchID touchId = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(uintptr_t)[touch device];
SDL_TouchDeviceType devtype = SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE;
SDL_Window *window = NULL;
if ([touch type] == NSTouchTypeDirect) {
continue;
}
if (SDL_AddTouch(touchId, devtype, "") < 0) {
return;
}
fingerId = (SDL_FingerID)(uintptr_t)[touch identity];
x = [touch normalizedPosition].x;
y = [touch normalizedPosition].y;
y = 1.0f - y;
switch (phase) {
case NSTouchPhaseBegan:
SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
break;
case NSTouchPhaseEnded:
SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_UP, x, y, 1.0f);
break;
case NSTouchPhaseCancelled:
SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_CANCELED, x, y, 1.0f);
break;
case NSTouchPhaseMoved:
SDL_SendTouchMotion(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, x, y, 1.0f);
break;
default:
break;
}
}
}
- (void)tabletProximity:(NSEvent *)theEvent
{
Cocoa_HandlePenEvent(_data, theEvent);
}
- (void)tabletPoint:(NSEvent *)theEvent
{
Cocoa_HandlePenEvent(_data, theEvent);
}
@end
@interface SDL3View : NSView
{
SDL_Window *_sdlWindow;
NSTrackingArea *_trackingArea; }
- (void)setSDLWindow:(SDL_Window *)window;
- (void)rightMouseDown:(NSEvent *)theEvent;
- (BOOL)mouseDownCanMoveWindow;
- (void)drawRect:(NSRect)dirtyRect;
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent;
- (BOOL)wantsUpdateLayer;
- (void)updateLayer;
- (void)updateTrackingAreas;
@end
@implementation SDL3View
- (void)setSDLWindow:(SDL_Window *)window
{
_sdlWindow = window;
}
- (void)drawRect:(NSRect)dirtyRect
{
BOOL transparent = (_sdlWindow->flags & SDL_WINDOW_TRANSPARENT) != 0;
if ([NSGraphicsContext currentContext]) {
NSColor *fillColor = transparent ? [NSColor clearColor] : [NSColor blackColor];
[fillColor setFill];
NSRectFill(dirtyRect);
} else if (self.layer) {
CFStringRef color = transparent ? kCGColorClear : kCGColorBlack;
self.layer.backgroundColor = CGColorGetConstantColor(color);
}
Cocoa_SendExposedEventIfVisible(_sdlWindow);
}
- (BOOL)wantsUpdateLayer
{
return YES;
}
- (void)updateLayer
{
BOOL transparent = (_sdlWindow->flags & SDL_WINDOW_TRANSPARENT) != 0;
CFStringRef color = transparent ? kCGColorClear : kCGColorBlack;
self.layer.backgroundColor = CGColorGetConstantColor(color);
ScheduleContextUpdates((__bridge SDL_CocoaWindowData *)_sdlWindow->internal);
Cocoa_SendExposedEventIfVisible(_sdlWindow);
}
- (void)rightMouseDown:(NSEvent *)theEvent
{
[[self nextResponder] rightMouseDown:theEvent];
}
- (BOOL)mouseDownCanMoveWindow
{
return YES;
}
- (void)resetCursorRects
{
[super resetCursorRects];
[self addCursorRect:[self bounds]
cursor:Cocoa_GetDesiredCursor()];
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
if (_sdlWindow->flags & SDL_WINDOW_POPUP_MENU) {
return YES;
} else if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) {
return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
} else {
return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", false);
}
}
- (void)updateTrackingAreas
{
[super updateTrackingAreas];
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)_sdlWindow->internal;
if (_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways owner:windata.listener userInfo:nil];
[self addTrackingArea:_trackingArea];
}
@end
static void Cocoa_UpdateMouseFocus()
{
const NSPoint mouseLocation = [NSEvent mouseLocation];
[NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
usingBlock:^(NSWindow *nswin, BOOL *stop) {
NSRect r = [nswin contentRectForFrameRect:[nswin frame]];
if (NSPointInRect(mouseLocation, r)) {
SDL_VideoDevice *vid = SDL_GetVideoDevice();
SDL_Window *sdlwindow;
for (sdlwindow = vid->windows; sdlwindow; sdlwindow = sdlwindow->next) {
if (nswin == ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow) {
break;
}
}
*stop = YES;
if (sdlwindow) {
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)vid->internal;
int wx, wy;
SDL_RelativeToGlobalForWindow(sdlwindow, sdlwindow->x, sdlwindow->y, &wx, &wy);
const float dx = mouseLocation.x - wx;
const float dy = (videodata.mainDisplayHeight - mouseLocation.y) - wy;
SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, dx, dy);
}
}
}];
}
static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview)
{
@autoreleasepool {
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal;
SDL_CocoaWindowData *data;
data = [[SDL_CocoaWindowData alloc] init];
if (!data) {
return SDL_OutOfMemory();
}
window->internal = (SDL_WindowData *)CFBridgingRetain(data);
data.window = window;
data.nswindow = nswindow;
data.videodata = videodata;
data.window_number = nswindow.windowNumber;
data.nscontexts = [[NSMutableArray alloc] init];
data.sdlContentView = nsview;
data.viewport = [data.sdlContentView bounds];
if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
data.viewport = [data.sdlContentView convertRectToBacking:data.viewport];
}
data.listener = [[SDL3Cocoa_WindowListener alloc] init];
{
int x, y;
NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
ConvertNSRect(&rect);
SDL_GlobalToRelativeForWindow(window, (int)rect.origin.x, (int)rect.origin.y, &x, &y);
window->x = x;
window->y = y;
window->w = (int)rect.size.width;
window->h = (int)rect.size.height;
}
[data.listener listen:data];
if ([nswindow isVisible]) {
window->flags &= ~SDL_WINDOW_HIDDEN;
} else {
window->flags |= SDL_WINDOW_HIDDEN;
}
{
unsigned long style = [nswindow styleMask];
if ((style & ~(NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable)) == NSWindowStyleMaskBorderless) {
window->flags |= SDL_WINDOW_BORDERLESS;
} else {
window->flags &= ~SDL_WINDOW_BORDERLESS;
}
if (style & NSWindowStyleMaskResizable) {
window->flags |= SDL_WINDOW_RESIZABLE;
} else {
window->flags &= ~SDL_WINDOW_RESIZABLE;
}
}
if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) {
window->flags |= SDL_WINDOW_MAXIMIZED;
} else {
window->flags &= ~SDL_WINDOW_MAXIMIZED;
}
if ([nswindow isMiniaturized]) {
window->flags |= SDL_WINDOW_MINIMIZED;
} else {
window->flags &= ~SDL_WINDOW_MINIMIZED;
}
if (window->parent) {
NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow;
[nsparent addChildWindow:nswindow ordered:NSWindowAbove];
if (window->flags & SDL_WINDOW_HIDDEN) {
[nswindow orderOut:nil];
}
}
if (!SDL_WINDOW_IS_POPUP(window)) {
if ([nswindow isKeyWindow]) {
window->flags |= SDL_WINDOW_INPUT_FOCUS;
Cocoa_SetKeyboardFocus(data.window, true);
}
} else {
if (window->flags & SDL_WINDOW_TOOLTIP) {
[nswindow setIgnoresMouseEvents:YES];
[nswindow setAcceptsMouseMovedEvents:NO];
} else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_HIDDEN)) {
if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) {
Cocoa_SetKeyboardFocus(window, true);
}
Cocoa_UpdateMouseFocus();
}
}
if (nswindow.isOpaque) {
window->flags &= ~SDL_WINDOW_TRANSPARENT;
} else {
window->flags |= SDL_WINDOW_TRANSPARENT;
}
nswindow.releasedWhenClosed = NO;
[nswindow setOneShot:NO];
if (window->flags & SDL_WINDOW_EXTERNAL) {
NSString *title = [nswindow title];
if (title) {
window->title = SDL_strdup([title UTF8String]);
}
}
SDL_PropertiesID props = SDL_GetWindowProperties(window);
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, (__bridge void *)data.nswindow);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
return true;
}
}
bool Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
{
@autoreleasepool {
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal;
const void *data = SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL);
NSWindow *nswindow = nil;
NSView *nsview = nil;
if (data) {
if ([(__bridge id)data isKindOfClass:[NSWindow class]]) {
nswindow = (__bridge NSWindow *)data;
} else if ([(__bridge id)data isKindOfClass:[NSView class]]) {
nsview = (__bridge NSView *)data;
} else {
SDL_assert(false);
}
} else {
nswindow = (__bridge NSWindow *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER, NULL);
nsview = (__bridge NSView *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER, NULL);
}
if (nswindow && !nsview) {
nsview = [nswindow contentView];
}
if (nsview && !nswindow) {
nswindow = [nsview window];
}
if (nswindow) {
window->flags |= SDL_WINDOW_EXTERNAL;
} else {
int x, y;
NSScreen *screen;
NSRect rect, screenRect;
NSUInteger style;
SDL3View *contentView;
SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y);
rect.origin.x = x;
rect.origin.y = y;
rect.size.width = window->w;
rect.size.height = window->h;
ConvertNSRect(&rect);
style = GetWindowStyle(window);
screen = ScreenForRect(&rect);
screenRect = [screen frame];
rect.origin.x -= screenRect.origin.x;
rect.origin.y -= screenRect.origin.y;
if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) {
if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) {
rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width);
}
if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) {
rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height);
}
rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x);
rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y);
}
@try {
nswindow = [[SDL3Window alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
}
@catch (NSException *e) {
return SDL_SetError("%s", [[e reason] UTF8String]);
}
[nswindow setColorSpace:[NSColorSpace sRGBColorSpace]];
[nswindow setTabbingMode:NSWindowTabbingModeDisallowed];
if ((window->flags & SDL_WINDOW_RESIZABLE) && videodata.allow_spaces) {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
} else {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenNone];
}
rect = [nswindow contentRectForFrameRect:[nswindow frame]];
contentView = [[SDL3View alloc] initWithFrame:rect];
[contentView setSDLWindow:window];
nsview = contentView;
}
if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
[nswindow setLevel:NSFloatingWindowLevel];
}
if (window->flags & SDL_WINDOW_TRANSPARENT) {
nswindow.opaque = NO;
nswindow.hasShadow = NO;
nswindow.backgroundColor = [NSColor clearColor];
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
BOOL highdpi = (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) ? YES : NO;
[nsview setWantsBestResolutionOpenGLSurface:highdpi];
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#ifdef SDL_VIDEO_OPENGL_ES2
#ifdef SDL_VIDEO_OPENGL_EGL
if ((window->flags & SDL_WINDOW_OPENGL) &&
_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
[nsview setWantsLayer:TRUE];
if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) {
nsview.layer.contentsScale = nswindow.screen.backingScaleFactor;
} else {
nsview.layer.contentsScale = 1;
}
}
#endif #endif [nswindow setContentView:nsview];
if (!SetupWindowData(_this, window, nswindow, nsview)) {
return false;
}
if (!(window->flags & SDL_WINDOW_OPENGL)) {
return true;
}
#ifdef SDL_VIDEO_OPENGL_ES2
if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
#ifdef SDL_VIDEO_OPENGL_EGL
if (!Cocoa_GLES_SetupWindow(_this, window)) {
Cocoa_DestroyWindow(_this, window);
return false;
}
return true;
#else
return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
#endif }
#endif return true;
}
}
void Cocoa_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
const char *title = window->title ? window->title : "";
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow;
NSString *string = [[NSString alloc] initWithUTF8String:title];
[nswindow setTitle:string];
}
}
bool Cocoa_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
{
@autoreleasepool {
NSImage *nsimage = Cocoa_CreateImage(icon);
if (nsimage) {
[NSApp setApplicationIconImage:nsimage];
return true;
}
return SDL_SetError("Unable to set the window's icon");
}
}
bool Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = windata.nswindow;
NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
int x, y;
if ([windata.listener isInFullscreenSpaceTransition]) {
windata.pending_position = YES;
return true;
}
if (!(window->flags & SDL_WINDOW_MAXIMIZED)) {
if (fullscreen) {
SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window);
SDL_Rect r;
SDL_GetDisplayBounds(display->id, &r);
rect.origin.x = r.x;
rect.origin.y = r.y;
} else {
SDL_RelativeToGlobalForWindow(window, window->pending.x, window->pending.y, &x, &y);
rect.origin.x = x;
rect.origin.y = y;
}
ConvertNSRect(&rect);
if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) {
NSRect screenRect = [ScreenForRect(&rect) frame];
if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) {
rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width);
}
if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) {
rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height);
}
rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x);
rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y);
}
[nswindow setFrameOrigin:rect.origin];
ScheduleContextUpdates(windata);
}
}
return true;
}
void Cocoa_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = windata.nswindow;
if ([windata.listener isInFullscreenSpace] ||
[windata.listener isInFullscreenSpaceTransition]) {
windata.pending_size = YES;
return;
}
if (!Cocoa_IsWindowZoomed(window)) {
int x, y;
NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, &x, &y);
rect.origin.x = x;
rect.origin.y = y;
rect.size.width = window->pending.w;
rect.size.height = window->pending.h;
ConvertNSRect(&rect);
[nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES];
ScheduleContextUpdates(windata);
} else {
window->last_size_pending = false;
}
}
}
void Cocoa_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
NSSize minSize;
minSize.width = window->min_w;
minSize.height = window->min_h;
[windata.nswindow setContentMinSize:minSize];
}
}
void Cocoa_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
NSSize maxSize;
maxSize.width = window->max_w ? window->max_w : CGFLOAT_MAX;
maxSize.height = window->max_h ? window->max_h : CGFLOAT_MAX;
[windata.nswindow setContentMaxSize:maxSize];
}
}
void Cocoa_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
if (window->min_aspect > 0.0f && window->min_aspect == window->max_aspect) {
int numerator = 0, denominator = 1;
SDL_CalculateFraction(window->max_aspect, &numerator, &denominator);
[windata.nswindow setContentAspectRatio:NSMakeSize(numerator, denominator)];
} else {
[windata.nswindow setContentAspectRatio:NSMakeSize(0, 0)];
}
}
}
void Cocoa_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
*w = (int)windata.viewport.size.width;
*h = (int)windata.viewport.size.height;
}
}
void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windowData = ((__bridge SDL_CocoaWindowData *)window->internal);
NSWindow *nswindow = windowData.nswindow;
bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true);
if (![nswindow isMiniaturized]) {
[windowData.listener pauseVisibleObservation];
if (window->parent) {
NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow;
[nsparent addChildWindow:nswindow ordered:NSWindowAbove];
if (window->flags & SDL_WINDOW_MODAL) {
Cocoa_SetWindowModal(_this, window, true);
}
}
if (!SDL_WINDOW_IS_POPUP(window)) {
if (bActivate) {
[nswindow makeKeyAndOrderFront:nil];
} else {
if ([NSApp keyWindow]) {
[nswindow orderWindow:NSWindowBelow relativeTo:[[NSApp keyWindow] windowNumber]];
}
}
} else if (window->flags & SDL_WINDOW_POPUP_MENU) {
if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) {
Cocoa_SetKeyboardFocus(window, true);
}
Cocoa_UpdateMouseFocus();
}
}
[nswindow setIsVisible:YES];
[windowData.listener resumeVisibleObservation];
}
}
void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow;
const BOOL waskey = [nswindow isKeyWindow];
if (![nswindow isMiniaturized]) {
[nswindow orderOut:nil];
} else {
[nswindow close];
}
Cocoa_SetWindowModal(_this, window, false);
if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) {
SDL_Window *new_focus;
const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus);
Cocoa_SetKeyboardFocus(new_focus, set_focus);
Cocoa_UpdateMouseFocus();
} else if (window->parent && waskey) {
SDL_Window *new_focus = window->parent;
while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) {
new_focus = new_focus->parent;
}
if (new_focus) {
NSWindow *newkey = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow;
[newkey makeKeyAndOrderFront:nil];
}
}
}
}
void Cocoa_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windowData = ((__bridge SDL_CocoaWindowData *)window->internal);
NSWindow *nswindow = windowData.nswindow;
bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true);
[windowData.listener pauseVisibleObservation];
if (![nswindow isMiniaturized] && [nswindow isVisible]) {
if (window->parent) {
NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow;
[nsparent addChildWindow:nswindow ordered:NSWindowAbove];
}
if (!SDL_WINDOW_IS_POPUP(window)) {
if (bActivate) {
[NSApp activateIgnoringOtherApps:YES];
[nswindow makeKeyAndOrderFront:nil];
} else {
[nswindow orderFront:nil];
}
} else {
if (bActivate) {
[nswindow makeKeyWindow];
}
}
}
[windowData.listener resumeVisibleObservation];
}
}
void Cocoa_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = windata.nswindow;
if ([windata.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] ||
[windata.listener isInFullscreenSpaceTransition]) {
Cocoa_SyncWindow(_this, window);
}
if (!(window->flags & SDL_WINDOW_FULLSCREEN) &&
![windata.listener isInFullscreenSpaceTransition] &&
![windata.listener isInFullscreenSpace]) {
[nswindow zoom:nil];
ScheduleContextUpdates(windata);
} else if (!windata.was_zoomed) {
[windata.listener addPendingWindowOperation:PENDING_OPERATION_ZOOM];
} else {
[windata.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
}
}
}
void Cocoa_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
[data.listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
if ([data.listener isInFullscreenSpace] || (window->flags & SDL_WINDOW_FULLSCREEN)) {
[data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
SDL_UpdateFullscreenMode(window, false, true);
} else if ([data.listener isInFullscreenSpaceTransition]) {
[data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN];
} else {
[nswindow miniaturize:nil];
}
}
}
void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
if (([data.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] &&
![data.nswindow isMiniaturized]) || [data.listener isInFullscreenSpaceTransition]) {
Cocoa_SyncWindow(_this, window);
}
[data.listener clearPendingWindowOperation:(PENDING_OPERATION_MINIMIZE)];
if (!(window->flags & SDL_WINDOW_FULLSCREEN) &&
![data.listener isInFullscreenSpaceTransition] &&
![data.listener isInFullscreenSpace]) {
if ([nswindow isMiniaturized]) {
[nswindow deminiaturize:nil];
} else if (Cocoa_IsWindowZoomed(window)) {
[nswindow zoom:nil];
}
} else if (data.was_zoomed) {
[data.listener addPendingWindowOperation:PENDING_OPERATION_ZOOM];
} else {
[data.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
}
}
}
void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) {
if (SetWindowStyle(window, GetWindowStyle(window))) {
if (bordered) {
Cocoa_SetWindowTitle(_this, window); }
}
} else {
data.border_toggled = true;
}
Cocoa_UpdateClipCursor(window);
}
}
void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
SDL3Cocoa_WindowListener *listener = data.listener;
NSWindow *nswindow = data.nswindow;
SDL_CocoaVideoData *videodata = data.videodata;
if (![listener isInFullscreenSpace] && ![listener isInFullscreenSpaceTransition]) {
SetWindowStyle(window, GetWindowStyle(window));
}
if (resizable && videodata.allow_spaces) {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
} else {
[nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenNone];
}
}
}
void Cocoa_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) {
if (on_top) {
[nswindow setLevel:NSFloatingWindowLevel];
} else {
[nswindow setLevel:kCGNormalWindowLevel];
}
}
}
}
SDL_FullscreenResult Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
NSRect rect;
[data.listener clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN];
if ([data.sdlContentView nextResponder] == data.listener) {
[data.sdlContentView setNextResponder:nil];
}
if (fullscreen) {
SDL_Rect bounds;
if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED);
}
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
Cocoa_GetDisplayBounds(_this, display, &bounds);
rect.origin.x = bounds.x;
rect.origin.y = bounds.y;
rect.size.width = bounds.w;
rect.size.height = bounds.h;
ConvertNSRect(&rect);
if (SDL_floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_14) {
NSRect screenRect = [[nswindow screen] frame];
if (screenRect.size.height >= 1.0f) {
rect.origin.y += (screenRect.size.height - rect.size.height);
}
}
[nswindow setStyleMask:NSWindowStyleMaskBorderless];
} else {
NSRect frameRect;
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
rect.origin.x = data.was_zoomed ? window->windowed.x : window->floating.x;
rect.origin.y = data.was_zoomed ? window->windowed.y : window->floating.y;
rect.size.width = data.was_zoomed ? window->windowed.w : window->floating.w;
rect.size.height = data.was_zoomed ? window->windowed.h : window->floating.h;
ConvertNSRect(&rect);
[nswindow setStyleMask:GetWindowWindowedStyle(window)];
frameRect = [nswindow frame];
[nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
[nswindow setFrame:frameRect display:NO];
}
if ([data.sdlContentView nextResponder] != data.listener) {
[data.sdlContentView setNextResponder:data.listener];
}
[nswindow setContentSize:rect.size];
[nswindow setFrameOrigin:rect.origin];
nswindow.hasShadow = !fullscreen && !(window->flags & SDL_WINDOW_TRANSPARENT);
if (!fullscreen) {
Cocoa_SetWindowTitle(_this, window);
data.was_zoomed = NO;
if ([data.listener windowOperationIsPending:PENDING_OPERATION_ZOOM]) {
[data.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM];
[nswindow zoom:nil];
}
}
if (SDL_ShouldAllowTopmost() && fullscreen) {
[nswindow setLevel:kCGMainMenuWindowLevel + 1];
} else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
[nswindow setLevel:NSFloatingWindowLevel];
} else {
[nswindow setLevel:kCGNormalWindowLevel];
}
if ([nswindow isVisible] || fullscreen) {
[data.listener pauseVisibleObservation];
[nswindow makeKeyAndOrderFront:nil];
[data.listener resumeVisibleObservation];
}
if (@available(macOS 12.0, *)) {
if (fullscreen) {
NSScreen *screen = [nswindow screen];
SDL_SetWindowSafeAreaInsets(data.window,
(int)SDL_ceilf(screen.safeAreaInsets.left),
(int)SDL_ceilf(screen.safeAreaInsets.right),
(int)SDL_ceilf(screen.safeAreaInsets.top),
(int)SDL_ceilf(screen.safeAreaInsets.bottom));
} else {
SDL_SetWindowSafeAreaInsets(data.window, 0, 0, 0, 0);
}
}
if (!fullscreen && [data.listener windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) {
Cocoa_WaitForMiniaturizable(window);
[data.listener addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN];
[data.listener clearPendingWindowOperation:PENDING_OPERATION_MINIMIZE];
[nswindow miniaturize:nil];
}
ScheduleContextUpdates(data);
Cocoa_SyncWindow(_this, window);
Cocoa_UpdateClipCursor(window);
}
return SDL_FULLSCREEN_SUCCEEDED;
}
void *Cocoa_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
NSWindow *nswindow = data.nswindow;
NSScreen *screen = [nswindow screen];
NSData *iccProfileData = nil;
void *retIccProfileData = NULL;
if (screen == nil) {
SDL_SetError("Could not get screen of window.");
return NULL;
}
if ([screen colorSpace] == nil) {
SDL_SetError("Could not get colorspace information of screen.");
return NULL;
}
iccProfileData = [[screen colorSpace] ICCProfileData];
if (iccProfileData == nil) {
SDL_SetError("Could not get ICC profile data.");
return NULL;
}
retIccProfileData = SDL_malloc([iccProfileData length]);
if (!retIccProfileData) {
return NULL;
}
[iccProfileData getBytes:retIccProfileData length:[iccProfileData length]];
*size = [iccProfileData length];
return retIccProfileData;
}
}
SDL_DisplayID Cocoa_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
NSScreen *screen;
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (data == nil) {
return 0;
}
screen = data.nswindow.screen;
if (screen != nil) {
CGDirectDisplayID displayid = [[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue];
SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid);
if (display) {
return display->id;
}
}
return 0;
}
}
bool Cocoa_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window)
{
Cocoa_UpdateClipCursor(window);
return true;
}
bool Cocoa_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
Cocoa_UpdateClipCursor(window);
if (data && (window->flags & SDL_WINDOW_FULLSCREEN) != 0) {
if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS) && ![data.listener isInFullscreenSpace]) {
[data.nswindow setLevel:kCGMainMenuWindowLevel + 1];
} else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
[data.nswindow setLevel:NSFloatingWindowLevel];
} else {
[data.nswindow setLevel:kCGNormalWindowLevel];
}
}
}
return true;
}
void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (SDL_CocoaWindowData *)CFBridgingRelease(window->internal);
if (data) {
#ifdef SDL_VIDEO_OPENGL
NSArray *contexts;
#endif SDL_Window *topmost = GetParentToplevelWindow(window);
if (topmost->keyboard_focus == window) {
SDL_Window *new_focus = window;
while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) {
new_focus = new_focus->parent;
}
topmost->keyboard_focus = new_focus;
}
if ([data.listener isInFullscreenSpace]) {
[NSMenu setMenuBarVisible:YES];
}
[data.listener close];
data.listener = nil;
if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
[data.nswindow setContentView:nil];
[data.nswindow close];
}
#ifdef SDL_VIDEO_OPENGL
contexts = [data.nscontexts copy];
for (SDL3OpenGLContext *context in contexts) {
[context setWindow:NULL];
}
#endif }
window->internal = NULL;
}
}
bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking)
{
@autoreleasepool {
bool succeeded = false;
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (state) {
data.fullscreen_space_requested = YES;
}
data.in_blocking_transition = blocking;
if ([data.listener setFullscreenSpace:(state ? YES : NO)]) {
if (blocking) {
const int maxattempts = 3;
int attempt = 0;
while (++attempt <= maxattempts) {
const int limit = 10000;
int count = 0;
while ([data.listener isInFullscreenSpaceTransition]) {
if (++count == limit) {
break;
}
SDL_Delay(1);
SDL_PumpEvents();
}
if ([data.listener isInFullscreenSpace] == (state ? YES : NO)) {
break;
}
if (![data.listener setFullscreenSpace:(state ? YES : NO)]) {
break; }
}
}
succeeded = true;
}
data.in_blocking_transition = NO;
return succeeded;
}
}
bool Cocoa_SetWindowHitTest(SDL_Window *window, bool enabled)
{
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
[data.listener updateHitTest];
return true;
}
void Cocoa_AcceptDragAndDrop(SDL_Window *window, bool accept)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (accept) {
[data.nswindow registerForDraggedTypes:@[ (NSString *)kUTTypeFileURL,
(NSString *)kUTTypeUTF8PlainText ]];
} else {
[data.nswindow unregisterDraggedTypes];
}
}
}
bool Cocoa_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent)
{
@autoreleasepool {
SDL_CocoaWindowData *child_data = (__bridge SDL_CocoaWindowData *)window->internal;
if (child_data.nswindow.parentWindow) {
NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow;
[nsparent removeChildWindow:child_data.nswindow];
}
if (parent) {
SDL_CocoaWindowData *parent_data = (__bridge SDL_CocoaWindowData *)parent->internal;
[parent_data.nswindow addChildWindow:child_data.nswindow ordered:NSWindowAbove];
}
}
return true;
}
bool Cocoa_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (data.modal_session) {
[NSApp endModalSession:data.modal_session];
data.modal_session = nil;
}
if (modal) {
data.modal_session = [NSApp beginModalSessionForWindow:data.nswindow];
}
}
return true;
}
bool Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
if (data.flash_request) {
[NSApp cancelUserAttentionRequest:data.flash_request];
data.flash_request = 0;
}
switch (operation) {
case SDL_FLASH_CANCEL:
break;
case SDL_FLASH_BRIEFLY:
data.flash_request = [NSApp requestUserAttention:NSInformationalRequest];
break;
case SDL_FLASH_UNTIL_FOCUSED:
data.flash_request = [NSApp requestUserAttention:NSCriticalRequest];
break;
default:
return SDL_Unsupported();
}
return true;
}
}
bool Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable)
{
if (window->flags & SDL_WINDOW_POPUP_MENU) {
if (!(window->flags & SDL_WINDOW_HIDDEN)) {
if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
SDL_Window *new_focus;
const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus);
Cocoa_SetKeyboardFocus(new_focus, set_focus);
} else if (focusable) {
if (SDL_ShouldFocusPopup(window)) {
Cocoa_SetKeyboardFocus(window, true);
}
}
}
}
return true; }
bool Cocoa_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity)
{
@autoreleasepool {
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
[data.nswindow setAlphaValue:opacity];
return true;
}
}
bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
bool result = false;
@autoreleasepool {
const Uint64 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(2500);
SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal;
for (;;) {
SDL_PumpEvents();
result = ![data.listener hasPendingWindowOperation];
if (result || SDL_GetTicksNS() >= timeout) {
break;
}
SDL_Delay(10);
}
}
return result;
}
#endif