/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaHardwareKeysEventSourceMac.h" #import #import #import #include "mozilla/dom/MediaControlUtils.h" using namespace mozilla::dom; // avoid redefined macro in unified build #undef LOG #define LOG(msg, ...) \ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__)) // This macro is used in static callback function, where we have to send `this` // explicitly. #define LOG2(msg, this, ...) \ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__)) static const char* ToMediaControlKeyStr(int aKeyCode) { switch (aKeyCode) { case NX_KEYTYPE_PLAY: return "Play"; case NX_KEYTYPE_NEXT: return "Next"; case NX_KEYTYPE_PREVIOUS: return "Previous"; case NX_KEYTYPE_FAST: return "Fast"; case NX_KEYTYPE_REWIND: return "Rewind"; default: MOZ_ASSERT_UNREACHABLE("Invalid key code."); return "UNKNOWN"; } } // The media keys subtype. No official docs found, but widely known. // http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html const int kSystemDefinedEventMediaKeysSubtype = 8; static bool IsSupportedKeyCode(int aKeyCode) { return aKeyCode == NX_KEYTYPE_PLAY || aKeyCode == NX_KEYTYPE_NEXT || aKeyCode == NX_KEYTYPE_FAST || aKeyCode == NX_KEYTYPE_PREVIOUS || aKeyCode == NX_KEYTYPE_REWIND; } static MediaControlKey ToMediaControlKey(int aKeyCode) { MOZ_ASSERT(IsSupportedKeyCode(aKeyCode)); switch (aKeyCode) { case NX_KEYTYPE_NEXT: case NX_KEYTYPE_FAST: return MediaControlKey::Nexttrack; case NX_KEYTYPE_PREVIOUS: case NX_KEYTYPE_REWIND: return MediaControlKey::Previoustrack; default: MOZ_ASSERT(aKeyCode == NX_KEYTYPE_PLAY); return MediaControlKey::Playpause; } } namespace mozilla { namespace widget { bool MediaHardwareKeysEventSourceMac::IsOpened() const { return mEventTap && mEventTapSource; } bool MediaHardwareKeysEventSourceMac::Open() { LOG("Open MediaHardwareKeysEventSourceMac"); return StartEventTap(); } void MediaHardwareKeysEventSourceMac::Close() { LOG("Close MediaHardwareKeysEventSourceMac"); StopEventTap(); MediaControlKeySource::Close(); } bool MediaHardwareKeysEventSourceMac::StartEventTap() { LOG("StartEventTap"); MOZ_ASSERT(!mEventTap); MOZ_ASSERT(!mEventTapSource); // Add an event tap to intercept the system defined media key events. mEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, CGEventMaskBit(NX_SYSDEFINED), EventTapCallback, this); if (!mEventTap) { LOG("Fail to create event tap"); return false; } mEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mEventTap, 0); if (!mEventTapSource) { LOG("Fail to create an event tap source."); return false; } LOG("Add an event tap source to current loop"); CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapSource, kCFRunLoopCommonModes); return true; } void MediaHardwareKeysEventSourceMac::StopEventTap() { LOG("StopEventTapIfNecessary"); if (mEventTap) { CFMachPortInvalidate(mEventTap); mEventTap = nullptr; } if (mEventTapSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapSource, kCFRunLoopCommonModes); CFRelease(mEventTapSource); mEventTapSource = nullptr; } } CGEventRef MediaHardwareKeysEventSourceMac::EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) { // Re-enable event tap when receiving disabled events. MediaHardwareKeysEventSourceMac* source = static_cast(refcon); if (type == kCGEventTapDisabledByUserInput || type == kCGEventTapDisabledByTimeout) { MOZ_ASSERT(source->mEventTap); CGEventTapEnable(source->mEventTap, true); return event; } NSEvent* nsEvent = [NSEvent eventWithCGEvent:event]; if (nsEvent == nil) { return event; } // Ignore not system defined media keys event. if ([nsEvent type] != NSSystemDefined || [nsEvent subtype] != kSystemDefinedEventMediaKeysSubtype) { return event; } // Ignore media keys that aren't previous, next and play/pause. // Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/ // - keyCode = (([event data1] & 0xFFFF0000) >> 16) // - keyFlags = ([event data1] & 0x0000FFFF) // - keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA // - keyRepeat = (keyFlags & 0x1); const NSInteger data1 = [nsEvent data1]; int keyCode = (data1 & 0xFFFF0000) >> 16; if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_NEXT && keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND) { return event; } // Ignore non-key pressed event (eg. key released). int keyFlags = data1 & 0x0000FFFF; bool isKeyPressed = ((keyFlags & 0xFF00) >> 8) == 0xA; if (!isKeyPressed) { return event; } // There is no listener waiting to process event. if (source->mListeners.IsEmpty()) { return event; } if (!IsSupportedKeyCode(keyCode)) { return event; } LOG2("Get media key %s", source, ToMediaControlKeyStr(keyCode)); for (auto iter = source->mListeners.begin(); iter != source->mListeners.end(); ++iter) { (*iter)->OnActionPerformed(MediaControlAction(ToMediaControlKey(keyCode))); } return event; } } // namespace widget } // namespace mozilla