Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3551)

Unified Diff: chrome/browser/extensions/global_shortcut_listener_mac.mm

Issue 60353008: Mac global keybindings (Closed) Base URL: https://src.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/extensions/global_shortcut_listener_mac.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/extensions/global_shortcut_listener_mac.mm
===================================================================
--- chrome/browser/extensions/global_shortcut_listener_mac.mm (revision 0)
+++ chrome/browser/extensions/global_shortcut_listener_mac.mm (working copy)
@@ -0,0 +1,308 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/global_shortcut_listener_mac.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "content/public/browser/browser_thread.h"
+#include "ui/events/event.h"
+#include "ui/base/accelerators/accelerator.h"
+#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
+
+#define SystemDefinedEventMediaKeys 8
+#define EVENT_KEY @"event"
+#define HANDLED_KEY @"handled"
+
+typedef extensions::GlobalShortcutListenerMac GSL;
+
+@interface GlobalShortcutListenerTap : NSObject {
+ @public
+ CFMachPortRef eventTap_;
+
+ @private
+ CFRunLoopSourceRef eventTapSource_;
+ CFRunLoopRef tapThreadRunLoop_;
+ GSL *gsl_;
+}
+
+- (id)initWithGSL:(GSL *)gsl;
+- (void)startWatchingMediaKeys;
+- (void)stopWatchingMediaKeys;
+- (void)handleKeyEvent:(NSEvent *)event;
+- (void)handleMediaKeyEvent:(NSEvent *)event;
+- (BOOL)performEventHandlerOnMainThread:(SEL)selector withEvent:(NSEvent*)event;
+@end
+
+// Processed events should propagate if they aren't handled by any listeners.
+// Returning event causes the event to propagate to other applications.
+// Returning NULL prevents the event from propagating.
+static CGEventRef tapEventCallback(
+ CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
+ NSAutoreleasePool *pool = [NSAutoreleasePool new];
Finnur 2013/11/07 10:33:07 nit: We prefer Foo* foo over Foo *foo. (multiple o
smus 2013/11/16 00:21:44 Done.
+
+ GlobalShortcutListenerTap *self = (GlobalShortcutListenerTap*) refcon;
+
+ // Handle the timeout case by re-enabling the tap.
+ if (type == kCGEventTapDisabledByTimeout) {
+ LOG(ERROR) << "Event tap was disabled by a timeout.";
+ CGEventTapEnable(self->eventTap_, TRUE);
+ return event;
+ }
+
+ // TODO(smus): do some error handling since eventWithCGEvent can fail.
+ NSEvent *nsEvent = [NSEvent eventWithCGEvent:event];
+
+ // Handle regular keys.
+ if (type == kCGEventKeyDown) {
+ BOOL wasHandled = [self
+ performEventHandlerOnMainThread:@selector(handleKeyEvent:)
+ withEvent:nsEvent];
+ return (wasHandled ? NULL : event);
+ }
+
+ // Handle media keys only (PlayPause, NextTrack, PreviousTrack).
+ if (type == NX_SYSDEFINED &&
+ [nsEvent subtype] == SystemDefinedEventMediaKeys) {
+ int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
+ if (keyCode == NX_KEYTYPE_PLAY || keyCode == NX_KEYTYPE_NEXT ||
+ keyCode == NX_KEYTYPE_PREVIOUS || keyCode == NX_KEYTYPE_FAST ||
+ keyCode == NX_KEYTYPE_REWIND) {
+ BOOL wasHandled = [self
+ performEventHandlerOnMainThread:@selector(handleMediaKeyEvent:)
+ withEvent:nsEvent];
+ return (wasHandled ? NULL : event);
+ }
+ }
+ [pool drain];
+ // By default, pass the event through.
+ return event;
+}
+
+@implementation GlobalShortcutListenerTap
+
+- (id)initWithGSL:(GSL*)gsl {
+ gsl_ = gsl;
+ return self;
+}
+
+- (void)eventTapThread {
+ tapThreadRunLoop_ = CFRunLoopGetCurrent();
+ CFRunLoopAddSource(tapThreadRunLoop_, eventTapSource_,
+ kCFRunLoopCommonModes);
+ CFRunLoopRun();
+}
+
+- (BOOL)performEventHandlerOnMainThread:(SEL)selector
+ withEvent:(NSEvent*)event {
+ NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+ [dict setObject:event forKey:EVENT_KEY];
+ [self performSelectorOnMainThread:selector
+ withObject:dict waitUntilDone:YES];
+ // Keep track of the result from the main thread to know if the event has
+ // been handled.
+ BOOL wasHandled = [[dict objectForKey:HANDLED_KEY] boolValue];
+ [dict release];
+ return wasHandled;
+}
+
+- (ui::KeyboardCode)mediaKeyCodeToKeyboardCode:(int)keyCode {
+ switch (keyCode) {
+ case NX_KEYTYPE_PLAY:
+ return ui::VKEY_MEDIA_PLAY_PAUSE;
+ case NX_KEYTYPE_PREVIOUS:
+ case NX_KEYTYPE_REWIND:
+ return ui::VKEY_MEDIA_PREV_TRACK;
+ case NX_KEYTYPE_NEXT:
+ case NX_KEYTYPE_FAST:
+ return ui::VKEY_MEDIA_NEXT_TRACK;
+ }
+ return ui::VKEY_UNKNOWN;
+}
+
+- (void)startWatchingMediaKeys {
+ // Prevent having multiple mediaKeys threads
Finnur 2013/11/07 10:33:07 nit: In general, we end all comments in period (na
smus 2013/11/16 00:21:44 Done.
+ [self stopWatchingMediaKeys];
Finnur 2013/11/07 10:33:07 Thinking out loud: Doesn't starting twice indicate
smus 2013/11/16 00:21:44 Yep, changed to an assertion.
+
+ // Add an event tap to intercept the system defined media key events
+ eventTap_ = CGEventTapCreate(kCGSessionEventTap,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionDefault,
+ CGEventMaskBit(NX_SYSDEFINED) | CGEventMaskBit(kCGEventKeyDown),
+ tapEventCallback,
+ self);
+ assert(eventTap_ != NULL);
+
+ eventTapSource_ = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault,
+ eventTap_, 0);
+ assert(eventTapSource_ != NULL);
+
+ // Run the event tap in separate thread to prevent blocking UI.
+ [NSThread detachNewThreadSelector:@selector(eventTapThread)
+ toTarget:self withObject:nil];
+}
+- (void)stopWatchingMediaKeys {
Finnur 2013/11/07 10:33:07 nit: linebreak
smus 2013/11/16 00:21:44 Done.
+ if (tapThreadRunLoop_) {
+ CFRunLoopStop(tapThreadRunLoop_);
+ tapThreadRunLoop_ = nil;
+ }
+
+ if (eventTap_) {
+ CFMachPortInvalidate(eventTap_);
+ CFRelease(eventTap_);
+ eventTap_ = nil;
+ }
+
+ if (eventTapSource_) {
+ CFRelease(eventTapSource_);
+ eventTapSource_ = nil;
+ }
+}
+
+- (void)handleKeyEvent:(NSMutableDictionary *)dict {
+ NSEvent *event = [dict objectForKey:EVENT_KEY];
+
+ ui::KeyboardCode keyCode = ui::KeyboardCodeFromNSEvent(event);
+ int modifiers = 0;
+ NSUInteger flags = [event modifierFlags];
+ modifiers |= (flags & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0;
+ modifiers |= (flags & NSAlternateKeyMask ) ? ui::EF_ALT_DOWN : 0;
+ modifiers |= (flags & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0;
+ modifiers |= (flags & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0;
+
+ bool result = gsl_->OnKeyEvent(keyCode, modifiers);
+ [dict setObject:[NSNumber numberWithBool:result] forKey:HANDLED_KEY];
+}
+
+// Event will have been retained in the other thread.
+- (void)handleMediaKeyEvent:(NSMutableDictionary *)dict {
+ NSEvent *event = [dict objectForKey:EVENT_KEY];
+
+ int keyCode = (([event data1] & 0xFFFF0000) >> 16);
+ int keyFlags = ([event data1] & 0x0000FFFF);
+ BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA;
+ //int keyRepeat = (keyFlags & 0x1);
Finnur 2013/11/07 10:33:07 Remove?
smus 2013/11/16 00:21:44 Done.
+
+ bool result = false;
+ if (keyIsPressed) {
+ result = gsl_->OnMediaKeyEvent([self mediaKeyCodeToKeyboardCode:keyCode]);
+ }
Finnur 2013/11/07 10:33:07 nit: When the if clause is single line we omit the
smus 2013/11/16 00:21:44 Not my favorite, but I will comply :)
+ [dict setObject:[NSNumber numberWithBool:result] forKey:HANDLED_KEY];
+}
+
+
+
Finnur 2013/11/07 10:33:07 nit: lots of extra linebreaks.
smus 2013/11/16 00:21:44 Done.
+@end
+
+
+using content::BrowserThread;
+
+namespace {
+
+static base::LazyInstance<extensions::GlobalShortcutListenerMac> instance =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace extensions {
+
+// static
+GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
+ LOG(ERROR) << "GlobalShortcutListener GetInstance";
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return instance.Pointer();
+}
+
+GlobalShortcutListenerMac::GlobalShortcutListenerMac()
+ : is_listening_(false) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // TODO(implementor): Remove this.
+ LOG(ERROR) << "GlobalShortcutListenerMac object created";
+
+ tap_.reset([[GlobalShortcutListenerTap alloc] initWithGSL:this]);
+}
+
+GlobalShortcutListenerMac::~GlobalShortcutListenerMac() {
+ if (is_listening_)
+ StopListening();
+}
+
+void GlobalShortcutListenerMac::StartListening() {
+ DCHECK(!is_listening_); // Don't start twice.
+ DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is
+ // registered.
+ LOG(ERROR) << "GlobalShortcutListenerMac StartListening";
+ is_listening_ = true;
+
+ [tap_ startWatchingMediaKeys];
+}
+
+void GlobalShortcutListenerMac::StopListening() {
+ DCHECK(is_listening_); // No point if we are not already listening.
+ DCHECK(registered_hot_keys_.empty()); // Make sure the set is clean before
+ // ending.
+ LOG(ERROR) << "GlobalShortcutListenerMac StopListening";
+ is_listening_ = false;
+
+ [tap_ stopWatchingMediaKeys];
+}
+
+void GlobalShortcutListenerMac::RegisterAccelerator(
+ const ui::Accelerator& accelerator,
+ GlobalShortcutListener::Observer* observer) {
+ LOG(ERROR) << "GlobalShortcutListenerMac RegisterAccelerator";
+ VLOG(0) << "Registered keyCode: " << accelerator.key_code()
+ << ", modifiers: " << accelerator.modifiers();
+ // To implement:
+ // 1) Convert modifiers to platform specific modifiers.
+ // 2) Register for the hotkey.
+ // 3) If not successful, log why.
+ // 4) Else, call base class RegisterAccelerator.
+ registered_hot_keys_.insert(accelerator);
+ GlobalShortcutListener::RegisterAccelerator(accelerator, observer);
+}
+
+void GlobalShortcutListenerMac::UnregisterAccelerator(
+ const ui::Accelerator& accelerator,
+ GlobalShortcutListener::Observer* observer) {
+ LOG(ERROR) << "GlobalShortcutListenerMac UnregisterAccelerator";
+ // To implement:
+ // 1) Unregister for the hotkey.
+ // 2) Call base class UnregisterAccelerator.
+
+ registered_hot_keys_.erase(accelerator);
+ GlobalShortcutListener::UnregisterAccelerator(accelerator, observer);
+}
+
+// Returns true iff event was handled.
+bool GlobalShortcutListenerMac::OnKeyEvent(ui::KeyboardCode keyCode,
+ int modifiers) {
+ VLOG(0) << "OnKeyEvent! keyCode: " << keyCode << ", modifiers: " << modifiers;
+ // Create an accelerator corresponding to the keyCode.
+ ui::Accelerator accelerator(keyCode, modifiers);
+ // Look for a match with a bound hotkey.
+ if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) {
+ // If matched, callback to the event handling system.
+ instance.Get().NotifyKeyPressed(accelerator);
+ return true;
+ }
+ return false;
+}
+
+// Returns true iff event was handled.
+bool GlobalShortcutListenerMac::OnMediaKeyEvent(ui::KeyboardCode keyCode) {
+ VLOG(0) << "OnMediaKeyEvent! keyCode: " << keyCode;
+ // Create an accelerator corresponding to the keyCode.
+ ui::Accelerator accelerator(keyCode, 0);
+ // Look for a match with a bound hotkey.
+ if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) {
+ // If matched, callback to the event handling system.
+ instance.Get().NotifyKeyPressed(accelerator);
+ return true;
+ }
+ return false;
+}
+
+} // namespace extensions
« no previous file with comments | « chrome/browser/extensions/global_shortcut_listener_mac.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698