[bluray] Fix stream info/language retrieval for blurays in non-nav mode.
[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 #import <sys/sysctl.h>
31
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";
41
42 #ifndef kCGEventTapOptionDefault
43 #define kCGEventTapOptionDefault 0
44 #endif
45
46 #define NX_KEYSTATE_DOWN    0x0A
47 #define NX_KEYSTATE_UP      0x0B
48
49 @implementation HotKeyController
50
51 + (HotKeyController*)sharedController
52 {
53   static HotKeyController *sharedHotKeyController = nil;
54   if (sharedHotKeyController == nil)
55     sharedHotKeyController = [[super allocWithZone:NULL] init];
56
57   return sharedHotKeyController;
58 }
59
60 + (id)allocWithZone:(NSZone *)zone
61 {
62   return [[self sharedController] retain];
63 }
64  
65 - (id)copyWithZone:(NSZone *)zone
66 {
67   return self;
68 }
69  
70 - (id)retain
71 {
72   return self;
73 }
74  
75 - (NSUInteger)retainCount
76 {
77   //denotes an object that cannot be released
78   return NSUIntegerMax;
79 }
80  
81 - (oneway void)release
82 {
83   //do nothing
84 }
85  
86 - (id)autorelease
87 {
88   return self;
89 }
90
91 - (CFMachPortRef)eventPort
92 {
93   return m_eventPort;
94 }
95
96 - (void)sysPower: (BOOL)enable;
97 {
98   m_controlSysPower = enable;
99 }
100 - (BOOL)controlPower;
101 {
102   return m_controlSysPower;
103 }
104
105 - (void)sysVolume: (BOOL)enable;
106 {
107   m_controlSysVolume = enable;
108 }
109 - (BOOL)controlVolume;
110 {
111   return m_controlSysVolume;
112 }
113
114 - (void)setActive: (BOOL)active
115 {
116   m_active = active;
117 }
118
119 - (BOOL)getActive
120 {
121   return m_active;
122 }
123
124 - (BOOL)getDebuggerActive
125 {
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).
129   int                 junk;
130   int                 mib[4];
131   struct kinfo_proc   info;
132   size_t              size;
133
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;
137
138   // initialize mib, which tells sysctl the info we want, in this case
139   // we are looking for information about a specific process ID.
140   mib[0] = CTL_KERN;
141   mib[1] = KERN_PROC;
142   mib[2] = KERN_PROC_PID;
143   mib[3] = getpid();
144
145   // Call sysctl.
146   size = sizeof(info);
147   junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
148   assert(junk == 0);
149
150   // we are being debugged if the P_TRACED flag is set.
151   return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
152 }
153
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)
158 {
159   HotKeyController *hot_key_controller = (HotKeyController*)refcon;
160
161   if (type == kCGEventTapDisabledByTimeout)
162   {
163     if ([hot_key_controller getActive])
164       CGEventTapEnable([hot_key_controller eventPort], TRUE);
165     return NULL;
166   }
167   
168   if ((type != NX_SYSDEFINED) || (![hot_key_controller getActive]))
169     return event;
170
171   NSEvent *nsEvent = [NSEvent eventWithCGEvent:event];
172   if (!nsEvent || [nsEvent subtype] != 8) 
173     return event;
174     
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;
180   
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) 
184     return event;
185   
186   NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
187   switch (keyCode)
188   {
189     case NX_POWER_KEY:
190       if ([hot_key_controller controlPower])
191       {
192         if (keyState == NX_KEYSTATE_DOWN)
193           [center postNotificationName:MediaKeyPower object:(HotKeyController *)refcon];
194         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
195           return NULL;
196       }
197     break;
198     case NX_KEYTYPE_MUTE:
199       if ([hot_key_controller controlVolume])
200       {
201         if (keyState == NX_KEYSTATE_DOWN)
202           [center postNotificationName:MediaKeySoundMute object:(HotKeyController *)refcon];
203         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
204           return NULL;
205       }
206     break;
207     case NX_KEYTYPE_SOUND_UP:
208       if ([hot_key_controller controlVolume])
209       {
210         if (keyState == NX_KEYSTATE_DOWN)
211           [center postNotificationName:MediaKeySoundUp object:(HotKeyController *)refcon];
212         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
213           return NULL;
214       }
215     break;
216     case NX_KEYTYPE_SOUND_DOWN:
217       if ([hot_key_controller controlVolume])
218       {
219         if (keyState == NX_KEYSTATE_DOWN)
220           [center postNotificationName:MediaKeySoundDown object:(HotKeyController *)refcon];
221         if (keyState == NX_KEYSTATE_UP || keyState == NX_KEYSTATE_DOWN)
222           return NULL;
223       }
224     break;
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)
229         return NULL;
230     break;
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)
235         return NULL;
236     break;
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)
241         return NULL;
242     break;
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)
247         return NULL;
248     break;
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)
253         return NULL;
254     break;
255   }
256   return event;
257 }
258
259 static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
260 {
261   NSAutoreleasePool *pool = [NSAutoreleasePool new];
262   CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
263   [pool drain];
264   return ret;
265 }
266
267
268 -(void)eventTapThread
269 {
270   m_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, m_eventPort, 0);
271   CFRunLoopAddSource(CFRunLoopGetCurrent(), m_runLoopSource, kCFRunLoopCommonModes);
272   // Enable the event tap.
273   CGEventTapEnable(m_eventPort, TRUE);
274
275   CFRunLoopRun();
276
277   [self setActive:NO];
278   // Disable the event tap.
279   if (m_eventPort)
280     CGEventTapEnable(m_eventPort, FALSE);
281
282   if (m_runLoopSource)
283     CFRelease(m_runLoopSource);
284   m_runLoopSource = NULL;
285   if (m_eventPort)
286     CFRelease(m_eventPort);
287   m_eventPort = NULL;
288 }
289
290 - (id)init
291 {
292   if (self = [super init])
293   {
294     m_active = NO;
295     m_eventPort = NULL;
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
300   }
301   return self;
302 }
303
304 - (void)enableTap
305 {
306   if (![self getDebuggerActive] && ![self getActive] && floor(NSAppKitVersionNumber) >= 949)
307   {
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)
313     {
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];
318     }
319   }
320 }
321
322 - (void)disableTap
323 {
324   if ([self getActive])
325   {
326     [self setActive:NO];
327
328     // Disable the event tap.
329     if (m_eventPort)
330       CGEventTapEnable(m_eventPort, FALSE);
331
332     if (m_runLoopSource)
333       CFRelease(m_runLoopSource);
334     m_runLoopSource = NULL;
335     if (m_eventPort)
336       CFRelease(m_eventPort);
337     m_eventPort = NULL;
338   }
339 }
340 @end