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