1// 2// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6 7// OSXWindow.mm: Implementation of OSWindow for OSX 8 9#include "osx/OSXWindow.h" 10 11#include <set> 12// Include Carbon to use the keycode names in Carbon's Event.h 13#include <Carbon/Carbon.h> 14#include <QuartzCore/CAMetalLayer.h> 15 16// On OSX 10.12 a number of AppKit interfaces have been renamed for consistency, and the previous 17// symbols tagged as deprecated. However we can't simply use the new symbols as it would break 18// compilation on our automated testing that doesn't use OSX 10.12 yet. So we just ignore the 19// warnings. 20#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 21 22// Some events such as "ShouldTerminate" are sent to the whole application so we keep a list of 23// all the windows in order to forward the event to each of them. However this and calling pushEvent 24// in ApplicationDelegate is inherently unsafe in a multithreaded environment. 25static std::set<OSXWindow*> gAllWindows; 26 27@interface Application : NSApplication 28@end 29 30@implementation Application 31 - (void) sendEvent: (NSEvent*) nsEvent 32 { 33 if ([nsEvent type] == NSApplicationDefined) 34 { 35 for (auto window : gAllWindows) 36 { 37 if ([window->getNSWindow() windowNumber] == [nsEvent windowNumber]) 38 { 39 Event event; 40 event.type = Event::EVENT_TEST; 41 window->pushEvent(event); 42 } 43 } 44 } 45 [super sendEvent: nsEvent]; 46 } 47@end 48 49// The Delegate receiving application-wide events. 50@interface ApplicationDelegate : NSObject 51@end 52 53@implementation ApplicationDelegate 54 - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) sender 55 { 56 Event event; 57 event.type = Event::EVENT_CLOSED; 58 for (auto window : gAllWindows) 59 { 60 window->pushEvent(event); 61 } 62 return NSTerminateCancel; 63 } 64@end 65static ApplicationDelegate *gApplicationDelegate = nil; 66 67static bool InitializeAppKit() 68{ 69 if (NSApp != nil) 70 { 71 return true; 72 } 73 74 // Initialize the global variable "NSApp" 75 [Application sharedApplication]; 76 77 // Make us appear in the dock 78 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 79 80 // Register our global event handler 81 gApplicationDelegate = [[ApplicationDelegate alloc] init]; 82 if (gApplicationDelegate == nil) 83 { 84 return false; 85 } 86 [NSApp setDelegate: static_cast<id>(gApplicationDelegate)]; 87 88 // Set our status to "started" so we are not bouncing in the doc and can activate 89 [NSApp finishLaunching]; 90 return true; 91} 92 93// NS's and CG's coordinate systems start at the bottom left, while OSWindow's coordinate 94// system starts at the top left. This function converts the Y coordinate accordingly. 95static float YCoordToFromCG(float y) 96{ 97 float screenHeight = CGDisplayBounds(CGMainDisplayID()).size.height; 98 return screenHeight - y; 99} 100 101// Delegate for window-wide events, note that the protocol doesn't contain anything input related. 102@implementation WindowDelegate 103 - (id) initWithWindow: (OSXWindow*) window 104 { 105 self = [super init]; 106 if (self != nil) 107 { 108 mWindow = window; 109 } 110 return self; 111 } 112 113 - (void) onOSXWindowDeleted 114 { 115 mWindow = nil; 116 } 117 118 - (BOOL) windowShouldClose: (id) sender 119 { 120 Event event; 121 event.type = Event::EVENT_CLOSED; 122 mWindow->pushEvent(event); 123 return NO; 124 } 125 126 - (void) windowDidResize: (NSNotification*) notification 127 { 128 NSSize windowSize = [[mWindow->getNSWindow() contentView] frame].size; 129 Event event; 130 event.type = Event::EVENT_RESIZED; 131 event.size.width = windowSize.width; 132 event.size.height = windowSize.height; 133 mWindow->pushEvent(event); 134 } 135 136 - (void) windowDidMove: (NSNotification*) notification 137 { 138 NSRect screenspace = [mWindow->getNSWindow() frame]; 139 Event event; 140 event.type = Event::EVENT_MOVED; 141 event.move.x = screenspace.origin.x; 142 event.move.y = YCoordToFromCG(screenspace.origin.y + screenspace.size.height); 143 mWindow->pushEvent(event); 144 } 145 146 - (void) windowDidBecomeKey: (NSNotification*) notification 147 { 148 Event event; 149 event.type = Event::EVENT_GAINED_FOCUS; 150 mWindow->pushEvent(event); 151 [self retain]; 152 } 153 154 - (void) windowDidResignKey: (NSNotification*) notification 155 { 156 if (mWindow != nil) 157 { 158 Event event; 159 event.type = Event::EVENT_LOST_FOCUS; 160 mWindow->pushEvent(event); 161 } 162 [self release]; 163 } 164@end 165 166static Key NSCodeToKey(int keyCode) 167{ 168 // Missing KEY_PAUSE 169 switch (keyCode) 170 { 171 case kVK_Shift: return KEY_LSHIFT; 172 case kVK_RightShift: return KEY_RSHIFT; 173 case kVK_Option: return KEY_LALT; 174 case kVK_RightOption: return KEY_RALT; 175 case kVK_Control: return KEY_LCONTROL; 176 case kVK_RightControl: return KEY_RCONTROL; 177 case kVK_Command: return KEY_LSYSTEM; 178 // Right System doesn't have a name, but shows up as 0x36. 179 case 0x36: return KEY_RSYSTEM; 180 case kVK_Function: return KEY_MENU; 181 182 case kVK_ANSI_Semicolon: return KEY_SEMICOLON; 183 case kVK_ANSI_Slash: return KEY_SLASH; 184 case kVK_ANSI_Equal: return KEY_EQUAL; 185 case kVK_ANSI_Minus: return KEY_DASH; 186 case kVK_ANSI_LeftBracket: return KEY_LBRACKET; 187 case kVK_ANSI_RightBracket: return KEY_RBRACKET; 188 case kVK_ANSI_Comma: return KEY_COMMA; 189 case kVK_ANSI_Period: return KEY_PERIOD; 190 case kVK_ANSI_Backslash: return KEY_BACKSLASH; 191 case kVK_ANSI_Grave: return KEY_TILDE; 192 case kVK_Escape: return KEY_ESCAPE; 193 case kVK_Space: return KEY_SPACE; 194 case kVK_Return: return KEY_RETURN; 195 case kVK_Delete: return KEY_BACK; 196 case kVK_Tab: return KEY_TAB; 197 case kVK_PageUp: return KEY_PAGEUP; 198 case kVK_PageDown: return KEY_PAGEDOWN; 199 case kVK_End: return KEY_END; 200 case kVK_Home: return KEY_HOME; 201 case kVK_Help: return KEY_INSERT; 202 case kVK_ForwardDelete: return KEY_DELETE; 203 case kVK_ANSI_KeypadPlus: return KEY_ADD; 204 case kVK_ANSI_KeypadMinus: return KEY_SUBTRACT; 205 case kVK_ANSI_KeypadMultiply: return KEY_MULTIPLY; 206 case kVK_ANSI_KeypadDivide: return KEY_DIVIDE; 207 208 case kVK_F1: return KEY_F1; 209 case kVK_F2: return KEY_F2; 210 case kVK_F3: return KEY_F3; 211 case kVK_F4: return KEY_F4; 212 case kVK_F5: return KEY_F5; 213 case kVK_F6: return KEY_F6; 214 case kVK_F7: return KEY_F7; 215 case kVK_F8: return KEY_F8; 216 case kVK_F9: return KEY_F9; 217 case kVK_F10: return KEY_F10; 218 case kVK_F11: return KEY_F11; 219 case kVK_F12: return KEY_F12; 220 case kVK_F13: return KEY_F13; 221 case kVK_F14: return KEY_F14; 222 case kVK_F15: return KEY_F15; 223 224 case kVK_LeftArrow: return KEY_LEFT; 225 case kVK_RightArrow: return KEY_RIGHT; 226 case kVK_DownArrow: return KEY_DOWN; 227 case kVK_UpArrow: return KEY_UP; 228 229 case kVK_ANSI_Keypad0: return KEY_NUMPAD0; 230 case kVK_ANSI_Keypad1: return KEY_NUMPAD1; 231 case kVK_ANSI_Keypad2: return KEY_NUMPAD2; 232 case kVK_ANSI_Keypad3: return KEY_NUMPAD3; 233 case kVK_ANSI_Keypad4: return KEY_NUMPAD4; 234 case kVK_ANSI_Keypad5: return KEY_NUMPAD5; 235 case kVK_ANSI_Keypad6: return KEY_NUMPAD6; 236 case kVK_ANSI_Keypad7: return KEY_NUMPAD7; 237 case kVK_ANSI_Keypad8: return KEY_NUMPAD8; 238 case kVK_ANSI_Keypad9: return KEY_NUMPAD9; 239 240 case kVK_ANSI_A: return KEY_A; 241 case kVK_ANSI_B: return KEY_B; 242 case kVK_ANSI_C: return KEY_C; 243 case kVK_ANSI_D: return KEY_D; 244 case kVK_ANSI_E: return KEY_E; 245 case kVK_ANSI_F: return KEY_F; 246 case kVK_ANSI_G: return KEY_G; 247 case kVK_ANSI_H: return KEY_H; 248 case kVK_ANSI_I: return KEY_I; 249 case kVK_ANSI_J: return KEY_J; 250 case kVK_ANSI_K: return KEY_K; 251 case kVK_ANSI_L: return KEY_L; 252 case kVK_ANSI_M: return KEY_M; 253 case kVK_ANSI_N: return KEY_N; 254 case kVK_ANSI_O: return KEY_O; 255 case kVK_ANSI_P: return KEY_P; 256 case kVK_ANSI_Q: return KEY_Q; 257 case kVK_ANSI_R: return KEY_R; 258 case kVK_ANSI_S: return KEY_S; 259 case kVK_ANSI_T: return KEY_T; 260 case kVK_ANSI_U: return KEY_U; 261 case kVK_ANSI_V: return KEY_V; 262 case kVK_ANSI_W: return KEY_W; 263 case kVK_ANSI_X: return KEY_X; 264 case kVK_ANSI_Y: return KEY_Y; 265 case kVK_ANSI_Z: return KEY_Z; 266 267 case kVK_ANSI_1: return KEY_NUM1; 268 case kVK_ANSI_2: return KEY_NUM2; 269 case kVK_ANSI_3: return KEY_NUM3; 270 case kVK_ANSI_4: return KEY_NUM4; 271 case kVK_ANSI_5: return KEY_NUM5; 272 case kVK_ANSI_6: return KEY_NUM6; 273 case kVK_ANSI_7: return KEY_NUM7; 274 case kVK_ANSI_8: return KEY_NUM8; 275 case kVK_ANSI_9: return KEY_NUM9; 276 case kVK_ANSI_0: return KEY_NUM0; 277 } 278 279 return Key(0); 280} 281 282static void AddNSKeyStateToEvent(Event *event, int state) 283{ 284 event->key.shift = state & NSShiftKeyMask; 285 event->key.control = state & NSControlKeyMask; 286 event->key.alt = state & NSAlternateKeyMask; 287 event->key.system = state & NSCommandKeyMask; 288} 289 290static MouseButton TranslateMouseButton(int button) 291{ 292 switch (button) 293 { 294 case 2: 295 return MOUSEBUTTON_MIDDLE; 296 case 3: 297 return MOUSEBUTTON_BUTTON4; 298 case 4: 299 return MOUSEBUTTON_BUTTON5; 300 default: 301 return MOUSEBUTTON_UNKNOWN; 302 } 303} 304 305// Delegate for NSView events, mostly the input events 306@implementation ContentView 307 - (id) initWithWindow: (OSXWindow*) window 308 { 309 self = [super init]; 310 if (self != nil) 311 { 312 mWindow = window; 313 mTrackingArea = nil; 314 mCurrentModifier = 0; 315 [self updateTrackingAreas]; 316 } 317 return self; 318 } 319 320 - (void) dealloc 321 { 322 [mTrackingArea release]; 323 [super dealloc]; 324 } 325 326 - (void) updateTrackingAreas 327 { 328 if (mTrackingArea != nil) 329 { 330 [self removeTrackingArea: mTrackingArea]; 331 [mTrackingArea release]; 332 mTrackingArea = nil; 333 } 334 335 NSRect bounds = [self bounds]; 336 NSTrackingAreaOptions flags = NSTrackingMouseEnteredAndExited | 337 NSTrackingActiveInKeyWindow | 338 NSTrackingCursorUpdate | 339 NSTrackingInVisibleRect | 340 NSTrackingAssumeInside; 341 mTrackingArea = [[NSTrackingArea alloc] initWithRect: bounds 342 options: flags 343 owner: self 344 userInfo: nil]; 345 346 [self addTrackingArea: mTrackingArea]; 347 [super updateTrackingAreas]; 348 } 349 350 // Helps with performance 351 - (BOOL) isOpaque 352 { 353 return YES; 354 } 355 356 - (BOOL) canBecomeKeyView 357 { 358 return YES; 359 } 360 361 - (BOOL) acceptsFirstResponder 362 { 363 return YES; 364 } 365 366 // Handle mouse events from the NSResponder protocol 367 - (float) translateMouseY: (float) y 368 { 369 return [self frame].size.height - y; 370 } 371 372 - (void) addButtonEvent: (NSEvent*) nsEvent type:(Event::EventType) eventType button:(MouseButton) button 373 { 374 Event event; 375 event.type = eventType; 376 event.mouseButton.button = button; 377 event.mouseButton.x = [nsEvent locationInWindow].x; 378 event.mouseButton.y = [self translateMouseY: [nsEvent locationInWindow].y]; 379 mWindow->pushEvent(event); 380 } 381 382 - (void) mouseDown: (NSEvent*) event 383 { 384 [self addButtonEvent: event 385 type: Event::EVENT_MOUSE_BUTTON_PRESSED 386 button: MOUSEBUTTON_LEFT]; 387 } 388 389 - (void) mouseDragged: (NSEvent*) event 390 { 391 [self mouseMoved: event]; 392 } 393 394 - (void) mouseUp: (NSEvent*) event 395 { 396 [self addButtonEvent: event 397 type: Event::EVENT_MOUSE_BUTTON_RELEASED 398 button: MOUSEBUTTON_LEFT]; 399 } 400 401 - (void) mouseMoved: (NSEvent*) nsEvent 402 { 403 Event event; 404 event.type = Event::EVENT_MOUSE_MOVED; 405 event.mouseMove.x = [nsEvent locationInWindow].x; 406 event.mouseMove.y = [self translateMouseY: [nsEvent locationInWindow].y]; 407 mWindow->pushEvent(event); 408 } 409 410 - (void) mouseEntered: (NSEvent*) nsEvent 411 { 412 Event event; 413 event.type = Event::EVENT_MOUSE_ENTERED; 414 mWindow->pushEvent(event); 415 } 416 417 - (void) mouseExited: (NSEvent*) nsEvent 418 { 419 Event event; 420 event.type = Event::EVENT_MOUSE_LEFT; 421 mWindow->pushEvent(event); 422 } 423 424 - (void)rightMouseDown:(NSEvent *)event 425 { 426 [self addButtonEvent: event 427 type: Event::EVENT_MOUSE_BUTTON_PRESSED 428 button: MOUSEBUTTON_RIGHT]; 429 } 430 431 - (void) rightMouseDragged: (NSEvent*) event 432 { 433 [self mouseMoved: event]; 434 } 435 436 - (void) rightMouseUp: (NSEvent*)event 437 { 438 [self addButtonEvent: event 439 type: Event::EVENT_MOUSE_BUTTON_RELEASED 440 button: MOUSEBUTTON_RIGHT]; 441 } 442 443 - (void) otherMouseDown: (NSEvent*) event 444 { 445 [self addButtonEvent: event 446 type: Event::EVENT_MOUSE_BUTTON_PRESSED 447 button: TranslateMouseButton([event buttonNumber])]; 448 } 449 450 - (void) otherMouseDragged: (NSEvent*) event 451 { 452 [self mouseMoved: event]; 453 } 454 455 - (void) otherMouseUp: (NSEvent*) event 456 { 457 [self addButtonEvent: event 458 type: Event::EVENT_MOUSE_BUTTON_RELEASED 459 button: TranslateMouseButton([event buttonNumber])]; 460 } 461 462 - (void) scrollWheel: (NSEvent*) nsEvent 463 { 464 if (static_cast<int>([nsEvent deltaY]) == 0) 465 { 466 return; 467 } 468 469 Event event; 470 event.type = Event::EVENT_MOUSE_WHEEL_MOVED; 471 event.mouseWheel.delta = [nsEvent deltaY]; 472 mWindow->pushEvent(event); 473 } 474 475 // Handle key events from the NSResponder protocol 476 - (void) keyDown: (NSEvent*) nsEvent 477 { 478 // TODO(cwallez) also send text events 479 Event event; 480 event.type = Event::EVENT_KEY_PRESSED; 481 event.key.code = NSCodeToKey([nsEvent keyCode]); 482 AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]); 483 mWindow->pushEvent(event); 484 } 485 486 - (void) keyUp: (NSEvent*) nsEvent 487 { 488 Event event; 489 event.type = Event::EVENT_KEY_RELEASED; 490 event.key.code = NSCodeToKey([nsEvent keyCode]); 491 AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]); 492 mWindow->pushEvent(event); 493 } 494 495 // Modifier keys do not trigger keyUp/Down events but only flagsChanged events. 496 - (void) flagsChanged: (NSEvent*) nsEvent 497 { 498 Event event; 499 500 // Guess if the key has been pressed or released with the change of modifiers 501 // It currently doesn't work when modifiers are unchanged, such as when pressing 502 // both shift keys. GLFW has a solution for this but it requires tracking the 503 // state of the keys. Implementing this is still TODO(cwallez) 504 int modifier = [nsEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; 505 if (modifier < mCurrentModifier) 506 { 507 event.type = Event::EVENT_KEY_RELEASED; 508 } 509 else 510 { 511 event.type = Event::EVENT_KEY_PRESSED; 512 } 513 mCurrentModifier = modifier; 514 515 event.key.code = NSCodeToKey([nsEvent keyCode]); 516 AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]); 517 mWindow->pushEvent(event); 518 } 519@end 520 521OSXWindow::OSXWindow() 522 : mWindow(nil), 523 mDelegate(nil), 524 mView(nil) 525{ 526} 527 528OSXWindow::~OSXWindow() 529{ 530 destroy(); 531} 532 533bool OSXWindow::initialize(const std::string &name, size_t width, size_t height) 534{ 535 if (!InitializeAppKit()) 536 { 537 return false; 538 } 539 540 unsigned int styleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | 541 NSMiniaturizableWindowMask; 542 mWindow = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, width, height) 543 styleMask: styleMask 544 backing: NSBackingStoreBuffered 545 defer: NO]; 546 547 if (mWindow == nil) 548 { 549 return false; 550 } 551 552 mDelegate = [[WindowDelegate alloc] initWithWindow: this]; 553 if (mDelegate == nil) 554 { 555 return false; 556 } 557 [mWindow setDelegate: static_cast<id>(mDelegate)]; 558 559 mView = [[ContentView alloc] initWithWindow: this]; 560 if (mView == nil) 561 { 562 return false; 563 } 564 565 mLayer = [CAMetalLayer layer]; 566 [mView setLayer:mLayer]; 567 [mView setWantsLayer:YES]; 568 569 [mWindow setContentView: mView]; 570 [mWindow setTitle: [NSString stringWithUTF8String: name.c_str()]]; 571 [mWindow setAcceptsMouseMovedEvents: YES]; 572 [mWindow center]; 573 574 [NSApp activateIgnoringOtherApps: YES]; 575 576 mX = 0; 577 mY = 0; 578 mWidth = width; 579 mHeight = height; 580 581 gAllWindows.insert(this); 582 return true; 583} 584 585void OSXWindow::destroy() 586{ 587 gAllWindows.erase(this); 588 589 [mView release]; 590 mView = nil; 591 [mDelegate onOSXWindowDeleted]; 592 [mDelegate release]; 593 mDelegate = nil; 594 [mWindow release]; 595 mWindow = nil; 596} 597 598EGLNativeWindowType OSXWindow::getNativeWindow() const 599{ 600 return [mView layer]; 601} 602 603EGLNativeDisplayType OSXWindow::getNativeDisplay() const 604{ 605 // TODO(cwallez): implement it once we have defined what EGLNativeDisplayType is 606 return static_cast<EGLNativeDisplayType>(0); 607} 608 609void* OSXWindow::getFramebufferNativeWindow() const 610{ 611 return static_cast<void*>(mWindow); 612} 613 614float OSXWindow::getDevicePixelRatio() const 615{ 616 return [[NSScreen mainScreen] backingScaleFactor]; 617} 618 619void OSXWindow::messageLoop() 620{ 621 @autoreleasepool 622 { 623 while (true) 624 { 625 NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask 626 untilDate: [NSDate distantPast] 627 inMode: NSDefaultRunLoopMode 628 dequeue: YES]; 629 if (event == nil) 630 { 631 break; 632 } 633 634 if ([event type] == NSAppKitDefined) 635 { 636 continue; 637 } 638 [NSApp sendEvent: event]; 639 } 640 } 641} 642 643void OSXWindow::setMousePosition(int x, int y) 644{ 645 y = [mWindow frame].size.height - y -1; 646 NSPoint screenspace; 647 648 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 649 screenspace = [mWindow convertBaseToScreen: NSMakePoint(x, y)]; 650 #else 651 screenspace = [mWindow convertRectToScreen: NSMakeRect(x, y, 0, 0)].origin; 652 #endif 653 CGWarpMouseCursorPosition(CGPointMake(screenspace.x, YCoordToFromCG(screenspace.y))); 654} 655 656bool OSXWindow::setPosition(int x, int y) 657{ 658 // Given CG and NS's coordinate system, the "Y" position of a window is the Y coordinate 659 // of the bottom of the window. 660 int newBottom = [mWindow frame].size.height + y; 661 NSRect emptyRect = NSMakeRect(x, YCoordToFromCG(newBottom), 0, 0); 662 [mWindow setFrameOrigin: [mWindow frameRectForContentRect: emptyRect].origin]; 663 return true; 664} 665 666bool OSXWindow::resize(int width, int height) 667{ 668 [mWindow setContentSize: NSMakeSize(width, height)]; 669 return true; 670} 671 672void OSXWindow::setVisible(bool isVisible) 673{ 674 if (isVisible) 675 { 676 [mWindow makeKeyAndOrderFront: nil]; 677 } 678 else 679 { 680 [mWindow orderOut: nil]; 681 } 682} 683 684void OSXWindow::signalTestEvent() 685{ 686 @autoreleasepool 687 { 688 NSEvent *event = [NSEvent otherEventWithType: NSApplicationDefined 689 location: NSMakePoint(0, 0) 690 modifierFlags: 0 691 timestamp: 0.0 692 windowNumber: [mWindow windowNumber] 693 context: nil 694 subtype: 0 695 data1: 0 696 data2: 0]; 697 [NSApp postEvent: event atStart: YES]; 698 } 699} 700 701NSWindow* OSXWindow::getNSWindow() const 702{ 703 return mWindow; 704} 705 706OSWindow *CreateOSWindow() 707{ 708 return new OSXWindow; 709} 710