+//
+// ITHotKeyCenter.m
+//
+// Created by Quentin Carnicelli on Sat Aug 02 2003.
+// Copyright (c) 2003 iThink Software. All rights reserved.
+//
+
+#import "ITHotKeyCenter.h"
+#import "ITHotKey.h"
+#import "ITKeyCombo.h"
+#import <Carbon/Carbon.h>
+
+#if __PROTEIN__
+#import "NSObjectAdditions.h"
+#endif
+
+@interface ITHotKeyCenter (Private)
+- (BOOL)_hasCarbonEventSupport;
+
+- (ITHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey;
+- (EventHotKeyRef)_carbonHotKeyForHotKey: (ITHotKey*)hotKey;
+
+- (void)_updateEventHandler;
+- (void)_hotKeyDown: (ITHotKey*)hotKey;
+- (void)_hotKeyUp: (ITHotKey*)hotKey;
+static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon );
+@end
+
+@implementation ITHotKeyCenter
+
+static id _sharedHotKeyCenter = nil;
+
++ (id)sharedCenter
+{
+ if( _sharedHotKeyCenter == nil )
+ {
+ _sharedHotKeyCenter = [[self alloc] init];
+ #if __PROTEIN__
+ [_sharedHotKeyCenter releaseOnTerminate];
+ #endif
+ }
+
+ return _sharedHotKeyCenter;
+}
+
+- (id)init
+{
+ self = [super init];
+
+ if( self )
+ {
+ mHotKeys = [[NSMutableDictionary alloc] init];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [mHotKeys release];
+ [super dealloc];
+}
+
+#pragma mark -
+
+- (BOOL)registerHotKey: (ITHotKey*)hotKey
+{
+ OSStatus err;
+ EventHotKeyID hotKeyID;
+ EventHotKeyRef carbonHotKey;
+ NSValue* key;
+
+ if( [[self allHotKeys] containsObject: hotKey] == YES )
+ [self unregisterHotKey: hotKey];
+
+ if( [[hotKey keyCombo] isValidHotKeyCombo] == NO )
+ return YES;
+
+ hotKeyID.signature = 'PTHk';
+ hotKeyID.id = (long)hotKey;
+
+ err = RegisterEventHotKey( [[hotKey keyCombo] keyCode],
+ [[hotKey keyCombo] modifiers],
+ hotKeyID,
+ GetEventDispatcherTarget(),
+ nil,
+ &carbonHotKey );
+
+ if( err )
+ return NO;
+
+ key = [NSValue valueWithPointer: carbonHotKey];
+ [mHotKeys setObject: hotKey forKey: key];
+
+ [self _updateEventHandler];
+
+ return YES;
+}
+
+- (void)unregisterHotKey: (ITHotKey*)hotKey
+{
+ OSStatus err;
+ EventHotKeyRef carbonHotKey;
+ NSValue* key;
+
+ if( [[self allHotKeys] containsObject: hotKey] == NO )
+ return;
+
+ carbonHotKey = [self _carbonHotKeyForHotKey: hotKey];
+ NSAssert( carbonHotKey != nil, @"" );
+
+ err = UnregisterEventHotKey( carbonHotKey );
+ //Watch as we ignore 'err':
+
+ key = [NSValue valueWithPointer: carbonHotKey];
+ [mHotKeys removeObjectForKey: key];
+
+ [self _updateEventHandler];
+
+ //See that? Completely ignored
+}
+
+- (NSArray*)allHotKeys
+{
+ return [mHotKeys allValues];
+}
+
+#pragma mark -
+
+- (BOOL)_hasCarbonEventSupport
+{
+ return floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_1;
+}
+
+- (ITHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey
+{
+ NSValue* key = [NSValue valueWithPointer: carbonHotKey];
+ return [mHotKeys objectForKey: key];
+}
+
+- (EventHotKeyRef)_carbonHotKeyForHotKey: (ITHotKey*)hotKey
+{
+ NSArray* values;
+ NSValue* value;
+
+ values = [mHotKeys allKeysForObject: hotKey];
+ NSAssert( [values count] == 1, @"Failed to find Carbon Hotkey for ITHotKey" );
+
+ value = [values lastObject];
+
+ return (EventHotKeyRef)[value pointerValue];
+}
+
+- (void)_updateEventHandler
+{
+ if( [self _hasCarbonEventSupport] == NO ) //Don't use event handler on these systems
+ return;
+
+ if( [mHotKeys count] && mEventHandlerInstalled == NO )
+ {
+ EventTypeSpec eventSpec[2] = {
+ { kEventClassKeyboard, kEventHotKeyPressed },
+ { kEventClassKeyboard, kEventHotKeyReleased }
+ };
+
+ InstallEventHandler( GetEventDispatcherTarget(),
+ (EventHandlerProcPtr)hotKeyEventHandler,
+ 2, eventSpec, nil, nil);
+
+ mEventHandlerInstalled = YES;
+ }
+}
+
+- (void)_hotKeyDown: (ITHotKey*)hotKey
+{
+ [hotKey invoke];
+}
+
+- (void)_hotKeyUp: (ITHotKey*)hotKey
+{
+}
+
+- (void)sendEvent: (NSEvent*)event
+{
+ long subType;
+ EventHotKeyRef carbonHotKey;
+
+ //We only have to intercept sendEvent to do hot keys on old system versions
+ if( [self _hasCarbonEventSupport] )
+ return;
+
+ if( [event type] == NSSystemDefined )
+ {
+ subType = [event subtype];
+
+ if( subType == 6 ) //6 is hot key down
+ {
+ carbonHotKey= (EventHotKeyRef)[event data1]; //data1 is our hot key ref
+ if( carbonHotKey != nil )
+ {
+ ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
+ [self _hotKeyDown: hotKey];
+ }
+ }
+ else if( subType == 9 ) //9 is hot key up
+ {
+ carbonHotKey= (EventHotKeyRef)[event data1];
+ if( carbonHotKey != nil )
+ {
+ ITHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey];
+ [self _hotKeyUp: hotKey];
+ }
+ }
+ }
+}
+
+- (OSStatus)sendCarbonEvent: (EventRef)event
+{
+ OSStatus err;
+ EventHotKeyID hotKeyID;
+ ITHotKey* hotKey;
+
+ NSAssert( [self _hasCarbonEventSupport], @"" );
+ NSAssert( GetEventClass( event ) == kEventClassKeyboard, @"Unknown event class" );
+
+ err = GetEventParameter( event,
+ kEventParamDirectObject,
+ typeEventHotKeyID,
+ nil,
+ sizeof(EventHotKeyID),
+ nil,
+ &hotKeyID );
+ if( err )
+ return err;
+
+
+ NSAssert( hotKeyID.signature == 'PTHk', @"Invalid hot key id" );
+ NSAssert( hotKeyID.id != nil, @"Invalid hot key id" );
+
+ hotKey = (ITHotKey*)hotKeyID.id;
+
+ switch( GetEventKind( event ) )
+ {
+ case kEventHotKeyPressed:
+ [self _hotKeyDown: hotKey];
+ break;
+
+ case kEventHotKeyReleased:
+ [self _hotKeyUp: hotKey];
+ break;
+
+ default:
+ NSAssert( 0, @"Unknown event kind" );
+ break;
+ }
+
+ return noErr;
+}
+
+static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon )
+{
+ return [[ITHotKeyCenter sharedCenter] sendCarbonEvent: inEvent];
+}
+
+@end