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
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`
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) {
33 case NX_KEYTYPE_PREVIOUS:
37 case NX_KEYTYPE_REWIND:
40 MOZ_ASSERT_UNREACHABLE("Invalid key code.");
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));
60 return MediaControlKey::Nexttrack;
61 case NX_KEYTYPE_PREVIOUS:
62 case NX_KEYTYPE_REWIND:
63 return MediaControlKey::Previoustrack;
65 MOZ_ASSERT(aKeyCode == NX_KEYTYPE_PLAY);
66 return MediaControlKey::Playpause;
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");
85 MediaControlKeySource::Close();
88 bool MediaHardwareKeysEventSourceMac::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);
98 LOG("Fail to create event tap");
103 CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mEventTap, 0);
104 if (!mEventTapSource) {
105 LOG("Fail to create an event tap source.");
109 LOG("Add an event tap source to current loop");
110 CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapSource,
111 kCFRunLoopCommonModes);
115 void MediaHardwareKeysEventSourceMac::StopEventTap() {
116 LOG("StopEventTapIfNecessary");
118 CFMachPortInvalidate(mEventTap);
121 if (mEventTapSource) {
122 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapSource,
123 kCFRunLoopCommonModes);
124 CFRelease(mEventTapSource);
125 mEventTapSource = nullptr;
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);
141 NSEvent* nsEvent = [NSEvent eventWithCGEvent:event];
142 if (nsEvent == nil) {
146 // Ignore not system defined media keys event.
147 if ([nsEvent type] != NSEventTypeSystemDefined ||
148 [nsEvent subtype] != kSystemDefinedEventMediaKeysSubtype) {
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) {
166 // Ignore non-key pressed event (eg. key released).
167 int keyFlags = data1 & 0x0000FFFF;
168 bool isKeyPressed = ((keyFlags & 0xFF00) >> 8) == 0xA;
173 // There is no listener waiting to process event.
174 if (source->mListeners.IsEmpty()) {
178 if (!IsSupportedKeyCode(keyCode)) {
182 LOG2("Get media key %s", source, ToMediaControlKeyStr(keyCode));
183 for (auto iter = source->mListeners.begin(); iter != source->mListeners.end();
185 (*iter)->OnActionPerformed(MediaControlAction(ToMediaControlKey(keyCode)));
190 } // namespace widget
191 } // namespace mozilla