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