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