Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / MediaHardwareKeysEventSourceMac.mm
blob9a6991a181c25a8b9528b48388bb685cbfe972c9
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "MediaHardwareKeysEventSourceMac.h"
7 #import <AppKit/AppKit.h>
8 #import <AppKit/NSEvent.h>
9 #import <IOKit/hidsystem/ev_keymap.h>
11 #include "mozilla/dom/MediaControlUtils.h"
13 using namespace mozilla::dom;
15 // avoid redefined macro in unified build
16 #undef LOG
17 #define LOG(msg, ...)                        \
18   MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
19           ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__))
21 // This macro is used in static callback function, where we have to send `this`
22 // explicitly.
23 #define LOG2(msg, this, ...)                 \
24   MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
25           ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__))
27 static const char* ToMediaControlKeyStr(int aKeyCode) {
28   switch (aKeyCode) {
29     case NX_KEYTYPE_PLAY:
30       return "Play";
31     case NX_KEYTYPE_NEXT:
32       return "Next";
33     case NX_KEYTYPE_PREVIOUS:
34       return "Previous";
35     case NX_KEYTYPE_FAST:
36       return "Fast";
37     case NX_KEYTYPE_REWIND:
38       return "Rewind";
39     default:
40       MOZ_ASSERT_UNREACHABLE("Invalid key code.");
41       return "UNKNOWN";
42   }
45 // The media keys subtype. No official docs found, but widely known.
46 // http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html
47 const int kSystemDefinedEventMediaKeysSubtype = 8;
49 static bool IsSupportedKeyCode(int aKeyCode) {
50   return aKeyCode == NX_KEYTYPE_PLAY || aKeyCode == NX_KEYTYPE_NEXT ||
51          aKeyCode == NX_KEYTYPE_FAST || aKeyCode == NX_KEYTYPE_PREVIOUS ||
52          aKeyCode == NX_KEYTYPE_REWIND;
55 static MediaControlKey ToMediaControlKey(int aKeyCode) {
56   MOZ_ASSERT(IsSupportedKeyCode(aKeyCode));
57   switch (aKeyCode) {
58     case NX_KEYTYPE_NEXT:
59     case NX_KEYTYPE_FAST:
60       return MediaControlKey::Nexttrack;
61     case NX_KEYTYPE_PREVIOUS:
62     case NX_KEYTYPE_REWIND:
63       return MediaControlKey::Previoustrack;
64     default:
65       MOZ_ASSERT(aKeyCode == NX_KEYTYPE_PLAY);
66       return MediaControlKey::Playpause;
67   }
70 namespace mozilla {
71 namespace widget {
73 bool MediaHardwareKeysEventSourceMac::IsOpened() const {
74   return mEventTap && mEventTapSource;
77 bool MediaHardwareKeysEventSourceMac::Open() {
78   LOG("Open MediaHardwareKeysEventSourceMac");
79   return StartEventTap();
82 void MediaHardwareKeysEventSourceMac::Close() {
83   LOG("Close MediaHardwareKeysEventSourceMac");
84   StopEventTap();
85   MediaControlKeySource::Close();
88 bool MediaHardwareKeysEventSourceMac::StartEventTap() {
89   LOG("StartEventTap");
90   MOZ_ASSERT(!mEventTap);
91   MOZ_ASSERT(!mEventTapSource);
93   // Add an event tap to intercept the system defined media key events.
94   mEventTap = CGEventTapCreate(
95       kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
96       CGEventMaskBit(NX_SYSDEFINED), EventTapCallback, this);
97   if (!mEventTap) {
98     LOG("Fail to create event tap");
99     return false;
100   }
102   mEventTapSource =
103       CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mEventTap, 0);
104   if (!mEventTapSource) {
105     LOG("Fail to create an event tap source.");
106     return false;
107   }
109   LOG("Add an event tap source to current loop");
110   CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapSource,
111                      kCFRunLoopCommonModes);
112   return true;
115 void MediaHardwareKeysEventSourceMac::StopEventTap() {
116   LOG("StopEventTapIfNecessary");
117   if (mEventTap) {
118     CFMachPortInvalidate(mEventTap);
119     mEventTap = nullptr;
120   }
121   if (mEventTapSource) {
122     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapSource,
123                           kCFRunLoopCommonModes);
124     CFRelease(mEventTapSource);
125     mEventTapSource = nullptr;
126   }
129 CGEventRef MediaHardwareKeysEventSourceMac::EventTapCallback(
130     CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) {
131   // Re-enable event tap when receiving disabled events.
132   MediaHardwareKeysEventSourceMac* source =
133       static_cast<MediaHardwareKeysEventSourceMac*>(refcon);
134   if (type == kCGEventTapDisabledByUserInput ||
135       type == kCGEventTapDisabledByTimeout) {
136     MOZ_ASSERT(source->mEventTap);
137     CGEventTapEnable(source->mEventTap, true);
138     return event;
139   }
141   NSEvent* nsEvent = [NSEvent eventWithCGEvent:event];
142   if (nsEvent == nil) {
143     return event;
144   }
146   // Ignore not system defined media keys event.
147   if ([nsEvent type] != NSEventTypeSystemDefined ||
148       [nsEvent subtype] != kSystemDefinedEventMediaKeysSubtype) {
149     return event;
150   }
152   // Ignore media keys that aren't previous, next and play/pause.
153   // Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/
154   // - keyCode = (([event data1] & 0xFFFF0000) >> 16)
155   // - keyFlags = ([event data1] & 0x0000FFFF)
156   // - keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA
157   // - keyRepeat = (keyFlags & 0x1);
158   const NSInteger data1 = [nsEvent data1];
159   int keyCode = (data1 & 0xFFFF0000) >> 16;
160   if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_NEXT &&
161       keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_FAST &&
162       keyCode != NX_KEYTYPE_REWIND) {
163     return event;
164   }
166   // Ignore non-key pressed event (eg. key released).
167   int keyFlags = data1 & 0x0000FFFF;
168   bool isKeyPressed = ((keyFlags & 0xFF00) >> 8) == 0xA;
169   if (!isKeyPressed) {
170     return event;
171   }
173   // There is no listener waiting to process event.
174   if (source->mListeners.IsEmpty()) {
175     return event;
176   }
178   if (!IsSupportedKeyCode(keyCode)) {
179     return event;
180   }
182   LOG2("Get media key %s", source, ToMediaControlKeyStr(keyCode));
183   for (auto iter = source->mListeners.begin(); iter != source->mListeners.end();
184        ++iter) {
185     (*iter)->OnActionPerformed(MediaControlAction(ToMediaControlKey(keyCode)));
186   }
187   return event;
190 }  // namespace widget
191 }  // namespace mozilla