4 // Modified by Gaurav Khanna on 8/17/10.
5 // SOURCE: http://github.com/sweetfm/SweetFM/blob/master/Source/HMediaKeys.m
6 // SOURCE: http://stackoverflow.com/questions/2969110/cgeventtapcreate-breaks-down-mysteriously-with-key-down-events
9 // Permission is hereby granted, free of charge, to any person
10 // obtaining a copy of this software and associated documentation
11 // files (the "Software"), to deal in the Software without restriction,
12 // including without limitation the rights to use, copy, modify,
13 // merge, publish, distribute, sublicense, and/or sell copies of
14 // the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
23 // ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
24 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #import "HotKeyController.h"
29 #import <IOKit/hidsystem/ev_keymap.h>
30 #import <sys/sysctl.h>
32 NSString* const MediaKeyPower = @"MediaKeyPower";
33 NSString* const MediaKeySoundMute = @"MediaKeySoundMute";
34 NSString* const MediaKeySoundUp = @"MediaKeySoundUp";
35 NSString* const MediaKeySoundDown = @"MediaKeySoundDown";
36 NSString* const MediaKeyPlayPauseNotification = @"MediaKeyPlayPauseNotification";
37 NSString* const MediaKeyFastNotification = @"MediaKeyFastNotification";
38 NSString* const MediaKeyRewindNotification = @"MediaKeyRewindNotification";
39 NSString* const MediaKeyNextNotification = @"MediaKeyNextNotification";
40 NSString* const MediaKeyPreviousNotification = @"MediaKeyPreviousNotification";
42 #ifndef kCGEventTapOptionDefault
43 #define kCGEventTapOptionDefault 0
46 #define NX_KEYSTATE_DOWN 0x0A
47 #define NX_KEYSTATE_UP 0x0B
49 @implementation HotKeyController
51 + (HotKeyController*)sharedController
53 static HotKeyController *sharedHotKeyController = nil;
54 if (sharedHotKeyController == nil)
55 sharedHotKeyController = [[super allocWithZone:NULL] init];
57 return sharedHotKeyController;
60 + (id)allocWithZone:(NSZone *)zone
62 return [[self sharedController] retain];
65 - (id)copyWithZone:(NSZone *)zone
75 - (NSUInteger)retainCount
77 //denotes an object that cannot be released
81 - (oneway void)release
91 - (CFMachPortRef)eventPort
96 - (void)sysPower: (BOOL)enable;
98 m_controlSysPower = enable;
100 - (BOOL)controlPower;
102 return m_controlSysPower;
105 - (void)sysVolume: (BOOL)enable;
107 m_controlSysVolume = enable;
109 - (BOOL)controlVolume;
111 return m_controlSysVolume;
114 - (void)setActive: (BOOL)active
124 - (BOOL)getDebuggerActive
126 // Technical Q&A QA1361
127 // returns true if the current process is being debugged (either
128 // running under the debugger or has a debugger attached post facto).
131 struct kinfo_proc info;
134 // initialize the flags so that, if sysctl fails for some bizarre
135 // reason, we get a predictable result.
136 info.kp_proc.p_flag = 0;
138 // initialize mib, which tells sysctl the info we want, in this case
139 // we are looking for information about a specific process ID.
142 mib[2] = KERN_PROC_PID;
147 junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
150 // we are being debugged if the P_TRACED flag is set.
151 return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
154 // WARNING: do not debugger breakpoint in this routine.
155 // It's a system level call back that taps ALL Events
156 // and you WILL lose all key control :)
157 static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
159 HotKeyController *hot_key_controller = (HotKeyController*)refcon;
161 if (type == kCGEventTapDisabledByTimeout)
163 if ([hot_key_controller getActive])
164 CGEventTapEnable([hot_key_controller eventPort], TRUE);
168 if ((type != NX_SYSDEFINED) || (![hot_key_controller getActive]))
171 NSEvent *nsEvent = [NSEvent eventWithCGEvent:event];
172 if (!nsEvent || [nsEvent subtype] != 8)
175 int data = [nsEvent data1];
176 int keyCode = (data & 0xFFFF0000) >> 16;
177 int keyFlags = (data & 0xFFFF);
178 int keyState = (keyFlags & 0xFF00) >> 8;
179 BOOL keyIsRepeat = (keyFlags & 0x1) > 0;
181 // allow repeated keypresses for volume buttons
182 // all other repeated keypresses are handled by the os (is this really good?)
183 if (keyIsRepeat && keyCode != NX_KEYTYPE_SOUND_UP && keyCode != NX_KEYTYPE_SOUND_DOWN)
186 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
190 if ([hot_key_controller controlPower])
192 if (keyState == NX_KEYSTATE_DOWN)
193 [center postNotificationName:MediaKeyPower object:(HotKeyController *)refcon];
194 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
198 case NX_KEYTYPE_MUTE:
199 if ([hot_key_controller controlVolume])
201 if (keyState == NX_KEYSTATE_DOWN)
202 [center postNotificationName:MediaKeySoundMute object:(HotKeyController *)refcon];
203 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
207 case NX_KEYTYPE_SOUND_UP:
208 if ([hot_key_controller controlVolume])
210 if (keyState == NX_KEYSTATE_DOWN)
211 [center postNotificationName:MediaKeySoundUp object:(HotKeyController *)refcon];
212 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
216 case NX_KEYTYPE_SOUND_DOWN:
217 if ([hot_key_controller controlVolume])
219 if (keyState == NX_KEYSTATE_DOWN)
220 [center postNotificationName:MediaKeySoundDown object:(HotKeyController *)refcon];
221 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
225 case NX_KEYTYPE_PLAY:
226 if (keyState == NX_KEYSTATE_DOWN)
227 [center postNotificationName:MediaKeyPlayPauseNotification object:(HotKeyController *)refcon];
228 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
231 case NX_KEYTYPE_FAST:
232 if (keyState == NX_KEYSTATE_DOWN)
233 [center postNotificationName:MediaKeyFastNotification object:(HotKeyController *)refcon];
234 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
237 case NX_KEYTYPE_REWIND:
238 if (keyState == NX_KEYSTATE_DOWN)
239 [center postNotificationName:MediaKeyRewindNotification object:(HotKeyController *)refcon];
240 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
243 case NX_KEYTYPE_NEXT:
244 if (keyState == NX_KEYSTATE_DOWN)
245 [center postNotificationName:MediaKeyNextNotification object:(HotKeyController *)refcon];
246 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
249 case NX_KEYTYPE_PREVIOUS:
250 if (keyState == NX_KEYSTATE_DOWN)
251 [center postNotificationName:MediaKeyPreviousNotification object:(HotKeyController *)refcon];
252 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
259 static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
261 NSAutoreleasePool *pool = [NSAutoreleasePool new];
262 CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
268 -(void)eventTapThread
270 m_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, m_eventPort, 0);
271 CFRunLoopAddSource(CFRunLoopGetCurrent(), m_runLoopSource, kCFRunLoopCommonModes);
272 // Enable the event tap.
273 CGEventTapEnable(m_eventPort, TRUE);
278 // Disable the event tap.
280 CGEventTapEnable(m_eventPort, FALSE);
283 CFRelease(m_runLoopSource);
284 m_runLoopSource = NULL;
286 CFRelease(m_eventPort);
292 if (self = [super init])
296 m_runLoopSource = NULL;
297 // power button controls xbmc sleep button (this will also trigger the osx shutdown menu - we can't prevent this as it seems)
298 m_controlSysPower = YES;
299 m_controlSysVolume = YES; // volume keys control xbmc volume
306 if (![self getDebuggerActive] && ![self getActive] && floor(NSAppKitVersionNumber) >= 949)
308 // check runtime, we only allow this on 10.5+
309 m_eventPort = CGEventTapCreate(kCGSessionEventTap,
310 kCGHeadInsertEventTap, kCGEventTapOptionDefault,
311 CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, self);
312 if (m_eventPort != NULL)
314 // Run this in a separate thread so that a slow app
315 // doesn't lag the event tap
316 [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
317 [self setActive:YES];
324 if ([self getActive])
328 // Disable the event tap.
330 CGEventTapEnable(m_eventPort, FALSE);
333 CFRelease(m_runLoopSource);
334 m_runLoopSource = NULL;
336 CFRelease(m_eventPort);