• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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