• 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
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