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