2e43e56587942c1ac8bcc8f73a83ab2b047ac4be
[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                 _enabled = YES;
54         }
55         
56         return self;
57 }
58
59 - (void)dealloc
60 {
61         [mHotKeys release];
62         [super dealloc];
63 }
64
65 #pragma mark -
66
67 - (BOOL)isEnabled
68 {
69         return _enabled;
70 }
71
72 - (void)setEnabled:(BOOL)flag
73 {
74         _enabled = flag;
75 }
76
77 - (BOOL)registerHotKey: (ITHotKey*)hotKey
78 {
79         OSStatus err;
80         EventHotKeyID hotKeyID;
81         EventHotKeyRef carbonHotKey;
82         NSValue* key;
83
84         if( [[self allHotKeys] containsObject: hotKey] == YES )
85                 [self unregisterHotKey: hotKey];
86         
87         if( [[hotKey keyCombo] isValidHotKeyCombo] == NO )
88                 return YES;
89         
90         hotKeyID.signature = 'PTHk';
91         hotKeyID.id = (long)hotKey;
92         
93         err = RegisterEventHotKey(  [[hotKey keyCombo] keyCode],
94                                                                 [[hotKey keyCombo] modifiers],
95                                                                 hotKeyID,
96                                                                 GetEventDispatcherTarget(),
97                                                                 nil,
98                                                                 &carbonHotKey );
99
100         if( err )
101                 return NO;
102
103         key = [NSValue valueWithPointer: carbonHotKey];
104         [mHotKeys setObject: hotKey forKey: key];
105
106         [self _updateEventHandler];
107         
108         return YES;
109 }
110
111 - (void)unregisterHotKey: (ITHotKey*)hotKey
112 {
113         OSStatus err;
114         EventHotKeyRef carbonHotKey;
115         NSValue* key;
116
117         if( [[self allHotKeys] containsObject: hotKey] == NO )
118                 return;
119         
120         carbonHotKey = [self _carbonHotKeyForHotKey: hotKey];
121         NSAssert( carbonHotKey != nil, @"" );
122
123         err = UnregisterEventHotKey( carbonHotKey );
124         //Watch as we ignore 'err':
125
126         key = [NSValue valueWithPointer: carbonHotKey];
127         [mHotKeys removeObjectForKey: key];
128         
129         [self _updateEventHandler];
130
131         //See that? Completely ignored
132 }
133
134 - (NSArray*)allHotKeys
135 {
136         return [mHotKeys allValues];
137 }
138
139 #pragma mark -
140
141 - (BOOL)_hasCarbonEventSupport
142 {
143         return floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_1;
144 }
145
146 - (ITHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey
147 {
148         NSValue* key = [NSValue valueWithPointer: carbonHotKey];
149         return [mHotKeys objectForKey: key];
150 }
151
152 - (EventHotKeyRef)_carbonHotKeyForHotKey: (ITHotKey*)hotKey
153 {
154         NSArray* values;
155         NSValue* value;
156         
157         values = [mHotKeys allKeysForObject: hotKey];
158         NSAssert( [values count] == 1, @"Failed to find Carbon Hotkey for ITHotKey" );
159         
160         value = [values lastObject];
161         
162         return (EventHotKeyRef)[value pointerValue];
163 }
164
165 - (void)_updateEventHandler
166 {
167         if( [self _hasCarbonEventSupport] == NO ) //Don't use event handler on these systems
168                 return;
169
170         if( [mHotKeys count] && mEventHandlerInstalled == NO )
171         {
172                 EventTypeSpec eventSpec[2] = {
173                         { kEventClassKeyboard, kEventHotKeyPressed },
174                         { kEventClassKeyboard, kEventHotKeyReleased }
175                 };    
176
177                 InstallEventHandler( GetEventDispatcherTarget(),
178                                                          (EventHandlerProcPtr)hotKeyEventHandler, 
179                                                          2, eventSpec, nil, nil);
180         
181                 mEventHandlerInstalled = YES;
182         }
183 }
184
185 - (void)_hotKeyDown: (ITHotKey*)hotKey
186 {
187         [hotKey invoke];
188 }
189
190 - (void)_hotKeyUp: (ITHotKey*)hotKey
191 {
192 }
193
194 - (void)sendEvent: (NSEvent*)event
195 {
196         long subType;
197         EventHotKeyRef carbonHotKey;
198         
199         if (!_enabled) {
200                 return;
201         }
202         
203         //We only have to intercept sendEvent to do hot keys on old system versions
204         if( [self _hasCarbonEventSupport] )
205                 return;
206         
207         if( [event type] == NSSystemDefined )
208         {
209                 subType = [event subtype];
210                 
211                 if( subType == 6 ) //6 is hot key down
212                 {
213                         carbonHotKey= (EventHotKeyRef)[event data1]; //data1 is our hot key ref
214                         if( carbonHotKey != nil )
215                         {
216                                 ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
217                                 [self _hotKeyDown: hotKey];
218                         }
219                 }
220                 else if( subType == 9 ) //9 is hot key up
221                 {
222                         carbonHotKey= (EventHotKeyRef)[event data1];
223                         if( carbonHotKey != nil )
224                         {
225                                 ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
226                                 [self _hotKeyUp: hotKey];
227                         }
228                 }
229         }
230 }
231
232 - (OSStatus)sendCarbonEvent: (EventRef)event
233 {
234         OSStatus err;
235         EventHotKeyID hotKeyID;
236         ITHotKey* hotKey;
237         
238         if (!_enabled) {
239                 return -1;
240         }
241         
242         NSAssert( [self _hasCarbonEventSupport], @"" );
243         NSAssert( GetEventClass( event ) == kEventClassKeyboard, @"Unknown event class" );
244
245         err = GetEventParameter(        event,
246                                                                 kEventParamDirectObject, 
247                                                                 typeEventHotKeyID,
248                                                                 nil,
249                                                                 sizeof(EventHotKeyID),
250                                                                 nil,
251                                                                 &hotKeyID );
252         if( err )
253                 return err;
254         
255
256         NSAssert( hotKeyID.signature == 'PTHk', @"Invalid hot key id" );
257         NSAssert( hotKeyID.id != nil, @"Invalid hot key id" );
258
259         hotKey = (ITHotKey*)hotKeyID.id;
260
261         switch( GetEventKind( event ) )
262         {
263                 case kEventHotKeyPressed:
264                         [self _hotKeyDown: hotKey];
265                 break;
266
267                 case kEventHotKeyReleased:
268                         [self _hotKeyUp: hotKey];
269                 break;
270
271                 default:
272                         NSAssert( 0, @"Unknown event kind" );
273                 break;
274         }
275         
276         return noErr;
277 }
278
279 static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon )
280 {
281         return [[ITHotKeyCenter sharedCenter] sendCarbonEvent: inEvent];
282 }
283
284 @end