Adding HotKeys to ITKit... This is the new HotKey code by Quentin of
[ITKit.git] / ITHotKeyCenter.m
1 //
2 //  ITHotKeyCenter.m
3 //
4 //  Created by Quentin Carnicelli on Sat Aug 02 2003.
5 //  Copyright (c) 2003 iThink Software. All rights reserved.
6 //
7
8 #import "ITHotKeyCenter.h"
9 #import "ITHotKey.h"
10 #import "ITKeyCombo.h"
11 #import <Carbon/Carbon.h>
12
13 #if __PROTEIN__
14 #import "NSObjectAdditions.h"
15 #endif
16
17 @interface ITHotKeyCenter (Private)
18 - (BOOL)_hasCarbonEventSupport;
19
20 - (ITHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey;
21 - (EventHotKeyRef)_carbonHotKeyForHotKey: (ITHotKey*)hotKey;
22
23 - (void)_updateEventHandler;
24 - (void)_hotKeyDown: (ITHotKey*)hotKey;
25 - (void)_hotKeyUp: (ITHotKey*)hotKey;
26 static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon );
27 @end
28
29 @implementation ITHotKeyCenter
30
31 static id _sharedHotKeyCenter = nil;
32
33 + (id)sharedCenter
34 {
35         if( _sharedHotKeyCenter == nil )
36         {
37                 _sharedHotKeyCenter = [[self alloc] init];
38                 #if __PROTEIN__
39                         [_sharedHotKeyCenter releaseOnTerminate];
40                 #endif
41         }
42         
43         return _sharedHotKeyCenter;
44 }
45
46 - (id)init
47 {
48         self = [super init];
49         
50         if( self )
51         {
52                 mHotKeys = [[NSMutableDictionary alloc] init];
53         }
54         
55         return self;
56 }
57
58 - (void)dealloc
59 {
60         [mHotKeys release];
61         [super dealloc];
62 }
63
64 #pragma mark -
65
66 - (BOOL)registerHotKey: (ITHotKey*)hotKey
67 {
68         OSStatus err;
69         EventHotKeyID hotKeyID;
70         EventHotKeyRef carbonHotKey;
71         NSValue* key;
72
73         if( [[self allHotKeys] containsObject: hotKey] == YES )
74                 [self unregisterHotKey: hotKey];
75         
76         if( [[hotKey keyCombo] isValidHotKeyCombo] == NO )
77                 return YES;
78         
79         hotKeyID.signature = 'PTHk';
80         hotKeyID.id = (long)hotKey;
81         
82         err = RegisterEventHotKey(  [[hotKey keyCombo] keyCode],
83                                                                 [[hotKey keyCombo] modifiers],
84                                                                 hotKeyID,
85                                                                 GetEventDispatcherTarget(),
86                                                                 nil,
87                                                                 &carbonHotKey );
88
89         if( err )
90                 return NO;
91
92         key = [NSValue valueWithPointer: carbonHotKey];
93         [mHotKeys setObject: hotKey forKey: key];
94
95         [self _updateEventHandler];
96         
97         return YES;
98 }
99
100 - (void)unregisterHotKey: (ITHotKey*)hotKey
101 {
102         OSStatus err;
103         EventHotKeyRef carbonHotKey;
104         NSValue* key;
105
106         if( [[self allHotKeys] containsObject: hotKey] == NO )
107                 return;
108         
109         carbonHotKey = [self _carbonHotKeyForHotKey: hotKey];
110         NSAssert( carbonHotKey != nil, @"" );
111
112         err = UnregisterEventHotKey( carbonHotKey );
113         //Watch as we ignore 'err':
114
115         key = [NSValue valueWithPointer: carbonHotKey];
116         [mHotKeys removeObjectForKey: key];
117         
118         [self _updateEventHandler];
119
120         //See that? Completely ignored
121 }
122
123 - (NSArray*)allHotKeys
124 {
125         return [mHotKeys allValues];
126 }
127
128 #pragma mark -
129
130 - (BOOL)_hasCarbonEventSupport
131 {
132         return floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_1;
133 }
134
135 - (ITHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey
136 {
137         NSValue* key = [NSValue valueWithPointer: carbonHotKey];
138         return [mHotKeys objectForKey: key];
139 }
140
141 - (EventHotKeyRef)_carbonHotKeyForHotKey: (ITHotKey*)hotKey
142 {
143         NSArray* values;
144         NSValue* value;
145         
146         values = [mHotKeys allKeysForObject: hotKey];
147         NSAssert( [values count] == 1, @"Failed to find Carbon Hotkey for ITHotKey" );
148         
149         value = [values lastObject];
150         
151         return (EventHotKeyRef)[value pointerValue];
152 }
153
154 - (void)_updateEventHandler
155 {
156         if( [self _hasCarbonEventSupport] == NO ) //Don't use event handler on these systems
157                 return;
158
159         if( [mHotKeys count] && mEventHandlerInstalled == NO )
160         {
161                 EventTypeSpec eventSpec[2] = {
162                         { kEventClassKeyboard, kEventHotKeyPressed },
163                         { kEventClassKeyboard, kEventHotKeyReleased }
164                 };    
165
166                 InstallEventHandler( GetEventDispatcherTarget(),
167                                                          (EventHandlerProcPtr)hotKeyEventHandler, 
168                                                          2, eventSpec, nil, nil);
169         
170                 mEventHandlerInstalled = YES;
171         }
172 }
173
174 - (void)_hotKeyDown: (ITHotKey*)hotKey
175 {
176         [hotKey invoke];
177 }
178
179 - (void)_hotKeyUp: (ITHotKey*)hotKey
180 {
181 }
182
183 - (void)sendEvent: (NSEvent*)event
184 {
185         long subType;
186         EventHotKeyRef carbonHotKey;
187         
188         //We only have to intercept sendEvent to do hot keys on old system versions
189         if( [self _hasCarbonEventSupport] )
190                 return;
191         
192         if( [event type] == NSSystemDefined )
193         {
194                 subType = [event subtype];
195                 
196                 if( subType == 6 ) //6 is hot key down
197                 {
198                         carbonHotKey= (EventHotKeyRef)[event data1]; //data1 is our hot key ref
199                         if( carbonHotKey != nil )
200                         {
201                                 ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
202                                 [self _hotKeyDown: hotKey];
203                         }
204                 }
205                 else if( subType == 9 ) //9 is hot key up
206                 {
207                         carbonHotKey= (EventHotKeyRef)[event data1];
208                         if( carbonHotKey != nil )
209                         {
210                                 ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
211                                 [self _hotKeyUp: hotKey];
212                         }
213                 }
214         }
215 }
216
217 - (OSStatus)sendCarbonEvent: (EventRef)event
218 {
219         OSStatus err;
220         EventHotKeyID hotKeyID;
221         ITHotKey* hotKey;
222
223         NSAssert( [self _hasCarbonEventSupport], @"" );
224         NSAssert( GetEventClass( event ) == kEventClassKeyboard, @"Unknown event class" );
225
226         err = GetEventParameter(        event,
227                                                                 kEventParamDirectObject, 
228                                                                 typeEventHotKeyID,
229                                                                 nil,
230                                                                 sizeof(EventHotKeyID),
231                                                                 nil,
232                                                                 &hotKeyID );
233         if( err )
234                 return err;
235         
236
237         NSAssert( hotKeyID.signature == 'PTHk', @"Invalid hot key id" );
238         NSAssert( hotKeyID.id != nil, @"Invalid hot key id" );
239
240         hotKey = (ITHotKey*)hotKeyID.id;
241
242         switch( GetEventKind( event ) )
243         {
244                 case kEventHotKeyPressed:
245                         [self _hotKeyDown: hotKey];
246                 break;
247
248                 case kEventHotKeyReleased:
249                         [self _hotKeyUp: hotKey];
250                 break;
251
252                 default:
253                         NSAssert( 0, @"Unknown event kind" );
254                 break;
255         }
256         
257         return noErr;
258 }
259
260 static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon )
261 {
262         return [[ITHotKeyCenter sharedCenter] sendCarbonEvent: inEvent];
263 }
264
265 @end