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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « chrome/browser/extensions/global_shortcut_listener_mac.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« 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