Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/global_shortcut_listener_mac.h" | |
| 6 | |
| 7 #import <Cocoa/Cocoa.h> | |
| 8 | |
| 9 #include "content/public/browser/browser_thread.h" | |
| 10 #include "ui/events/event.h" | |
| 11 #include "ui/base/accelerators/accelerator.h" | |
| 12 #import "ui/events/keycodes/keyboard_code_conversion_mac.h" | |
| 13 | |
| 14 #define SystemDefinedEventMediaKeys 8 | |
| 15 #define EVENT_KEY @"event" | |
| 16 #define HANDLED_KEY @"handled" | |
| 17 | |
| 18 typedef extensions::GlobalShortcutListenerMac GSL; | |
| 19 | |
| 20 @interface GlobalShortcutListenerTap : NSObject { | |
| 21 @public | |
| 22 CFMachPortRef eventTap_; | |
| 23 | |
| 24 @private | |
| 25 CFRunLoopSourceRef eventTapSource_; | |
| 26 CFRunLoopRef tapThreadRunLoop_; | |
| 27 GSL *gsl_; | |
| 28 } | |
| 29 | |
| 30 - (id)initWithGSL:(GSL *)gsl; | |
| 31 - (void)startWatchingMediaKeys; | |
| 32 - (void)stopWatchingMediaKeys; | |
| 33 - (void)handleKeyEvent:(NSEvent *)event; | |
| 34 - (void)handleMediaKeyEvent:(NSEvent *)event; | |
| 35 - (BOOL)performEventHandlerOnMainThread:(SEL)selector withEvent:(NSEvent*)event; | |
| 36 @end | |
| 37 | |
| 38 // Processed events should propagate if they aren't handled by any listeners. | |
| 39 // Returning event causes the event to propagate to other applications. | |
| 40 // Returning NULL prevents the event from propagating. | |
| 41 static CGEventRef tapEventCallback( | |
| 42 CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { | |
| 43 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.
| |
| 44 | |
| 45 GlobalShortcutListenerTap *self = (GlobalShortcutListenerTap*) refcon; | |
| 46 | |
| 47 // Handle the timeout case by re-enabling the tap. | |
| 48 if (type == kCGEventTapDisabledByTimeout) { | |
| 49 LOG(ERROR) << "Event tap was disabled by a timeout."; | |
| 50 CGEventTapEnable(self->eventTap_, TRUE); | |
| 51 return event; | |
| 52 } | |
| 53 | |
| 54 // TODO(smus): do some error handling since eventWithCGEvent can fail. | |
| 55 NSEvent *nsEvent = [NSEvent eventWithCGEvent:event]; | |
| 56 | |
| 57 // Handle regular keys. | |
| 58 if (type == kCGEventKeyDown) { | |
| 59 BOOL wasHandled = [self | |
| 60 performEventHandlerOnMainThread:@selector(handleKeyEvent:) | |
| 61 withEvent:nsEvent]; | |
| 62 return (wasHandled ? NULL : event); | |
| 63 } | |
| 64 | |
| 65 // Handle media keys only (PlayPause, NextTrack, PreviousTrack). | |
| 66 if (type == NX_SYSDEFINED && | |
| 67 [nsEvent subtype] == SystemDefinedEventMediaKeys) { | |
| 68 int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); | |
| 69 if (keyCode == NX_KEYTYPE_PLAY || keyCode == NX_KEYTYPE_NEXT || | |
| 70 keyCode == NX_KEYTYPE_PREVIOUS || keyCode == NX_KEYTYPE_FAST || | |
| 71 keyCode == NX_KEYTYPE_REWIND) { | |
| 72 BOOL wasHandled = [self | |
| 73 performEventHandlerOnMainThread:@selector(handleMediaKeyEvent:) | |
| 74 withEvent:nsEvent]; | |
| 75 return (wasHandled ? NULL : event); | |
| 76 } | |
| 77 } | |
| 78 [pool drain]; | |
| 79 // By default, pass the event through. | |
| 80 return event; | |
| 81 } | |
| 82 | |
| 83 @implementation GlobalShortcutListenerTap | |
| 84 | |
| 85 - (id)initWithGSL:(GSL*)gsl { | |
| 86 gsl_ = gsl; | |
| 87 return self; | |
| 88 } | |
| 89 | |
| 90 - (void)eventTapThread { | |
| 91 tapThreadRunLoop_ = CFRunLoopGetCurrent(); | |
| 92 CFRunLoopAddSource(tapThreadRunLoop_, eventTapSource_, | |
| 93 kCFRunLoopCommonModes); | |
| 94 CFRunLoopRun(); | |
| 95 } | |
| 96 | |
| 97 - (BOOL)performEventHandlerOnMainThread:(SEL)selector | |
| 98 withEvent:(NSEvent*)event { | |
| 99 NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; | |
| 100 [dict setObject:event forKey:EVENT_KEY]; | |
| 101 [self performSelectorOnMainThread:selector | |
| 102 withObject:dict waitUntilDone:YES]; | |
| 103 // Keep track of the result from the main thread to know if the event has | |
| 104 // been handled. | |
| 105 BOOL wasHandled = [[dict objectForKey:HANDLED_KEY] boolValue]; | |
| 106 [dict release]; | |
| 107 return wasHandled; | |
| 108 } | |
| 109 | |
| 110 - (ui::KeyboardCode)mediaKeyCodeToKeyboardCode:(int)keyCode { | |
| 111 switch (keyCode) { | |
| 112 case NX_KEYTYPE_PLAY: | |
| 113 return ui::VKEY_MEDIA_PLAY_PAUSE; | |
| 114 case NX_KEYTYPE_PREVIOUS: | |
| 115 case NX_KEYTYPE_REWIND: | |
| 116 return ui::VKEY_MEDIA_PREV_TRACK; | |
| 117 case NX_KEYTYPE_NEXT: | |
| 118 case NX_KEYTYPE_FAST: | |
| 119 return ui::VKEY_MEDIA_NEXT_TRACK; | |
| 120 } | |
| 121 return ui::VKEY_UNKNOWN; | |
| 122 } | |
| 123 | |
| 124 - (void)startWatchingMediaKeys { | |
| 125 // 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.
| |
| 126 [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.
| |
| 127 | |
| 128 // Add an event tap to intercept the system defined media key events | |
| 129 eventTap_ = CGEventTapCreate(kCGSessionEventTap, | |
| 130 kCGHeadInsertEventTap, | |
| 131 kCGEventTapOptionDefault, | |
| 132 CGEventMaskBit(NX_SYSDEFINED) | CGEventMaskBit(kCGEventKeyDown), | |
| 133 tapEventCallback, | |
| 134 self); | |
| 135 assert(eventTap_ != NULL); | |
| 136 | |
| 137 eventTapSource_ = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, | |
| 138 eventTap_, 0); | |
| 139 assert(eventTapSource_ != NULL); | |
| 140 | |
| 141 // Run the event tap in separate thread to prevent blocking UI. | |
| 142 [NSThread detachNewThreadSelector:@selector(eventTapThread) | |
| 143 toTarget:self withObject:nil]; | |
| 144 } | |
| 145 - (void)stopWatchingMediaKeys { | |
|
Finnur
2013/11/07 10:33:07
nit: linebreak
smus
2013/11/16 00:21:44
Done.
| |
| 146 if (tapThreadRunLoop_) { | |
| 147 CFRunLoopStop(tapThreadRunLoop_); | |
| 148 tapThreadRunLoop_ = nil; | |
| 149 } | |
| 150 | |
| 151 if (eventTap_) { | |
| 152 CFMachPortInvalidate(eventTap_); | |
| 153 CFRelease(eventTap_); | |
| 154 eventTap_ = nil; | |
| 155 } | |
| 156 | |
| 157 if (eventTapSource_) { | |
| 158 CFRelease(eventTapSource_); | |
| 159 eventTapSource_ = nil; | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 - (void)handleKeyEvent:(NSMutableDictionary *)dict { | |
| 164 NSEvent *event = [dict objectForKey:EVENT_KEY]; | |
| 165 | |
| 166 ui::KeyboardCode keyCode = ui::KeyboardCodeFromNSEvent(event); | |
| 167 int modifiers = 0; | |
| 168 NSUInteger flags = [event modifierFlags]; | |
| 169 modifiers |= (flags & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0; | |
| 170 modifiers |= (flags & NSAlternateKeyMask ) ? ui::EF_ALT_DOWN : 0; | |
| 171 modifiers |= (flags & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0; | |
| 172 modifiers |= (flags & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0; | |
| 173 | |
| 174 bool result = gsl_->OnKeyEvent(keyCode, modifiers); | |
| 175 [dict setObject:[NSNumber numberWithBool:result] forKey:HANDLED_KEY]; | |
| 176 } | |
| 177 | |
| 178 // Event will have been retained in the other thread. | |
| 179 - (void)handleMediaKeyEvent:(NSMutableDictionary *)dict { | |
| 180 NSEvent *event = [dict objectForKey:EVENT_KEY]; | |
| 181 | |
| 182 int keyCode = (([event data1] & 0xFFFF0000) >> 16); | |
| 183 int keyFlags = ([event data1] & 0x0000FFFF); | |
| 184 BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA; | |
| 185 //int keyRepeat = (keyFlags & 0x1); | |
|
Finnur
2013/11/07 10:33:07
Remove?
smus
2013/11/16 00:21:44
Done.
| |
| 186 | |
| 187 bool result = false; | |
| 188 if (keyIsPressed) { | |
| 189 result = gsl_->OnMediaKeyEvent([self mediaKeyCodeToKeyboardCode:keyCode]); | |
| 190 } | |
|
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 :)
| |
| 191 [dict setObject:[NSNumber numberWithBool:result] forKey:HANDLED_KEY]; | |
| 192 } | |
| 193 | |
| 194 | |
| 195 | |
|
Finnur
2013/11/07 10:33:07
nit: lots of extra linebreaks.
smus
2013/11/16 00:21:44
Done.
| |
| 196 @end | |
| 197 | |
| 198 | |
| 199 using content::BrowserThread; | |
| 200 | |
| 201 namespace { | |
| 202 | |
| 203 static base::LazyInstance<extensions::GlobalShortcutListenerMac> instance = | |
| 204 LAZY_INSTANCE_INITIALIZER; | |
| 205 | |
| 206 } // namespace | |
| 207 | |
| 208 namespace extensions { | |
| 209 | |
| 210 // static | |
| 211 GlobalShortcutListener* GlobalShortcutListener::GetInstance() { | |
| 212 LOG(ERROR) << "GlobalShortcutListener GetInstance"; | |
| 213 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 214 return instance.Pointer(); | |
| 215 } | |
| 216 | |
| 217 GlobalShortcutListenerMac::GlobalShortcutListenerMac() | |
| 218 : is_listening_(false) { | |
| 219 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 220 | |
| 221 // TODO(implementor): Remove this. | |
| 222 LOG(ERROR) << "GlobalShortcutListenerMac object created"; | |
| 223 | |
| 224 tap_.reset([[GlobalShortcutListenerTap alloc] initWithGSL:this]); | |
| 225 } | |
| 226 | |
| 227 GlobalShortcutListenerMac::~GlobalShortcutListenerMac() { | |
| 228 if (is_listening_) | |
| 229 StopListening(); | |
| 230 } | |
| 231 | |
| 232 void GlobalShortcutListenerMac::StartListening() { | |
| 233 DCHECK(!is_listening_); // Don't start twice. | |
| 234 DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is | |
| 235 // registered. | |
| 236 LOG(ERROR) << "GlobalShortcutListenerMac StartListening"; | |
| 237 is_listening_ = true; | |
| 238 | |
| 239 [tap_ startWatchingMediaKeys]; | |
| 240 } | |
| 241 | |
| 242 void GlobalShortcutListenerMac::StopListening() { | |
| 243 DCHECK(is_listening_); // No point if we are not already listening. | |
| 244 DCHECK(registered_hot_keys_.empty()); // Make sure the set is clean before | |
| 245 // ending. | |
| 246 LOG(ERROR) << "GlobalShortcutListenerMac StopListening"; | |
| 247 is_listening_ = false; | |
| 248 | |
| 249 [tap_ stopWatchingMediaKeys]; | |
| 250 } | |
| 251 | |
| 252 void GlobalShortcutListenerMac::RegisterAccelerator( | |
| 253 const ui::Accelerator& accelerator, | |
| 254 GlobalShortcutListener::Observer* observer) { | |
| 255 LOG(ERROR) << "GlobalShortcutListenerMac RegisterAccelerator"; | |
| 256 VLOG(0) << "Registered keyCode: " << accelerator.key_code() | |
| 257 << ", modifiers: " << accelerator.modifiers(); | |
| 258 // To implement: | |
| 259 // 1) Convert modifiers to platform specific modifiers. | |
| 260 // 2) Register for the hotkey. | |
| 261 // 3) If not successful, log why. | |
| 262 // 4) Else, call base class RegisterAccelerator. | |
| 263 registered_hot_keys_.insert(accelerator); | |
| 264 GlobalShortcutListener::RegisterAccelerator(accelerator, observer); | |
| 265 } | |
| 266 | |
| 267 void GlobalShortcutListenerMac::UnregisterAccelerator( | |
| 268 const ui::Accelerator& accelerator, | |
| 269 GlobalShortcutListener::Observer* observer) { | |
| 270 LOG(ERROR) << "GlobalShortcutListenerMac UnregisterAccelerator"; | |
| 271 // To implement: | |
| 272 // 1) Unregister for the hotkey. | |
| 273 // 2) Call base class UnregisterAccelerator. | |
| 274 | |
| 275 registered_hot_keys_.erase(accelerator); | |
| 276 GlobalShortcutListener::UnregisterAccelerator(accelerator, observer); | |
| 277 } | |
| 278 | |
| 279 // Returns true iff event was handled. | |
| 280 bool GlobalShortcutListenerMac::OnKeyEvent(ui::KeyboardCode keyCode, | |
| 281 int modifiers) { | |
| 282 VLOG(0) << "OnKeyEvent! keyCode: " << keyCode << ", modifiers: " << modifiers; | |
| 283 // Create an accelerator corresponding to the keyCode. | |
| 284 ui::Accelerator accelerator(keyCode, modifiers); | |
| 285 // Look for a match with a bound hotkey. | |
| 286 if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) { | |
| 287 // If matched, callback to the event handling system. | |
| 288 instance.Get().NotifyKeyPressed(accelerator); | |
| 289 return true; | |
| 290 } | |
| 291 return false; | |
| 292 } | |
| 293 | |
| 294 // Returns true iff event was handled. | |
| 295 bool GlobalShortcutListenerMac::OnMediaKeyEvent(ui::KeyboardCode keyCode) { | |
| 296 VLOG(0) << "OnMediaKeyEvent! keyCode: " << keyCode; | |
| 297 // Create an accelerator corresponding to the keyCode. | |
| 298 ui::Accelerator accelerator(keyCode, 0); | |
| 299 // Look for a match with a bound hotkey. | |
| 300 if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) { | |
| 301 // If matched, callback to the event handling system. | |
| 302 instance.Get().NotifyKeyPressed(accelerator); | |
| 303 return true; | |
| 304 } | |
| 305 return false; | |
| 306 } | |
| 307 | |
| 308 } // namespace extensions | |
| OLD | NEW |