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 | |
Robert Sesek
2013/11/18 15:25:03
Use type-safe C++ constants. Do not use #define fo
smus
2013/11/18 23:13:41
Done.
| |
15 #define EVENT_KEY @"event" | |
16 #define HANDLED_KEY @"handled" | |
17 | |
18 typedef extensions::GlobalShortcutListenerMac GSL; | |
Robert Sesek
2013/11/18 15:25:03
This is not an appropriate typedef. Use |using ext
smus
2013/11/18 23:13:41
Done.
| |
19 | |
20 @interface GlobalShortcutListenerTap : NSObject { | |
21 @public | |
Robert Sesek
2013/11/18 15:25:03
nit: one space before @public. But, I'm not sure y
smus
2013/11/18 23:13:41
Removed @public member.
| |
22 CFMachPortRef eventTap_; | |
23 | |
24 @private | |
25 CFRunLoopSourceRef eventTapSource_; | |
26 CFRunLoopRef tapThreadRunLoop_; | |
27 GSL* gsl_; | |
Robert Sesek
2013/11/18 15:25:03
Similarly, this is not an appropriate variable nam
smus
2013/11/18 23:13:41
Done.
| |
28 } | |
29 | |
30 - (id)initWithGSL:(GSL*)gsl; | |
31 - (void)startWatchingMediaKeys; | |
32 - (void)stopWatchingMediaKeys; | |
33 - (void)handleMediaKeyEvent:(NSEvent*)event; | |
34 - (BOOL)performEventHandlerOnMainThread:(SEL)selector withEvent:(NSEvent*)event; | |
35 @end | |
36 | |
37 // Processed events should propagate if they aren't handled by any listeners. | |
38 // Returning event causes the event to propagate to other applications. | |
39 // Returning NULL prevents the event from propagating. | |
40 static CGEventRef tapEventCallback( | |
Robert Sesek
2013/11/18 15:25:03
naming: TapEventCallback
You mix and match anonym
smus
2013/11/18 23:13:41
Done.
| |
41 CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) { | |
42 NSAutoreleasePool* pool = [NSAutoreleasePool new]; | |
43 CGEventRef ret = event; | |
Robert Sesek
2013/11/18 15:25:03
Naming: |ret|?
smus
2013/11/18 23:13:41
Done.
| |
44 | |
45 GlobalShortcutListenerTap* self = (GlobalShortcutListenerTap*) refcon; | |
Robert Sesek
2013/11/18 15:25:03
C-style casts are banned.
smus
2013/11/18 23:13:41
Changed to static_cast.
| |
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 // Release the event as soon as possible. | |
52 return ret; | |
53 } | |
54 | |
55 // TODO(smus): do some error handling since eventWithCGEvent can fail. | |
56 NSEvent* nsEvent = [NSEvent eventWithCGEvent:event]; | |
Robert Sesek
2013/11/18 15:25:03
Why do you need to convert this to a NSEvent? Can'
Robert Sesek
2013/11/18 15:25:03
Naming: in C/C++, use under_scores for variables.
smus
2013/11/18 23:13:41
I looked for a way of doing this from the CGEvent,
smus
2013/11/18 23:13:41
Done.
Robert Sesek
2013/11/19 18:34:58
int64_t subtype = CGEventGetIntegerValueField(even
smus
2013/11/20 04:28:51
Not sure I follow. Are you suggesting that CGEvent
Robert Sesek
2013/11/20 16:23:57
What about kCGEventSourceUserData?
smus
2013/11/20 17:50:53
No, unfortunately that field is always zero.
in
| |
57 | |
58 // Handle media keys (PlayPause, NextTrack, PreviousTrack). | |
59 if (type != NX_SYSDEFINED || | |
60 [nsEvent subtype] != SystemDefinedEventMediaKeys) { | |
61 int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); | |
62 if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_NEXT && | |
63 keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_FAST && | |
64 keyCode != NX_KEYTYPE_REWIND) { | |
65 // Release the event as soon as possible. | |
66 return ret; | |
67 } | |
68 } | |
69 | |
70 // If we got here, we are dealing with a real media key event. | |
71 BOOL wasHandled = [self | |
72 performEventHandlerOnMainThread:@selector(handleMediaKeyEvent:) | |
73 withEvent:nsEvent]; | |
Finnur
2013/11/18 11:55:57
As I recall, from my brief exposure to Mac, the co
smus
2013/11/18 23:13:41
Couldn't confirm that from http://google-styleguid
| |
74 // Prevent the event from proagating to other mac applications if it was | |
75 // handled by Chrome. | |
76 if (wasHandled) { | |
77 ret = NULL; | |
78 } | |
Finnur
2013/11/18 11:55:57
Yup, you guessed it (single-line if, no braces) :)
smus
2013/11/18 23:13:41
Done.
| |
79 | |
80 [pool drain]; | |
81 // By default, pass the event through. | |
82 return ret; | |
83 } | |
84 | |
85 @implementation GlobalShortcutListenerTap | |
86 | |
87 - (id)initWithGSL:(GSL*)gsl { | |
88 gsl_ = gsl; | |
Robert Sesek
2013/11/18 15:25:03
if ((self = [super init])) {
shortcutListener_ =
smus
2013/11/18 23:13:41
Done.
| |
89 return self; | |
90 } | |
91 | |
92 - (void)eventTapThread { | |
Robert Sesek
2013/11/18 15:25:03
Yikes – an entire thread just to listen for global
smus
2013/11/18 23:13:41
I think this is the best practice. Doing this on t
Robert Sesek
2013/11/19 18:34:58
What are you worried about blocking? You already h
smus
2013/11/20 04:28:51
Good point. Initially the thread was not synchroni
Robert Sesek
2013/11/20 16:23:57
Rebroadcasting the event would post it out-of-orde
smus
2013/11/20 17:50:53
Okay, I've gone this route. I had a (big!) bug bef
| |
93 tapThreadRunLoop_ = CFRunLoopGetCurrent(); | |
94 CFRunLoopAddSource(tapThreadRunLoop_, eventTapSource_, | |
95 kCFRunLoopCommonModes); | |
96 CFRunLoopRun(); | |
97 } | |
98 | |
99 - (BOOL)performEventHandlerOnMainThread:(SEL)selector | |
100 withEvent:(NSEvent*)event { | |
101 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; | |
102 [dict setObject:event forKey:EVENT_KEY]; | |
103 [self performSelectorOnMainThread:selector | |
104 withObject:dict waitUntilDone:YES]; | |
105 // Keep track of the result from the main thread to know if the event has | |
106 // been handled. | |
107 BOOL wasHandled = [[dict objectForKey:HANDLED_KEY] boolValue]; | |
108 [dict release]; | |
109 return wasHandled; | |
110 } | |
111 | |
112 - (ui::KeyboardCode)mediaKeyCodeToKeyboardCode:(int)keyCode { | |
113 switch (keyCode) { | |
114 case NX_KEYTYPE_PLAY: | |
115 return ui::VKEY_MEDIA_PLAY_PAUSE; | |
116 case NX_KEYTYPE_PREVIOUS: | |
117 case NX_KEYTYPE_REWIND: | |
118 return ui::VKEY_MEDIA_PREV_TRACK; | |
119 case NX_KEYTYPE_NEXT: | |
120 case NX_KEYTYPE_FAST: | |
121 return ui::VKEY_MEDIA_NEXT_TRACK; | |
Finnur
2013/11/18 11:55:57
Is there no 'Stop' event?
For example, see Chaobi
smus
2013/11/18 23:13:41
Mac keyboards don't have this button.
| |
122 } | |
123 return ui::VKEY_UNKNOWN; | |
124 } | |
125 | |
126 - (void)startWatchingMediaKeys { | |
127 // Make sure there's no existing event tap. | |
128 assert(eventTap_ == NULL); | |
Robert Sesek
2013/11/18 15:25:03
No assert(). Use Chromium's logging facilities for
smus
2013/11/18 23:13:41
Done.
| |
129 | |
130 // Add an event tap to intercept the system defined media key events. | |
131 eventTap_ = CGEventTapCreate(kCGSessionEventTap, | |
Robert Sesek
2013/11/18 15:25:03
Per https://developer.apple.com/library/mac/docume
Robert Sesek
2013/11/18 18:36:20
Sorry, this isn't accurate. This code isn't tappin
smus
2013/11/18 23:13:41
Self-addressed.
smus
2013/11/18 23:13:41
Done.
| |
132 kCGHeadInsertEventTap, | |
133 kCGEventTapOptionDefault, | |
134 CGEventMaskBit(NX_SYSDEFINED), | |
135 tapEventCallback, | |
136 self); | |
137 assert(eventTap_ != NULL); | |
138 | |
139 eventTapSource_ = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, | |
140 eventTap_, 0); | |
141 assert(eventTapSource_ != NULL); | |
142 | |
143 // Run the event tap in separate thread to prevent blocking UI. | |
144 [NSThread detachNewThreadSelector:@selector(eventTapThread) | |
145 toTarget:self withObject:nil]; | |
146 } | |
147 | |
148 - (void)stopWatchingMediaKeys { | |
149 if (tapThreadRunLoop_) { | |
150 CFRunLoopStop(tapThreadRunLoop_); | |
151 tapThreadRunLoop_ = nil; | |
152 } | |
153 | |
154 if (eventTap_) { | |
155 CFMachPortInvalidate(eventTap_); | |
156 CFRelease(eventTap_); | |
157 eventTap_ = nil; | |
158 } | |
159 | |
160 if (eventTapSource_) { | |
161 CFRelease(eventTapSource_); | |
162 eventTapSource_ = nil; | |
163 } | |
164 } | |
165 | |
166 // Event will have been retained in the other thread. | |
167 - (void)handleMediaKeyEvent:(NSMutableDictionary*)dict { | |
168 NSEvent* event = [dict objectForKey:EVENT_KEY]; | |
169 | |
170 int keyCode = (([event data1] & 0xFFFF0000) >> 16); | |
171 int keyFlags = ([event data1] & 0x0000FFFF); | |
172 BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA; | |
173 | |
174 bool result = false; | |
175 if (keyIsPressed) | |
176 result = gsl_->OnMediaKeyEvent([self mediaKeyCodeToKeyboardCode:keyCode]); | |
177 | |
178 [dict setObject:[NSNumber numberWithBool:result] forKey:HANDLED_KEY]; | |
179 } | |
180 | |
181 @end | |
182 | |
183 using content::BrowserThread; | |
Robert Sesek
2013/11/18 15:25:03
This belongs after the #includes, not here.
smus
2013/11/18 23:13:41
Done.
| |
184 | |
185 namespace { | |
186 | |
187 static base::LazyInstance<extensions::GlobalShortcutListenerMac> instance = | |
Robert Sesek
2013/11/18 15:25:03
Naming: g_instance
smus
2013/11/18 23:13:41
Done.
| |
188 LAZY_INSTANCE_INITIALIZER; | |
189 | |
190 } // namespace | |
191 | |
192 namespace extensions { | |
193 | |
194 // static | |
195 GlobalShortcutListener* GlobalShortcutListener::GetInstance() { | |
196 LOG(ERROR) << "GlobalShortcutListener GetInstance"; | |
197 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
198 return instance.Pointer(); | |
199 } | |
200 | |
201 GlobalShortcutListenerMac::GlobalShortcutListenerMac() | |
202 : is_listening_(false) { | |
203 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
204 | |
205 // TODO(implementor): Remove this. | |
206 LOG(ERROR) << "GlobalShortcutListenerMac object created"; | |
Finnur
2013/11/18 11:55:57
You can remove this and other LOG(ERROR) statement
smus
2013/11/18 23:13:41
Done.
| |
207 | |
208 tap_.reset([[GlobalShortcutListenerTap alloc] initWithGSL:this]); | |
209 } | |
210 | |
211 GlobalShortcutListenerMac::~GlobalShortcutListenerMac() { | |
Robert Sesek
2013/11/18 15:25:03
What about all the registered hotkeys? Won't those
smus
2013/11/18 23:13:41
Following other implementations (X11 and Win). I t
Finnur
2013/11/19 13:10:05
Yes, we'll get calls to Unregister before the obje
| |
212 if (is_listening_) | |
213 StopListening(); | |
214 } | |
215 | |
216 void GlobalShortcutListenerMac::StartListening() { | |
Robert Sesek
2013/11/18 15:25:03
The Start/StopListeneing pair of methods only affe
smus
2013/11/18 23:13:41
Added clarifying comment.
Finnur
2013/11/19 13:10:05
So, this Start/StopListening pair is meant as a co
smus
2013/11/20 04:28:51
Ok, made this change.
| |
217 DCHECK(!is_listening_); // Don't start twice. | |
218 DCHECK(!hotkey_ids_.empty()); // Don't start if no hotkey registered. | |
219 DCHECK(!id_hotkeys_.empty()); | |
220 DCHECK(!id_hotkey_refs_.empty()); | |
221 LOG(ERROR) << "GlobalShortcutListenerMac StartListening"; | |
222 is_listening_ = true; | |
223 | |
224 [tap_ startWatchingMediaKeys]; | |
225 } | |
226 | |
227 void GlobalShortcutListenerMac::StopListening() { | |
228 DCHECK(is_listening_); // No point if we are not already listening. | |
229 DCHECK(hotkey_ids_.empty()); // Make sure the set is clean. | |
230 DCHECK(id_hotkeys_.empty()); | |
231 DCHECK(id_hotkey_refs_.empty()); | |
232 LOG(ERROR) << "GlobalShortcutListenerMac StopListening"; | |
233 is_listening_ = false; | |
234 | |
235 [tap_ stopWatchingMediaKeys]; | |
236 } | |
237 | |
238 void GlobalShortcutListenerMac::RegisterAccelerator( | |
239 const ui::Accelerator& accelerator, | |
240 GlobalShortcutListener::Observer* observer) { | |
241 LOG(ERROR) << "GlobalShortcutListenerMac RegisterAccelerator"; | |
242 VLOG(0) << "Registered keyCode: " << accelerator.key_code() | |
243 << ", modifiers: " << accelerator.modifiers(); | |
244 // To implement: | |
245 // 1) Convert modifiers to platform specific modifiers. | |
246 bool isMediaKey = (accelerator.modifiers() == 0); | |
247 VLOG(0) << "isMediaKey: " << isMediaKey; | |
248 if (!isMediaKey) { | |
249 RegisterHotKey(accelerator); | |
250 } | |
Finnur
2013/11/18 11:55:57
nit: Single line. :)
smus
2013/11/18 23:13:41
Done.
| |
251 // 2) Register for the hotkey. | |
252 // 3) If not successful, log why. | |
253 // 4) Else, call base class RegisterAccelerator. | |
Finnur
2013/11/18 11:55:57
You can remove these comments, they were only mean
smus
2013/11/18 23:13:41
Done.
| |
254 id_hotkeys_[hotkey_id] = accelerator; | |
255 hotkey_ids_[accelerator] = hotkey_id; | |
256 hotkey_id += 1; | |
257 GlobalShortcutListener::RegisterAccelerator(accelerator, observer); | |
258 } | |
259 | |
260 void GlobalShortcutListenerMac::UnregisterAccelerator( | |
261 const ui::Accelerator& accelerator, | |
262 GlobalShortcutListener::Observer* observer) { | |
263 LOG(ERROR) << "GlobalShortcutListenerMac UnregisterAccelerator"; | |
264 // To implement: | |
265 // 1) Unregister for the hotkey. | |
266 // 2) Call base class UnregisterAccelerator. | |
Finnur
2013/11/18 11:55:57
Same here.
smus
2013/11/18 23:13:41
Done.
| |
267 bool isMediaKey = (accelerator.modifiers() == 0); | |
Finnur
2013/11/18 11:55:57
Chaobin is adding a static IsMediaKey function to
smus
2013/11/18 23:13:41
Oh excellent, I added my own for now.
| |
268 VLOG(0) << "isMediaKey: " << isMediaKey; | |
269 if (!isMediaKey) { | |
270 UnregisterHotKey(accelerator); | |
271 } | |
Finnur
2013/11/18 11:55:57
nit: Single line, your favorite. :)
smus
2013/11/18 23:13:41
Done.
| |
272 | |
273 int id = hotkey_ids_[accelerator]; | |
274 id_hotkeys_.erase(id); | |
275 hotkey_ids_.erase(accelerator); | |
276 GlobalShortcutListener::UnregisterAccelerator(accelerator, observer); | |
277 } | |
278 | |
279 bool GlobalShortcutListenerMac::OnKeyEvent(EventHotKeyID hotKeyID) { | |
280 // Look up the accelerator based on this hot key ID. | |
281 VLOG(0) << "OnKeyEvent! hotKeyID: " << hotKeyID.id; | |
282 ui::Accelerator accelerator = id_hotkeys_[hotKeyID.id]; | |
283 VLOG(0) << "Key code: " << accelerator.key_code() << | |
284 " modifiers: " << accelerator.modifiers(); | |
285 instance.Get().NotifyKeyPressed(accelerator); | |
Robert Sesek
2013/11/18 15:25:03
Why are you not using the |this| pointer? This met
smus
2013/11/18 23:13:41
Fair point (I think). If so, same should apply to
Finnur
2013/11/19 13:10:05
Yeah, looks like.
On 2013/11/18 23:13:41, smus wr
| |
286 return true; | |
287 } | |
288 | |
289 // Returns true iff event was handled. | |
290 bool GlobalShortcutListenerMac::OnMediaKeyEvent(ui::KeyboardCode keyCode) { | |
291 VLOG(0) << "OnMediaKeyEvent! keyCode: " << keyCode; | |
292 // Create an accelerator corresponding to the keyCode. | |
293 ui::Accelerator accelerator(keyCode, 0); | |
294 // Look for a match with a bound hotkey. | |
295 if (hotkey_ids_.find(accelerator) != hotkey_ids_.end()) { | |
296 // If matched, callback to the event handling system. | |
297 instance.Get().NotifyKeyPressed(accelerator); | |
298 return true; | |
299 } | |
300 return false; | |
301 } | |
302 | |
303 OSStatus HotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, | |
Robert Sesek
2013/11/18 15:25:03
This doesn't belong here. This belongs with your o
Robert Sesek
2013/11/18 15:25:03
naming: You use HotKey here but Hotkey elsewhere.
smus
2013/11/18 23:13:41
Done.
smus
2013/11/18 23:13:41
Done.
| |
304 void *userData) { | |
305 VLOG(0) << "HotKeyHandler fired with event: " << theEvent; | |
306 // Extract the hotkey from the event. | |
307 EventHotKeyID hotKeyID; | |
308 int result = GetEventParameter(theEvent, kEventParamDirectObject, | |
309 typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); | |
310 assert(result == noErr); | |
311 | |
312 // Callback to the parent class. | |
313 GlobalShortcutListenerMac* gsl = (GlobalShortcutListenerMac*) userData; | |
314 gsl->OnKeyEvent(hotKeyID); | |
315 return noErr; | |
316 } | |
317 | |
318 void GlobalShortcutListenerMac::RegisterHotKey(ui::Accelerator accelerator) { | |
319 VLOG(0) << "Registering hotkey. Windows keycode: " << accelerator.key_code(); | |
320 EventHotKeyRef hotKeyRef; | |
321 EventHotKeyID hotKeyID; | |
322 EventHandlerUPP hotKeyFunction = NewEventHandlerUPP(HotKeyHandler); | |
323 | |
324 EventTypeSpec eventType; | |
325 eventType.eventClass = kEventClassKeyboard; | |
326 eventType.eventKind = kEventHotKeyPressed; | |
327 InstallApplicationEventHandler(hotKeyFunction, 1, &eventType, this, NULL); | |
328 | |
329 // TODO: Understand what signature means. | |
Robert Sesek
2013/11/18 15:25:03
I think this is important to understand before com
smus
2013/11/18 23:13:41
Agreed. Added a comment explaining what it is.
| |
330 hotKeyID.signature = 31337; | |
331 hotKeyID.id = hotkey_id; | |
332 | |
333 // Translate ui::Accelerator modifiers to cmdKey, altKey, etc. | |
334 int modifiers = 0; | |
335 modifiers += (accelerator.IsShiftDown() ? shiftKey : 0); | |
336 modifiers += (accelerator.IsCtrlDown() ? controlKey : 0); | |
337 modifiers += (accelerator.IsAltDown() ? optionKey : 0); | |
338 modifiers += (accelerator.IsCmdDown() ? cmdKey : 0); | |
339 | |
340 unichar character; | |
341 unichar characterIgnoringModifiers; | |
342 int keyCode = ui::MacKeyCodeForWindowsKeyCode(accelerator.key_code(), 0, | |
343 &character, &characterIgnoringModifiers); | |
344 VLOG(0) << "RegisterHotKey. Code: " << keyCode << " modifier: " << modifiers; | |
345 | |
346 // TODO: Move away from hardcoded CMD + Enter event. | |
Finnur
2013/11/18 11:55:57
What does this mean?
smus
2013/11/18 23:13:41
Stale comment. Removed.
| |
347 RegisterEventHotKey(keyCode, modifiers, hotKeyID, | |
348 GetApplicationEventTarget(), 0, &hotKeyRef); | |
349 | |
350 id_hotkey_refs_[hotkey_id] = hotKeyRef; | |
Finnur
2013/11/18 11:55:57
Don't you need to advance hotkey_id now?
smus
2013/11/18 23:13:41
Added a comment explaining (it happens in the call
| |
351 } | |
352 | |
353 void GlobalShortcutListenerMac::UnregisterHotKey(ui::Accelerator accelerator) { | |
354 // Get the ref corresponding to this accelerator. | |
355 int id = hotkey_ids_[accelerator]; | |
356 EventHotKeyRef ref = id_hotkey_refs_[id]; | |
357 // Unregister the event hot key. | |
358 UnregisterEventHotKey(ref); | |
359 | |
360 // Remove the event from the mapping. | |
361 id_hotkey_refs_.erase(id); | |
362 } | |
363 | |
364 | |
Finnur
2013/11/18 11:55:57
nit: Extra line break.
smus
2013/11/18 23:13:41
Done.
| |
365 } // namespace extensions | |
OLD | NEW |