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>
31 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
32 #if __LP64__ || NS_BUILD_32_LIKE_64
33 typedef long NSInteger;
34 typedef unsigned long NSUInteger;
35 #define NSUIntegerMax ULONG_MAX
37 typedef int NSInteger;
38 typedef unsigned int NSUInteger;
39 #define NSUIntegerMax UINT_MAX
43 NSString* const MediaKeyPower = @"MediaKeyPower";
44 NSString* const MediaKeySoundMute = @"MediaKeySoundMute";
45 NSString* const MediaKeySoundUp = @"MediaKeySoundUp";
46 NSString* const MediaKeySoundDown = @"MediaKeySoundDown";
47 NSString* const MediaKeyPlayPauseNotification = @"MediaKeyPlayPauseNotification";
48 NSString* const MediaKeyFastNotification = @"MediaKeyFastNotification";
49 NSString* const MediaKeyRewindNotification = @"MediaKeyRewindNotification";
50 NSString* const MediaKeyNextNotification = @"MediaKeyNextNotification";
51 NSString* const MediaKeyPreviousNotification = @"MediaKeyPreviousNotification";
53 #ifndef kCGEventTapOptionDefault
54 #define kCGEventTapOptionDefault 0
57 #define NX_KEYSTATE_UP 0x0A
58 #define NX_KEYSTATE_DOWN 0x0B
60 @implementation HotKeyController
62 + (HotKeyController*)sharedController
64 static HotKeyController *sharedHotKeyController = nil;
65 if (sharedHotKeyController == nil)
66 sharedHotKeyController = [[super allocWithZone:NULL] init];
68 return sharedHotKeyController;
71 + (id)allocWithZone:(NSZone *)zone
73 return [[self sharedController] retain];
76 - (id)copyWithZone:(NSZone *)zone
86 - (NSUInteger)retainCount
88 //denotes an object that cannot be released
102 - (CFMachPortRef)eventPort
107 - (void)sysPower: (BOOL)enable;
109 m_controlSysPower = enable;
111 - (BOOL)controlPower;
113 return m_controlSysPower;
116 - (void)sysVolume: (BOOL)enable;
118 m_controlSysVolume = enable;
120 - (BOOL)controlVolume;
122 return m_controlSysVolume;
125 - (void)setActive: (BOOL)active
135 // WARNING: do not debugger breakpoint in this routine.
136 // It's a system level call back that taps ALL Events
137 // and you WILL lose all key control :)
138 static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
140 HotKeyController *hot_key_controller = (HotKeyController*)refcon;
142 if (type == kCGEventTapDisabledByTimeout)
144 CGEventTapEnable([hot_key_controller eventPort], TRUE);
148 if ((type != NX_SYSDEFINED) || (![hot_key_controller getActive]))
151 // eventWithCGEvent does not exist under 10.4 SDK,
152 // but thanks to how objc works, it will resolve at runtime on 10.5+
153 // but we will get a compile warning, just ignore it.
154 NSEvent *nsEvent = [NSEvent eventWithCGEvent:event];
155 if (!nsEvent || [nsEvent subtype] != 8)
158 int data = [nsEvent data1];
159 int keyCode = (data & 0xFFFF0000) >> 16;
160 int keyFlags = (data & 0xFFFF);
161 int keyState = (keyFlags & 0xFF00) >> 8;
162 BOOL keyIsRepeat = (keyFlags & 0x1) > 0;
167 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
171 if ([hot_key_controller controlPower])
173 if (keyState == NX_KEYSTATE_DOWN)
174 [center postNotificationName:MediaKeyPower object:(HotKeyController *)refcon];
175 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
179 case NX_KEYTYPE_MUTE:
180 if ([hot_key_controller controlVolume])
182 if (keyState == NX_KEYSTATE_DOWN)
183 [center postNotificationName:MediaKeySoundMute object:(HotKeyController *)refcon];
184 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
188 case NX_KEYTYPE_SOUND_UP:
189 if ([hot_key_controller controlVolume])
191 if (keyState == NX_KEYSTATE_DOWN)
192 [center postNotificationName:MediaKeySoundUp object:(HotKeyController *)refcon];
193 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
197 case NX_KEYTYPE_SOUND_DOWN:
198 if ([hot_key_controller controlVolume])
200 if (keyState == NX_KEYSTATE_DOWN)
201 [center postNotificationName:MediaKeySoundDown object:(HotKeyController *)refcon];
202 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
206 case NX_KEYTYPE_PLAY:
207 if (keyState == NX_KEYSTATE_DOWN)
208 [center postNotificationName:MediaKeyPlayPauseNotification object:(HotKeyController *)refcon];
209 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
212 case NX_KEYTYPE_FAST:
213 if (keyState == NX_KEYSTATE_DOWN)
214 [center postNotificationName:MediaKeyFastNotification object:(HotKeyController *)refcon];
215 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
218 case NX_KEYTYPE_REWIND:
219 if (keyState == NX_KEYSTATE_DOWN)
220 [center postNotificationName:MediaKeyRewindNotification object:(HotKeyController *)refcon];
221 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
224 case NX_KEYTYPE_NEXT:
225 if (keyState == NX_KEYSTATE_DOWN)
226 [center postNotificationName:MediaKeyNextNotification object:(HotKeyController *)refcon];
227 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
230 case NX_KEYTYPE_PREVIOUS:
231 if (keyState == NX_KEYSTATE_DOWN)
232 [center postNotificationName:MediaKeyPreviousNotification object:(HotKeyController *)refcon];
233 if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
240 static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
242 NSAutoreleasePool *pool = [NSAutoreleasePool new];
243 CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
249 -(void)eventTapThread
251 CFRunLoopSourceRef runLoopSource;
253 runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, m_eventPort, 0);
254 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
255 // Enable the event tap.
256 CGEventTapEnable(m_eventPort, TRUE);
259 CFRelease(runLoopSource);
260 CFRelease(m_eventPort);
266 if (self = [super init])
269 m_controlSysPower = NO;
270 m_controlSysVolume = NO;
272 if (floor(NSAppKitVersionNumber) < 949)
274 // check runtime, we only allow this on 10.5+
279 m_eventPort = CGEventTapCreate(kCGSessionEventTap,
280 kCGHeadInsertEventTap, kCGEventTapOptionDefault,
281 CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, self);
282 if (m_eventPort != NULL)
284 // Run this in a separate thread so that a slow app
285 // doesn't lag the event tap
286 [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
296 CFRelease(m_eventPort);