Bug 1575995 - part2 : implement the event source on OSX. r=spohl
authorAlastor Wu <alwu@mozilla.com>
Sat, 14 Sep 2019 03:30:30 +0000
changeset 493947 a417c89cf52baa165e3821cce14ed126531e3ee7
parent 493946 239c106912ab92eceb5e7738f0240dd13c268342
child 493948 deb0bc2ec44fcb4af8efca40c6938769e861bd18
push id95775
push useralwu@mozilla.com
push dateThu, 19 Sep 2019 01:12:57 +0000
treeherderautoland@fa579e9b1e7d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1575995
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1575995 - part2 : implement the event source on OSX. r=spohl On OSX, we would use the CG event tap to intercept media keys event. Differential Revision: https://phabricator.services.mozilla.com/D43314
dom/media/mediacontrol/MediaHardwareKeysEventSourceMac.h
dom/media/mediacontrol/MediaHardwareKeysEventSourceMac.mm
dom/media/mediacontrol/MediaHardwareKeysManager.cpp
dom/media/mediacontrol/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/mediacontrol/MediaHardwareKeysEventSourceMac.h
@@ -0,0 +1,38 @@
+/* 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/. */
+
+#ifndef mozilla_dom_mediahardwarekeyseventmac_h__
+#define mozilla_dom_mediahardwarekeyseventmac_h__
+
+#import <ApplicationServices/ApplicationServices.h>
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "MediaHardwareKeysEvent.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaHardwareKeysEventSourceMac final
+    : public MediaHardwareKeysEventSource {
+ public:
+  MediaHardwareKeysEventSourceMac();
+  ~MediaHardwareKeysEventSourceMac();
+
+  static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type,
+                                     CGEventRef event, void* refcon);
+
+ private:
+  void StartEventTap();
+  void StopEventTap();
+
+  // They are used to intercept mac hardware media keys.
+  CFMachPortRef mEventTap = nullptr;
+  CFRunLoopSourceRef mEventTapSource = nullptr;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/media/mediacontrol/MediaHardwareKeysEventSourceMac.mm
@@ -0,0 +1,170 @@
+/* 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/. */
+
+#import <AppKit/AppKit.h>
+#import <AppKit/NSEvent.h>
+#import <IOKit/hidsystem/ev_keymap.h>
+
+#include "MediaHardwareKeysEventSourceMac.h"
+
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gMediaControlLog;
+
+// 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 action.");
+  }
+  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;
+
+namespace mozilla {
+namespace dom {
+
+static MediaControlKeysEvent ToMediaControlKeysEvent(int aKeyCode) {
+  switch (aKeyCode) {
+    case NX_KEYTYPE_PLAY:
+      return MediaControlKeysEvent::ePlayPause;
+    case NX_KEYTYPE_NEXT:
+    case NX_KEYTYPE_FAST:
+      return MediaControlKeysEvent::eNext;
+    case NX_KEYTYPE_PREVIOUS:
+    case NX_KEYTYPE_REWIND:
+      return MediaControlKeysEvent::ePrev;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Invalid action.");
+  }
+  return MediaControlKeysEvent::eNone;
+}
+
+MediaHardwareKeysEventSourceMac::MediaHardwareKeysEventSourceMac() {
+  LOG("Create MediaHardwareKeysEventSourceMac");
+  StartEventTap();
+}
+
+MediaHardwareKeysEventSourceMac::~MediaHardwareKeysEventSourceMac() {
+  LOG("Destroy MediaHardwareKeysEventSourceMac");
+  StopEventTap();
+}
+
+void 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;
+  }
+
+  mEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mEventTap, 0);
+  if (!mEventTapSource) {
+    LOG("Fail to create an event tap source.");
+    return;
+  }
+
+  LOG("Add an event tap source to current loop");
+  CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapSource, kCFRunLoopCommonModes);
+}
+
+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<MediaHardwareKeysEventSourceMac*>(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;
+  }
+
+  LOG2("Get media key %s", source, ToMediaControlKeyStr(keyCode));
+  for (auto iter = source->mListeners.begin(); iter != source->mListeners.end(); ++iter) {
+    (*iter)->OnKeyPressed(ToMediaControlKeysEvent(keyCode));
+  }
+  return event;
+}
+
+}  // namespace dom
+}  // namespace mozilla
--- a/dom/media/mediacontrol/MediaHardwareKeysManager.cpp
+++ b/dom/media/mediacontrol/MediaHardwareKeysManager.cpp
@@ -2,16 +2,20 @@
  * 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 "MediaHardwareKeysManager.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Logging.h"
 
+#ifdef MOZ_APPLEMEDIA
+#  include "MediaHardwareKeysEventSourceMac.h"
+#endif
+
 extern mozilla::LazyLogModule gMediaControlLog;
 
 #undef LOG
 #define LOG(msg, ...)                        \
   MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
           ("MediaHardwareKeysManager=%p, " msg, this, ##__VA_ARGS__))
 
 namespace mozilla {
@@ -29,17 +33,19 @@ void MediaHardwareKeysManager::StartMoni
   LOG("StartMonitoringHardwareKeys");
   CreateEventSource();
   if (mEventSource) {
     mEventSource->AddListener(new MediaHardwareKeysEventListener());
   }
 }
 
 void MediaHardwareKeysManager::CreateEventSource() {
-  // TODO : create source per platform.
+#ifdef MOZ_APPLEMEDIA
+  mEventSource = new MediaHardwareKeysEventSourceMac();
+#endif
 }
 
 void MediaHardwareKeysManager::StopMonitoringHardwareKeys() {
   LOG("StopMonitoringHardwareKeys");
   if (mEventSource) {
     mEventSource->Close();
     mEventSource = nullptr;
   }
--- a/dom/media/mediacontrol/moz.build
+++ b/dom/media/mediacontrol/moz.build
@@ -11,16 +11,24 @@ EXPORTS.mozilla.dom += [
     'MediaHardwareKeysEvent.h',
     'MediaHardwareKeysManager.h',
 ]
 
 EXPORTS.ipc += [
     'MediaControlIPC.h',
 ]
 
+if CONFIG['MOZ_APPLEMEDIA']:
+  EXPORTS += [
+      'MediaHardwareKeysEventSourceMac.h',
+  ]
+  UNIFIED_SOURCES += [
+      'MediaHardwareKeysEventSourceMac.mm',
+  ]
+
 UNIFIED_SOURCES += [
     'AudioFocusManager.cpp',
     'MediaController.cpp',
     'MediaControlService.cpp',
     'MediaHardwareKeysEvent.cpp',
     'MediaHardwareKeysManager.cpp',
 ]