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