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 |