Merge pull request #473 from Montellese/onplaybackspeedchanged
[vuplus_xbmc] / xbmc / osx / HotKeyController.m
1 //
2 //  HotKeyController.m
3 //
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
7 //
8 //
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:
16 //
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.
26 //
27
28 #import "HotKeyController.h"
29 #import <IOKit/hidsystem/ev_keymap.h>
30
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
36 #else
37 typedef int NSInteger;
38 typedef unsigned int NSUInteger;
39 #define NSUIntegerMax   UINT_MAX
40 #endif
41 #endif
42
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";
52
53 #ifndef kCGEventTapOptionDefault
54 #define kCGEventTapOptionDefault 0
55 #endif
56
57 #define NX_KEYSTATE_UP      0x0A
58 #define NX_KEYSTATE_DOWN    0x0B
59
60 @implementation HotKeyController
61
62 + (HotKeyController*)sharedController
63 {
64   static HotKeyController *sharedHotKeyController = nil;
65   if (sharedHotKeyController == nil)
66     sharedHotKeyController = [[super allocWithZone:NULL] init];
67
68   return sharedHotKeyController;
69 }
70
71 + (id)allocWithZone:(NSZone *)zone
72 {
73   return [[self sharedController] retain];
74 }
75  
76 - (id)copyWithZone:(NSZone *)zone
77 {
78   return self;
79 }
80  
81 - (id)retain
82 {
83   return self;
84 }
85  
86 - (NSUInteger)retainCount
87 {
88   //denotes an object that cannot be released
89   return NSUIntegerMax;
90 }
91  
92 - (void)release
93 {
94   //do nothing
95 }
96  
97 - (id)autorelease
98 {
99   return self;
100 }
101
102 - (CFMachPortRef)eventPort
103 {
104   return m_eventPort;
105 }
106
107 - (void)sysPower: (BOOL)enable;
108 {
109   m_controlSysPower = enable;
110 }
111 - (BOOL)controlPower;
112 {
113   return m_controlSysPower;
114 }
115
116 - (void)sysVolume: (BOOL)enable;
117 {
118   m_controlSysVolume = enable;
119 }
120 - (BOOL)controlVolume;
121 {
122   return m_controlSysVolume;
123 }
124
125 - (void)setActive: (BOOL)active
126 {
127   m_active = active;
128 }
129
130 - (BOOL)getActive
131 {
132   return m_active;
133 }
134
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)
139 {
140   HotKeyController *hot_key_controller = (HotKeyController*)refcon;
141
142   if (type == kCGEventTapDisabledByTimeout)
143   {
144     CGEventTapEnable([hot_key_controller eventPort], TRUE);
145     return NULL;
146   }
147   
148   if ((type != NX_SYSDEFINED) || (![hot_key_controller getActive]))
149     return event;
150
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) 
156     return event;
157     
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;
163   
164   if (keyIsRepeat) 
165     return event;
166   
167   NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
168   switch (keyCode)
169   {
170     case NX_POWER_KEY:
171       if ([hot_key_controller controlPower])
172       {
173         if (keyState == NX_KEYSTATE_DOWN)
174           [center postNotificationName:MediaKeyPower object:(HotKeyController *)refcon];
175         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
176           return NULL;
177       }
178     break;
179     case NX_KEYTYPE_MUTE:
180       if ([hot_key_controller controlVolume])
181       {
182         if (keyState == NX_KEYSTATE_DOWN)
183           [center postNotificationName:MediaKeySoundMute object:(HotKeyController *)refcon];
184         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
185           return NULL;
186       }
187     break;
188     case NX_KEYTYPE_SOUND_UP:
189       if ([hot_key_controller controlVolume])
190       {
191         if (keyState == NX_KEYSTATE_DOWN)
192           [center postNotificationName:MediaKeySoundUp object:(HotKeyController *)refcon];
193         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
194           return NULL;
195       }
196     break;
197     case NX_KEYTYPE_SOUND_DOWN:
198       if ([hot_key_controller controlVolume])
199       {
200         if (keyState == NX_KEYSTATE_DOWN)
201           [center postNotificationName:MediaKeySoundDown object:(HotKeyController *)refcon];
202         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
203           return NULL;
204       }
205     break;
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)
210         return NULL;
211     break;
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)
216         return NULL;
217     break;
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)
222         return NULL;
223     break;
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)
228         return NULL;
229     break;
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)
234         return NULL;
235     break;
236   }
237   return event;
238 }
239
240 static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
241 {
242   NSAutoreleasePool *pool = [NSAutoreleasePool new];
243   CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
244   [pool drain];
245   return ret;
246 }
247
248
249 -(void)eventTapThread
250 {
251   CFRunLoopSourceRef runLoopSource;
252
253   runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, m_eventPort, 0);
254   CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
255   // Enable the event tap.
256   CGEventTapEnable(m_eventPort, TRUE);
257
258   CFRunLoopRun();
259   CFRelease(runLoopSource);
260   CFRelease(m_eventPort);
261   m_eventPort = NULL;
262 }
263
264 - (id)init
265 {
266   if (self = [super init])
267   {
268     m_active = NO;
269     m_controlSysPower = NO;
270     m_controlSysVolume = NO;
271     
272     if (floor(NSAppKitVersionNumber) < 949)
273     {
274       // check runtime, we only allow this on 10.5+
275       m_eventPort = NULL;
276     }
277     else
278     {
279       m_eventPort = CGEventTapCreate(kCGSessionEventTap,
280         kCGHeadInsertEventTap, kCGEventTapOptionDefault,
281         CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, self);
282       if (m_eventPort != NULL)
283       {
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];
287       }
288     }
289   }
290   return self;
291 }
292
293 - (void)dealloc
294 {
295   if (m_eventPort)
296     CFRelease(m_eventPort);
297   [super dealloc];
298 }
299 @end