Added notes to the TODO. Matt, please check and modify TODO as required :)
[MenuTunes.git] / HotKeyCenter.m
1 //
2 //  HotKeyCenter.m
3 //
4 //  Created by Quentin D. Carnicelli on Thu Jun 06 2002.
5 //  Copyright (c) 2002 Subband inc.. All rights reserved.
6 //
7 //  Feedback welcome at qdc@subband.com
8 //  This code is provided AS IS, so don't hurt yourself with it...
9 //
10
11 #import "HotKeyCenter.h"
12 #import "KeyCombo.h"
13
14 #import <Carbon/Carbon.h>
15
16 #define kHotKeyCenterSignature 'HKyC'
17
18 //*** _HotKeyData
19 @interface _HotKeyData : NSObject
20 {
21     @public
22         BOOL mRegistered;
23         EventHotKeyRef mRef;
24         KeyCombo *mCombo;
25         id mTarget;
26         SEL mAction;
27 }
28 @end
29
30 @implementation _HotKeyData
31 @end
32
33 //**** HotKeyCenter
34 @interface HotKeyCenter (Private)
35     - (OSStatus)handleHotKeyEvent: (EventRef)inEvent;
36
37     - (BOOL)_registerHotKeyIfNeeded:(_HotKeyData *)hk;
38     - (void)_unregisterHotKeyIfNeeded:(_HotKeyData *)hk;
39
40     + (BOOL)_systemSupportsHotKeys;
41     - (void)_hotKeyUp:(_HotKeyData *)hotKey;
42     - (void)_hotKeyDown:(_HotKeyData *)hotKey;
43     - (void)_hotKeyDownWithRef:(EventHotKeyRef)ref;
44     - (void)_hotKeyUpWithRef:(EventHotKeyRef)ref;
45     - (_HotKeyData*)_findHotKeyWithRef:(EventHotKeyRef)ref;
46
47     pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon);
48 @end
49
50 @implementation HotKeyCenter
51
52 static id _sharedHKCenter = nil;
53
54 + (id)sharedCenter
55 {
56     if (_sharedHKCenter != nil)
57     {
58         return _sharedHKCenter;
59     }
60     
61     _sharedHKCenter = [[HotKeyCenter alloc] init];
62     
63     if ([self _systemSupportsHotKeys])
64     {
65         EventTypeSpec eventSpec[2] =
66         {
67             { kEventClassKeyboard, kEventHotKeyPressed },
68             { kEventClassKeyboard, kEventHotKeyReleased }
69         };    
70     
71         InstallEventHandler( GetEventDispatcherTarget(), NewEventHandlerUPP((EventHandlerProcPtr) keyEventHandler), 2, eventSpec, nil, nil);
72     }
73     
74     return _sharedHKCenter;
75 }
76
77 - (id)init
78 {
79     if ( (self = [super init]) )
80     {
81             mEnabled = YES;
82             mHotKeys = [[NSMutableDictionary alloc] init];
83     }
84     return self;
85 }
86
87 - (void)dealloc
88 {
89     [mHotKeys release];
90     [super dealloc];
91 }
92
93 - (BOOL)addHotKey:(NSString *)name combo:(KeyCombo *)combo target:(id)target action:(SEL)action
94 {
95     _HotKeyData *oldHotKey;
96     _HotKeyData *newHotKey;
97     
98     NSParameterAssert(name != nil);
99     NSParameterAssert(combo != nil);
100     NSParameterAssert(target != nil);
101     NSParameterAssert(action != nil);
102     
103     //** Check if we have one of these yet
104     oldHotKey = [mHotKeys objectForKey:name];
105     
106     if (oldHotKey) //Registered already?
107     {
108         [self removeHotKey:name];
109     }
110     
111     //** Save the hot key to our own list
112     newHotKey = [[[_HotKeyData alloc] init] autorelease];
113     newHotKey->mRegistered = NO;
114     newHotKey->mRef = nil;
115     newHotKey->mCombo = [combo retain];
116     newHotKey->mTarget = target; //Retain this?
117     newHotKey->mAction = action;
118     
119     [mHotKeys setObject:newHotKey forKey:name];
120     
121     return [self _registerHotKeyIfNeeded:newHotKey];
122 }
123
124 - (void)removeHotKey:(NSString *)name;
125 {
126     _HotKeyData *hotKey;
127     
128     hotKey = [mHotKeys objectForKey:name];
129     if (hotKey == nil) //Not registered
130         return;
131     
132     [self _unregisterHotKeyIfNeeded:hotKey];
133     [hotKey->mCombo release];
134     
135     //Drop it from our hot key list
136     [mHotKeys removeObjectForKey: name];
137 }
138
139 - (NSArray *)allNames
140 {
141     return [mHotKeys allKeys];
142 }
143
144 - (KeyCombo *)keyComboForName:(NSString *)name
145 {
146     _HotKeyData * hotKey;
147     
148     hotKey = [mHotKeys objectForKey:name];
149     if( hotKey == nil ) //Not registered
150         return nil;
151     
152     return hotKey->mCombo;
153 }
154
155 - (void)setEnabled:(BOOL)enabled
156 {
157     NSEnumerator *enumerator;
158     _HotKeyData *hotKey;
159     
160     enumerator = [mHotKeys objectEnumerator];
161
162     while ((hotKey = [enumerator nextObject]) != nil)
163     {
164         if (enabled)
165             [self _registerHotKeyIfNeeded:hotKey];
166         else
167             [self _unregisterHotKeyIfNeeded:hotKey];
168     }
169     
170     mEnabled = enabled;
171 }
172
173 - (BOOL)enabled
174 {
175     return mEnabled;
176 }
177
178 - (void)sendEvent:(NSEvent *)event;
179 {
180     long subType;
181     EventHotKeyRef hotKeyRef;
182     
183     //We only have to intercept sendEvent to do hot keys on old system versions
184     if ([HotKeyCenter _systemSupportsHotKeys] == YES)
185             return;
186     
187     if ([event type] == NSSystemDefined)
188     {
189         subType = [event subtype];
190         
191         if (subType == 6) //6 is hot key down
192         {
193             hotKeyRef = (EventHotKeyRef)[event data1]; //data1 is our hot key ref
194             if (hotKeyRef != nil)
195             {
196                 [self _hotKeyDownWithRef:hotKeyRef];
197             }
198         }
199         else if (subType == 9) //9 is hot key up
200         {
201             hotKeyRef = (EventHotKeyRef)[event data1];
202             if (hotKeyRef != nil)
203             {
204                 [self _hotKeyUpWithRef:hotKeyRef];
205             }
206         }
207     }
208 }
209
210 - (OSStatus)handleHotKeyEvent:(EventRef)inEvent
211 {
212     OSStatus err;
213     EventHotKeyID hotKeyID;
214     _HotKeyData *hk;
215     
216     //Shouldnt get here on non-hotkey supporting system versions
217     NSAssert([HotKeyCenter _systemSupportsHotKeys] == YES, @"");
218     NSAssert(GetEventClass(inEvent) == kEventClassKeyboard, @"Got unhandled event class");
219     
220     err = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, nil, sizeof(EventHotKeyID), nil, &hotKeyID);
221     
222     if (err)
223     {
224         return err;
225     }
226             
227     NSAssert(hotKeyID.signature == kHotKeyCenterSignature, @"Got unknown hot key");
228     
229     hk = (_HotKeyData *)hotKeyID.id;
230     NSAssert(hk != nil, @"Got bad hot key");
231     
232     switch (GetEventKind(inEvent))
233     {
234         case kEventHotKeyPressed:
235             [self _hotKeyDown:hk];
236         break;
237         
238         case kEventHotKeyReleased:
239             [self _hotKeyUp:hk];
240         break;
241         
242         default:
243         break;
244     }
245     
246     return noErr;
247 }
248
249 + (BOOL)_systemSupportsHotKeys
250 {
251     SInt32 vers; 
252     Gestalt(gestaltSystemVersion,&vers); 
253     return (vers >= 0x00001020);
254 }
255
256 - (BOOL)_registerHotKeyIfNeeded:(_HotKeyData *)hk
257 {
258     KeyCombo *combo;
259     
260     NSParameterAssert(hk != nil);
261     
262     combo = hk->mCombo;
263
264     if( mEnabled == YES && hk->mRegistered == NO && [combo isValid] == YES )
265     {
266         EventHotKeyID keyID;
267         OSStatus err;
268         
269         keyID.signature = kHotKeyCenterSignature;
270         keyID.id = (unsigned long)hk;
271         err = RegisterEventHotKey([combo keyCode], [combo modifiers], 
272                 keyID, GetEventDispatcherTarget(), 0, &hk->mRef);
273         if (err)
274         {
275             return NO;
276         }
277         
278         hk->mRegistered = YES;
279     }
280     
281     return YES;
282 }
283
284 - (void)_unregisterHotKeyIfNeeded:(_HotKeyData *)hk
285 {
286     NSParameterAssert(hk != nil);
287     
288     if (hk->mRegistered && hk->mRef != nil)
289     {
290         UnregisterEventHotKey(hk->mRef);
291     }
292 }
293
294 - (void)_hotKeyDown:(_HotKeyData *)hotKey
295 {
296     id target = hotKey->mTarget;
297     SEL action = hotKey->mAction;
298     [target performSelector:action withObject:self];
299 }
300
301 - (void)_hotKeyUp: (_HotKeyData *)hotKey
302 {
303 }
304
305 - (void)_hotKeyDownWithRef:(EventHotKeyRef)ref
306 {
307     _HotKeyData *hotKey;
308     
309     hotKey = [self _findHotKeyWithRef:ref];
310     if (hotKey)
311     {
312         [self _hotKeyDown:hotKey];
313     }
314 }
315
316 - (void)_hotKeyUpWithRef:(EventHotKeyRef)ref
317 {
318 }
319
320 - (_HotKeyData *)_findHotKeyWithRef:(EventHotKeyRef)ref
321 {
322     NSEnumerator *enumerator;
323     _HotKeyData *hotKey;
324     
325     enumerator = [mHotKeys objectEnumerator];
326
327     while ((hotKey = [enumerator nextObject]) != nil)
328     {
329         if (hotKey->mRef == ref)
330         {
331             return hotKey;
332         }
333     }
334     
335     return nil;
336 }
337
338 pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon)
339 {
340     return [[HotKeyCenter sharedCenter] handleHotKeyEvent:inEvent];
341 }
342
343 @end
344