1156 lines
32 KiB
Objective-C
Executable File
1156 lines
32 KiB
Objective-C
Executable File
/*
|
|
* NDHotKeyEvent.m
|
|
* NDHotKeyEvent
|
|
*
|
|
* Created by Nathan Day on Wed Feb 26 2003.
|
|
* Copyright (c) 2002 Nathan Day. All rights reserved.
|
|
*/
|
|
|
|
#import "NDHotKeyEvent.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "Logging.h"
|
|
|
|
@interface NDHotKeyEvent (Private)
|
|
+ (NSMapTable *)allHotKeyEvents;
|
|
- (BOOL)addHotKey;
|
|
- (void)removeHotKey;
|
|
- (BOOL)setCollectiveEnabled:(BOOL)aFlag;
|
|
- (BOOL)collectiveEnable;
|
|
@end
|
|
|
|
static NSString * kArchivingKeyCodeKey = @"KeyCodeKey",
|
|
* kArchivingCharacterKey = @"CharacterKey",
|
|
* kArchivingModifierFlagsKey = @"ModifierFlagsKey",
|
|
* kArchivingSelectorReleasedCodeKey = @"SelectorReleasedCodeKey",
|
|
* kArchivingSelectorPressedCodeKey = @"SelectorPressedCodeKey";
|
|
const OSType NDHotKeyDefaultSignature = 'NDHK';
|
|
|
|
static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag );
|
|
|
|
unichar unicodeForFunctionKey( UInt32 aKeyCode );
|
|
|
|
/*
|
|
* class implementation NDHotKeyEvent
|
|
*/
|
|
@implementation NDHotKeyEvent
|
|
|
|
#ifdef NDHotKeyEventThreadSafe
|
|
static NSLock * hotKeysLock = NULL;
|
|
#warning Thread saftey has been enabled for NDHotKeyEvent class methods
|
|
#define NDHotKeyEventLock [hotKeysLock lock]
|
|
#define NDHotKeyEventUnlock [hotKeysLock unlock]
|
|
#else
|
|
#warning The NDHotKeyEvent class methods are NOT thread safe
|
|
#define NDHotKeyEventLock // lock
|
|
#define NDHotKeyEventUnlock // unlock
|
|
#endif
|
|
|
|
static NSMapTable * allHotKeyEvents = NULL;
|
|
static BOOL isInstalled = NO;
|
|
static OSType signature = 0;
|
|
|
|
unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags );
|
|
|
|
pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * self );
|
|
|
|
NSUInteger hashValueHashFunction( NSMapTable * aTable, const void * aHotKeyEntry );
|
|
BOOL isEqualHashFunction( NSMapTable * aTable, const void * aFirstHotKeyEvent, const void * aSecondHotKeyEvent );
|
|
NSString * describeHashFunction( NSMapTable * aTable, const void * aHotKeyEvent );
|
|
|
|
NSUInteger hashKeyHashFunction( NSMapTable * aTable, const void * aNumber );
|
|
BOOL isEqualKeyHashFunction( NSMapTable * aTable, const void * aFirstNumber, const void * aSecondNumber );
|
|
void hashKeyRetainFunction( NSMapTable * aTable, const void * aNumber );
|
|
void hashKeyReleaseFunction( NSMapTable * aTable, void * aNumber );
|
|
|
|
struct HotKeyMappingEntry
|
|
{
|
|
unsigned short keyCode;
|
|
unsigned int modifierFlags;
|
|
NDHotKeyEvent * hotKeyEvent;
|
|
};
|
|
|
|
/*
|
|
* +install
|
|
*/
|
|
+ (BOOL)install
|
|
{
|
|
if( isInstalled == NO )
|
|
{
|
|
NSMapTable * theHotKeyEvents = [self allHotKeyEvents];
|
|
EventTypeSpec theTypeSpec[] =
|
|
{
|
|
{ kEventClassKeyboard, kEventHotKeyPressed },
|
|
{ kEventClassKeyboard, kEventHotKeyReleased }
|
|
};
|
|
|
|
NDHotKeyEventLock;
|
|
if( theHotKeyEvents != nil && isInstalled == NO )
|
|
{
|
|
if( InstallEventHandler( GetEventDispatcherTarget(), NewEventHandlerUPP((EventHandlerProcPtr)eventHandlerCallback), 2, theTypeSpec, theHotKeyEvents, nil ) == noErr )
|
|
{
|
|
isInstalled = YES;
|
|
}
|
|
else
|
|
{
|
|
DLog(@"Could not install Event handler");
|
|
}
|
|
}
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
|
|
return isInstalled;
|
|
}
|
|
|
|
#ifdef NDHotKeyEventThreadSafe
|
|
/*
|
|
* +initialize:
|
|
*/
|
|
+ (void)initialize
|
|
{
|
|
while( hotKeysLock == nil )
|
|
{
|
|
NSLock * theInstance = [[NSLock alloc] init];
|
|
|
|
if( !CompareAndSwap( nil, (unsigned long int)theInstance, (unsigned long int*)&hotKeysLock) )
|
|
[theInstance release]; // did not use instance
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* +setSignature:
|
|
*/
|
|
+ (void)setSignature:(OSType)aSignature
|
|
{
|
|
NSAssert( signature == 0 || aSignature == signature, @"The signature used by NDHotKeyEvent can only be set once safely" );
|
|
signature = aSignature;
|
|
}
|
|
|
|
/*
|
|
* +signature
|
|
*/
|
|
+ (OSType)signature
|
|
{
|
|
signature = signature ? signature : NDHotKeyDefaultSignature;
|
|
return signature;
|
|
}
|
|
|
|
/*
|
|
* +setAllEnabled:
|
|
*/
|
|
+ (BOOL)setAllEnabled:(BOOL)aFlag
|
|
{
|
|
BOOL theAllSucceeded = YES;
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
|
|
/*
|
|
* need to install before to make sure the method 'setCollectiveEnabled:'
|
|
* doesn't try install since install tries to aquire the lock 'hotKeysLock'
|
|
*/
|
|
if( theMapTable && [NDHotKeyEvent install] )
|
|
{
|
|
NSMapEnumerator theEnumerator;
|
|
NSUInteger theKey;
|
|
struct HotKeyMappingEntry * theHotKeyMapEntry;
|
|
NDHotKeyEventLock;
|
|
theEnumerator = NSEnumerateMapTable(theMapTable);
|
|
|
|
while ( NSNextMapEnumeratorPair(&theEnumerator, (void**)&theKey, (void**)&theHotKeyMapEntry) )
|
|
{
|
|
if( ![theHotKeyMapEntry->hotKeyEvent setCollectiveEnabled:aFlag] )
|
|
theAllSucceeded = NO;
|
|
}
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
|
|
return theAllSucceeded;
|
|
}
|
|
|
|
/*
|
|
* +isEnabledKeyCode:modifierFlags:
|
|
*/
|
|
+ (BOOL)isEnabledKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags
|
|
{
|
|
return [[self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags] isEnabled];
|
|
}
|
|
|
|
/*
|
|
* +getHotKeyForKeyCode:character:modifierFlags:
|
|
*/
|
|
+ (NDHotKeyEvent *)getHotKeyForKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags
|
|
{
|
|
NDHotKeyEvent * theHotKey = nil;
|
|
|
|
theHotKey = [self findHotKeyForKeyCode:aKeyCode modifierFlags:aModifierFlags];
|
|
return theHotKey ? theHotKey : [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags];
|
|
}
|
|
|
|
/*
|
|
* +findHotKeyForKeyCode:modifierFlags:
|
|
*/
|
|
+ (NDHotKeyEvent *)findHotKeyForKeyCode:(unsigned short)aKeyCode modifierFlags:(unsigned int)aModifierFlags
|
|
{
|
|
struct HotKeyMappingEntry * theFoundEntry = NULL;
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
|
|
if( theMapTable )
|
|
{
|
|
NDHotKeyEventLock;
|
|
theFoundEntry = (struct HotKeyMappingEntry *) NSMapGet( theMapTable, (void*)(NSUInteger)(aKeyCode ^ aModifierFlags) );
|
|
if( theFoundEntry != NULL )
|
|
[[theFoundEntry->hotKeyEvent retain] autorelease];
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
|
|
return (theFoundEntry) ? theFoundEntry->hotKeyEvent : nil;
|
|
}
|
|
|
|
/*
|
|
* +hotKeyWithKeyCode:character:modifierFlags:
|
|
*/
|
|
+ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags
|
|
{
|
|
return [self hotKeyWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL];
|
|
}
|
|
|
|
/*
|
|
* +hotKeyWithKeyCode:character:modifierFlags:target:selector:
|
|
*/
|
|
+ (id)hotKeyWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector
|
|
{
|
|
return [[[self alloc] initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:aTarget selector:aSelector] autorelease];
|
|
}
|
|
|
|
+ (id)hotKeyWithWithPropertyList:(id)aPropertyList
|
|
{
|
|
return [[[self alloc] initWithPropertyList:aPropertyList] autorelease];
|
|
}
|
|
|
|
+ (NSString *)description
|
|
{
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
NSString * theDescription = nil;
|
|
if( theMapTable )
|
|
{
|
|
NDHotKeyEventLock;
|
|
theDescription = @"";
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
return theDescription;
|
|
}
|
|
|
|
/*
|
|
* -init
|
|
*/
|
|
- (id)init
|
|
{
|
|
[self release];
|
|
NSAssert( NO, @"You can not initialize a Hot Key with the init method" );
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* -initWithKeyCode:character:modifierFlags:
|
|
*/
|
|
- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags
|
|
{
|
|
return [self initWithKeyCode:aKeyCode character:aChar modifierFlags:aModifierFlags target:nil selector:NULL];
|
|
}
|
|
|
|
/*
|
|
* -initWithKeyCode:character:modifierFlags:target:selector:
|
|
*/
|
|
- (id)initWithKeyCode:(unsigned short)aKeyCode character:(unichar)aChar modifierFlags:(unsigned int)aModifierFlags target:(id)aTarget selector:(SEL)aSelector
|
|
{
|
|
if( (self = [super init]) != nil )
|
|
{
|
|
keyCode = aKeyCode;
|
|
character = aChar;
|
|
modifierFlags = aModifierFlags;
|
|
target = aTarget;
|
|
selectorReleased = aSelector;
|
|
currentEventType = NDHotKeyNoEvent;
|
|
isEnabled.collective = YES;
|
|
|
|
if( ![self addHotKey] )
|
|
{
|
|
[self release];
|
|
self = nil;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self release];
|
|
self = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithCoder:(NSCoder *)aDecoder
|
|
{
|
|
if( (self = [super init]) != nil)
|
|
{
|
|
if( [aDecoder allowsKeyedCoding] )
|
|
{
|
|
keyCode = [[aDecoder decodeObjectForKey:kArchivingKeyCodeKey] unsignedShortValue];
|
|
character = [[aDecoder decodeObjectForKey:kArchivingCharacterKey] unsignedShortValue];
|
|
modifierFlags = [[aDecoder decodeObjectForKey:kArchivingModifierFlagsKey] unsignedIntValue];
|
|
|
|
selectorReleased = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorReleasedCodeKey] );
|
|
selectorPressed = NSSelectorFromString( [aDecoder decodeObjectForKey:kArchivingSelectorPressedCodeKey] );
|
|
}
|
|
else
|
|
{
|
|
[aDecoder decodeValueOfObjCType:@encode(unsigned short) at:&keyCode];
|
|
[aDecoder decodeValueOfObjCType:@encode(unichar) at:&character];
|
|
[aDecoder decodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags];
|
|
|
|
selectorReleased = NSSelectorFromString( [aDecoder decodeObject] );
|
|
selectorPressed = NSSelectorFromString( [aDecoder decodeObject] );
|
|
}
|
|
|
|
if( ![self addHotKey] )
|
|
{
|
|
[self release];
|
|
self = nil;
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)anEncoder
|
|
{
|
|
if( [anEncoder allowsKeyedCoding] )
|
|
{
|
|
[anEncoder encodeObject:[NSNumber numberWithUnsignedShort:keyCode] forKey:kArchivingKeyCodeKey];
|
|
[anEncoder encodeObject:[NSNumber numberWithUnsignedShort:character] forKey:kArchivingCharacterKey];
|
|
[anEncoder encodeObject:[NSNumber numberWithUnsignedInt:modifierFlags] forKey:kArchivingModifierFlagsKey];
|
|
|
|
[anEncoder encodeObject:NSStringFromSelector( selectorReleased ) forKey:kArchivingSelectorReleasedCodeKey];
|
|
[anEncoder encodeObject:NSStringFromSelector( selectorPressed ) forKey:kArchivingSelectorPressedCodeKey];
|
|
}
|
|
else
|
|
{
|
|
[anEncoder encodeValueOfObjCType:@encode(unsigned short) at:&keyCode];
|
|
[anEncoder encodeValueOfObjCType:@encode(unichar) at:&character];
|
|
[anEncoder encodeValueOfObjCType:@encode(unsigned int) at:&modifierFlags];
|
|
|
|
[anEncoder encodeObject:NSStringFromSelector( selectorReleased )];
|
|
[anEncoder encodeObject:NSStringFromSelector( selectorPressed )];
|
|
}
|
|
}
|
|
|
|
- (id)initWithPropertyList:(id)aPropertyList
|
|
{
|
|
if( aPropertyList )
|
|
{
|
|
NSString * theCharacter;
|
|
NSNumber * theKeyCode,
|
|
* theModiferFlag;
|
|
SEL theKeyPressedSelector,
|
|
theKeyReleasedSelector;
|
|
|
|
theKeyCode = [aPropertyList objectForKey:kArchivingKeyCodeKey];
|
|
theCharacter = [aPropertyList objectForKey:kArchivingCharacterKey];
|
|
theModiferFlag = [aPropertyList objectForKey:kArchivingModifierFlagsKey];
|
|
theKeyPressedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorPressedCodeKey]);
|
|
theKeyReleasedSelector = NSSelectorFromString([aPropertyList objectForKey:kArchivingSelectorReleasedCodeKey]);
|
|
|
|
self = [self initWithKeyCode:[theKeyCode unsignedShortValue] character:[theCharacter characterAtIndex:0] modifierFlags:[theModiferFlag unsignedIntValue]];
|
|
}
|
|
else
|
|
{
|
|
[self release];
|
|
self = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)propertyList
|
|
{
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithUnsignedShort:[self keyCode]], kArchivingKeyCodeKey,
|
|
[NSString stringWithCharacters:&character length:1] , kArchivingCharacterKey,
|
|
[NSNumber numberWithUnsignedInt:[self modifierFlags]], kArchivingModifierFlagsKey,
|
|
NSStringFromSelector( selectorPressed ), kArchivingSelectorPressedCodeKey,
|
|
NSStringFromSelector( selectorReleased ), kArchivingSelectorReleasedCodeKey,
|
|
nil];
|
|
}
|
|
|
|
/*
|
|
* -release
|
|
*/
|
|
- (oneway void)release
|
|
{
|
|
/*
|
|
* We need to remove the hot key from the hash table before it's retain count reaches zero
|
|
*/
|
|
if( [self retainCount] == 1 )
|
|
{
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
if( theMapTable )
|
|
{
|
|
NDHotKeyEventLock;
|
|
switchHotKey( self, NO );
|
|
if( [self retainCount] == 1 ) // check again because it might have changed
|
|
{
|
|
[theMapTable removeObjectForKey:[NSNumber numberWithInteger:[self hash]]];
|
|
}
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
}
|
|
// else
|
|
[super release];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if( UnregisterEventHotKey( reference ) != noErr ) // in lock from release
|
|
DLog( @"Failed to unregister hot key %@", self );
|
|
[super dealloc];
|
|
}
|
|
|
|
/*
|
|
* -setEnabled:
|
|
*/
|
|
- (BOOL)setEnabled:(BOOL)aFlag
|
|
{
|
|
BOOL theResult = YES;
|
|
|
|
if( [NDHotKeyEvent install] )
|
|
{
|
|
/*
|
|
* if individual and collective YES then currently ON, otherwise currently off
|
|
*/
|
|
NDHotKeyEventLock;
|
|
if( aFlag == YES && isEnabled.collective == YES && isEnabled.individual == NO )
|
|
{
|
|
theResult = (switchHotKey( self, YES ) == noErr);
|
|
}
|
|
else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES )
|
|
{
|
|
theResult = (switchHotKey( self, NO ) == noErr);
|
|
}
|
|
NDHotKeyEventUnlock;
|
|
|
|
if( theResult )
|
|
isEnabled.individual = aFlag;
|
|
else
|
|
DLog(@"%s failed ", aFlag ? "enable" : "disable" );
|
|
}
|
|
else
|
|
theResult = NO;
|
|
|
|
return theResult;
|
|
}
|
|
|
|
/*
|
|
* -isEnabled
|
|
*/
|
|
- (BOOL)isEnabled
|
|
{
|
|
return isEnabled.individual && isEnabled.collective;
|
|
}
|
|
|
|
/*
|
|
* -target
|
|
*/
|
|
- (id)target
|
|
{
|
|
return target;
|
|
}
|
|
|
|
/*
|
|
* -selector
|
|
*/
|
|
- (SEL)selector
|
|
{
|
|
return selectorReleased;
|
|
}
|
|
|
|
/*
|
|
* -selectorReleased
|
|
*/
|
|
- (SEL)selectorReleased
|
|
{
|
|
return selectorReleased;
|
|
}
|
|
|
|
/*
|
|
* -selectorPressed
|
|
*/
|
|
- (SEL)selectorPressed
|
|
{
|
|
return selectorPressed;
|
|
}
|
|
|
|
/*
|
|
* -currentEventType
|
|
* (NDHotKeyNoEvent | NDHotKeyPressedEvent | NDHotKeyReleasedEvent)
|
|
*/
|
|
- (int)currentEventType
|
|
{
|
|
return currentEventType;
|
|
}
|
|
|
|
/*
|
|
* -setTarget:selector:
|
|
*/
|
|
- (BOOL)setTarget:(id)aTarget selector:(SEL)aSelector
|
|
{
|
|
return [self setTarget:aTarget selectorReleased:aSelector selectorPressed:(SEL)0];
|
|
}
|
|
|
|
/*
|
|
* -setTarget:selectorReleased:selectorPressed:
|
|
*/
|
|
- (BOOL)setTarget:(id)aTarget selectorReleased:(SEL)aSelectorReleased selectorPressed:(SEL)aSelectorPressed
|
|
{
|
|
[self setEnabled:NO];
|
|
if( target && target != aTarget )
|
|
{
|
|
[self setEnabled:NO];
|
|
if( ![target respondsToSelector:@selector(targetWillChangeToObject:forHotKeyEvent:)] || [target targetWillChangeToObject:aTarget forHotKeyEvent:self] )
|
|
{
|
|
target = aTarget;
|
|
selectorReleased = aSelectorReleased;
|
|
selectorPressed = aSelectorPressed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target = aTarget;
|
|
selectorReleased = aSelectorReleased;
|
|
selectorPressed = aSelectorPressed;
|
|
}
|
|
|
|
return target == aTarget; // was change succesful
|
|
}
|
|
|
|
/*
|
|
* -performHotKeyReleased
|
|
*/
|
|
- (void)performHotKeyReleased
|
|
{
|
|
NSAssert( target, @"NDHotKeyEvent tried to perfrom release with no target" );
|
|
|
|
if( selectorReleased && [target respondsToSelector:selectorReleased])
|
|
{
|
|
currentEventType = NDHotKeyReleasedEvent;
|
|
[target performSelector:selectorReleased withObject:self];
|
|
currentEventType = NDHotKeyNoEvent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* -performHotKeyPressed
|
|
*/
|
|
- (void)performHotKeyPressed
|
|
{
|
|
NSAssert( target, @"NDHotKeyEvent tried to perfrom press with no target" );
|
|
|
|
if( selectorPressed && [target respondsToSelector:selectorPressed])
|
|
{
|
|
currentEventType = NDHotKeyPressedEvent;
|
|
[target performSelector:selectorPressed withObject:self];
|
|
currentEventType = NDHotKeyNoEvent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* -keyCode
|
|
*/
|
|
- (unsigned short)keyCode
|
|
{
|
|
return keyCode;
|
|
}
|
|
|
|
/*
|
|
* -character
|
|
*/
|
|
- (unichar)character
|
|
{
|
|
return character;
|
|
}
|
|
|
|
/*
|
|
* -modifierFlags
|
|
*/
|
|
- (unsigned int)modifierFlags
|
|
{
|
|
return modifierFlags;
|
|
}
|
|
|
|
/*
|
|
* -stringValue
|
|
*/
|
|
- (NSString *)stringValue
|
|
{
|
|
NSString * theStringValue = nil;
|
|
NDHotKeyEventLock;
|
|
theStringValue = stringForKeyCodeAndModifierFlags( [self keyCode], [self character], [self modifierFlags] );
|
|
NDHotKeyEventUnlock;
|
|
|
|
return theStringValue;
|
|
}
|
|
|
|
|
|
/*
|
|
* -isEqual:
|
|
*/
|
|
- (BOOL)isEqual:(id)anObject
|
|
{
|
|
return [super isEqual:anObject] || ([anObject isKindOfClass:[self class]] == YES && [self keyCode] == [(NDHotKeyEvent*)anObject keyCode] && [self modifierFlags] == [(NDHotKeyEvent*)anObject modifierFlags]);
|
|
}
|
|
|
|
/*
|
|
* -hash
|
|
*/
|
|
- (NSUInteger)hash
|
|
{
|
|
return (unsigned int)keyCode ^ modifierFlags; // xor
|
|
}
|
|
|
|
/*
|
|
* -description
|
|
*/
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"{\n\tKey Combination: %@,\n\tEnabled: %s\n\tKey Press Selector: %@\n\tKey Release Selector: %@\n}\n", [self stringValue],
|
|
[self isEnabled] ? "yes" : "no",
|
|
NSStringFromSelector([self selectorPressed]),
|
|
NSStringFromSelector([self selectorReleased])];
|
|
}
|
|
|
|
/*
|
|
* cocoaModifierFlagsToCarbonModifierFlags()
|
|
*/
|
|
unsigned int cocoaModifierFlagsToCarbonModifierFlags( unsigned int aModifierFlags )
|
|
{
|
|
unsigned int theCarbonModifierFlags = 0;
|
|
|
|
if(aModifierFlags & NSShiftKeyMask)
|
|
theCarbonModifierFlags |= shiftKey;
|
|
|
|
if(aModifierFlags & NSControlKeyMask)
|
|
theCarbonModifierFlags |= controlKey;
|
|
|
|
if(aModifierFlags & NSAlternateKeyMask)
|
|
theCarbonModifierFlags |= optionKey;
|
|
|
|
if(aModifierFlags & NSCommandKeyMask)
|
|
theCarbonModifierFlags |= cmdKey;
|
|
|
|
return theCarbonModifierFlags;
|
|
}
|
|
|
|
/*
|
|
* eventHandlerCallback()
|
|
*/
|
|
pascal OSErr eventHandlerCallback( EventHandlerCallRef anInHandlerCallRef, EventRef anInEvent, void * anInUserData )
|
|
{
|
|
NSMapTable * allHotKeyEvents = (NSMapTable *)anInUserData;
|
|
EventHotKeyID theHotKeyID;
|
|
OSStatus theError;
|
|
|
|
NSCAssert( GetEventClass( anInEvent ) == kEventClassKeyboard, @"Got event that is not a hot key event" );
|
|
|
|
theError = GetEventParameter( anInEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &theHotKeyID );
|
|
|
|
if( theError == noErr )
|
|
{
|
|
struct HotKeyMappingEntry * theHotKeyMappingEntry;
|
|
NDHotKeyEvent * theHotKeyEvent;
|
|
UInt32 theEventKind;
|
|
|
|
NSCAssert( [NDHotKeyEvent signature] == theHotKeyID.signature, @"Got hot key event with wrong signature" );
|
|
|
|
theHotKeyMappingEntry = (struct HotKeyMappingEntry *) NSMapGet(allHotKeyEvents, (void *)(NSUInteger)theHotKeyID.id);
|
|
|
|
if ( theHotKeyMappingEntry ) {
|
|
theHotKeyEvent = theHotKeyMappingEntry->hotKeyEvent;
|
|
theEventKind = GetEventKind( anInEvent );
|
|
if( kEventHotKeyPressed == theEventKind )
|
|
{
|
|
[theHotKeyEvent performHotKeyPressed];
|
|
}
|
|
else if( kEventHotKeyReleased == theEventKind )
|
|
{
|
|
[theHotKeyEvent performHotKeyReleased];
|
|
}
|
|
}
|
|
}
|
|
|
|
return theError;
|
|
}
|
|
|
|
/*
|
|
* hashValueHashFunction()
|
|
*/
|
|
NSUInteger hashValueHashFunction( NSMapTable * aTable, const void * aHotKeyEntry )
|
|
{
|
|
struct HotKeyMappingEntry * theHotKeyEntry;
|
|
unsigned int theKeyCode,
|
|
theModifiers;
|
|
|
|
theHotKeyEntry = (struct HotKeyMappingEntry*)aHotKeyEntry;
|
|
theKeyCode = (unsigned int)theHotKeyEntry->keyCode;
|
|
theModifiers = (unsigned int)theHotKeyEntry->modifierFlags;
|
|
return theKeyCode ^ theModifiers; // xor
|
|
}
|
|
|
|
/*
|
|
* isEqualHashFunction()
|
|
*/
|
|
BOOL isEqualHashFunction( NSMapTable * aTable, const void * aFirstHotKeyEntry, const void * aSecondHotKeyEntry )
|
|
{
|
|
struct HotKeyMappingEntry * theFirst,
|
|
* theSecond;
|
|
|
|
theFirst = (struct HotKeyMappingEntry*)aFirstHotKeyEntry;
|
|
theSecond = (struct HotKeyMappingEntry*)aSecondHotKeyEntry;
|
|
return theFirst->keyCode == theSecond->keyCode && theFirst->modifierFlags == theSecond->modifierFlags;
|
|
}
|
|
|
|
/*
|
|
* describeHashFunction()
|
|
*/
|
|
NSString * describeHashFunction( NSMapTable * aTable, const void * aHotKeyEntry )
|
|
{
|
|
NDHotKeyEvent * theHotKey;
|
|
|
|
theHotKey = ((struct HotKeyMappingEntry*)aHotKeyEntry)->hotKeyEvent;
|
|
return [theHotKey description];
|
|
}
|
|
|
|
NSUInteger hashKeyHashFunction( NSMapTable * aTable, const void * aNumber )
|
|
{
|
|
NSNumber * theNumber = (NSNumber *) aNumber;
|
|
return [theNumber integerValue];
|
|
}
|
|
|
|
BOOL isEqualKeyHashFunction( NSMapTable * aTable, const void * aFirstNumber, const void * aSecondNumber )
|
|
{
|
|
NSNumber * theFirstNumber = (NSNumber *) aFirstNumber;
|
|
NSNumber * theSecondNumber = (NSNumber *) aSecondNumber;
|
|
return [theFirstNumber isEqual:theSecondNumber];
|
|
}
|
|
|
|
void hashKeyRetainFunction( NSMapTable * aTable, const void * aNumber )
|
|
{
|
|
const NSNumber * theNumber = (const NSNumber *) aNumber;
|
|
[theNumber retain];
|
|
}
|
|
|
|
void hashKeyReleaseFunction( NSMapTable * aTable, void * aNumber )
|
|
{
|
|
NSNumber * theNumber = (NSNumber *) aNumber;
|
|
[theNumber release];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NDHotKeyEvent (Private)
|
|
|
|
/*
|
|
* +allHotKeyEvents
|
|
*/
|
|
+ (NSMapTable *)allHotKeyEvents
|
|
{
|
|
if( allHotKeyEvents == NULL )
|
|
{
|
|
NDHotKeyEventLock;
|
|
if( allHotKeyEvents == NULL )
|
|
allHotKeyEvents = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
|
|
return allHotKeyEvents;
|
|
}
|
|
|
|
/*
|
|
* -addHotKey
|
|
*/
|
|
- (BOOL)addHotKey
|
|
{
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
if( theMapTable )
|
|
{
|
|
struct HotKeyMappingEntry * theEntry;
|
|
|
|
theEntry = (struct HotKeyMappingEntry *)malloc(sizeof(struct HotKeyMappingEntry));
|
|
|
|
theEntry->keyCode = [self keyCode];
|
|
theEntry->modifierFlags = [self modifierFlags];
|
|
theEntry->hotKeyEvent = self;
|
|
|
|
NDHotKeyEventLock;
|
|
NSMapInsert(theMapTable, (void*)[self hash], theEntry);
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
/*
|
|
* -removeHotKey
|
|
*/
|
|
- (void)removeHotKey
|
|
{
|
|
[self setEnabled:NO];
|
|
|
|
NSMapTable * theMapTable = [NDHotKeyEvent allHotKeyEvents];
|
|
if( theMapTable )
|
|
{
|
|
NDHotKeyEventLock;
|
|
free(NSMapGet(theMapTable, (void *)[self hash]));
|
|
NSMapRemove(theMapTable, (void*)[self hash]);
|
|
NDHotKeyEventUnlock;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* setCollectiveEnabled:
|
|
*/
|
|
- (BOOL)setCollectiveEnabled:(BOOL)aFlag
|
|
{
|
|
BOOL theResult = YES;
|
|
|
|
if( [NDHotKeyEvent install] )
|
|
{
|
|
/*
|
|
* if individual and collective YES then currently ON, otherwise currently off
|
|
*/
|
|
NDHotKeyEventLock;
|
|
if( aFlag == YES && isEnabled.collective == NO && isEnabled.individual == YES )
|
|
{
|
|
theResult = (switchHotKey( self, YES ) == noErr);
|
|
}
|
|
else if( aFlag == NO && isEnabled.collective == YES && isEnabled.individual == YES )
|
|
{
|
|
theResult = (switchHotKey( self, NO ) == noErr);
|
|
}
|
|
NDHotKeyEventUnlock;
|
|
|
|
if( theResult )
|
|
isEnabled.collective = aFlag;
|
|
else
|
|
DLog(@"%s failed", aFlag ? "enable" : "disable" );
|
|
}
|
|
else
|
|
theResult = NO;
|
|
|
|
return theResult;
|
|
}
|
|
|
|
/*
|
|
* collectiveEnable()
|
|
*/
|
|
- (BOOL)collectiveEnable
|
|
{
|
|
return isEnabled.collective;
|
|
}
|
|
|
|
/*
|
|
* switchHotKey()
|
|
*/
|
|
static OSStatus switchHotKey( NDHotKeyEvent * self, BOOL aFlag )
|
|
{
|
|
OSStatus theError;
|
|
if( aFlag )
|
|
{
|
|
struct HotKeyMappingEntry theDummyEntry;
|
|
|
|
theDummyEntry.keyCode = [self keyCode];
|
|
theDummyEntry.modifierFlags = [self modifierFlags];
|
|
theDummyEntry.hotKeyEvent = nil;
|
|
|
|
EventHotKeyID theHotKeyID;
|
|
|
|
theHotKeyID.signature = [NDHotKeyEvent signature];
|
|
theHotKeyID.id = hashValueHashFunction( nil, (const void *)&theDummyEntry );
|
|
|
|
NSCAssert( theHotKeyID.signature, @"HotKeyEvent signature has not been set yet" );
|
|
NSCParameterAssert(sizeof(unsigned long) >= sizeof(id) );
|
|
|
|
theError = RegisterEventHotKey( self->keyCode, cocoaModifierFlagsToCarbonModifierFlags(self->modifierFlags), theHotKeyID, GetEventDispatcherTarget(), 0, &self->reference );
|
|
}
|
|
else
|
|
{
|
|
theError = UnregisterEventHotKey( self->reference );
|
|
}
|
|
|
|
return theError;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/*
|
|
* stringForKeyCodeAndModifierFlags()
|
|
*/
|
|
NSString * stringForKeyCodeAndModifierFlags( unsigned short aKeyCode, unichar aChar, unsigned int aModifierFlags )
|
|
{
|
|
NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter );
|
|
NSString * stringForModifiers( unsigned int aModifierFlags );
|
|
|
|
return [stringForModifiers(aModifierFlags) stringByAppendingString:stringForCharacter( aKeyCode, aChar )];
|
|
}
|
|
|
|
/*
|
|
* unicharForKeyCode()
|
|
*/
|
|
unichar unicharForKeyCode( unsigned short aKeyCode )
|
|
{
|
|
UInt32 theChar = kNullCharCode;
|
|
|
|
TISInputSourceRef theCurrentKeyBoardLayout = TISCopyCurrentKeyboardLayoutInputSource();
|
|
UCKeyboardLayout *uchr = (UCKeyboardLayout *) TISGetInputSourceProperty(theCurrentKeyBoardLayout, kTISPropertyUnicodeKeyLayoutData);
|
|
if ( uchr != nil )
|
|
{
|
|
UInt32 deadKeyState = 0;
|
|
UInt32 flags = 0;
|
|
UniCharCount maxStringLength = 255;
|
|
UniCharCount actualStringLength = 0;
|
|
UniChar unicodeString[maxStringLength];
|
|
|
|
UCKeyTranslate(uchr, aKeyCode, kUCKeyActionDown, flags, LMGetKbdType(), 1, &deadKeyState, maxStringLength, &actualStringLength, unicodeString);
|
|
|
|
if ( actualStringLength == 1 )
|
|
theChar = unicodeString[ 0 ];
|
|
|
|
switch( theChar )
|
|
{
|
|
case kHomeCharCode: theChar = NSHomeFunctionKey; break;
|
|
// case kEnterCharCode: theChar = ; break;
|
|
case kEndCharCode: theChar = NSEndFunctionKey; break;
|
|
case kHelpCharCode: theChar = NSHelpFunctionKey; break;
|
|
// case kBellCharCode: theChar = ; break;
|
|
// case kBackspaceCharCode: theChar = ; break;
|
|
// case kTabCharCode: theChar = ; break;
|
|
// case kLineFeedCharCode: theChar = ; break;
|
|
case kPageUpCharCode: theChar = NSPageUpFunctionKey; break;
|
|
case kPageDownCharCode: theChar = NSPageDownFunctionKey; break;
|
|
// case kReturnCharCode: theChar = ; break;
|
|
case kFunctionKeyCharCode: theChar = unicodeForFunctionKey( aKeyCode ); break;
|
|
// case kCommandCharCode: theChar = ; break;
|
|
// case kCheckCharCode: theChar = ; break;
|
|
// case kDiamondCharCode : theChar = ; break;
|
|
// case kAppleLogoCharCode: theChar = ; break;
|
|
// case kEscapeCharCode: theChar = ; break;
|
|
case kClearCharCode:
|
|
theChar = (aKeyCode==0x47) ? NSInsertFunctionKey : theChar;
|
|
break;
|
|
case kLeftArrowCharCode: theChar = NSLeftArrowFunctionKey; break;
|
|
case kRightArrowCharCode: theChar = NSRightArrowFunctionKey; break;
|
|
case kUpArrowCharCode: theChar = NSUpArrowFunctionKey; break;
|
|
case kDownArrowCharCode: theChar = NSDownArrowFunctionKey; break;
|
|
// case kSpaceCharCode: theChar = ; break;
|
|
case kDeleteCharCode: theChar = NSDeleteCharFunctionKey; break;
|
|
// case kBulletCharCode: theChar = ; break;
|
|
// case kNonBreakingSpaceCharCode: theChar = ; break;
|
|
}
|
|
}
|
|
|
|
return theChar;
|
|
}
|
|
|
|
unichar unicodeForFunctionKey( UInt32 aKeyCode )
|
|
{
|
|
switch( aKeyCode )
|
|
{
|
|
case 0x7A: return NSF1FunctionKey;
|
|
case 0x78: return NSF2FunctionKey;
|
|
case 0x63: return NSF3FunctionKey;
|
|
case 0x76: return NSF4FunctionKey;
|
|
case 0x60: return NSF5FunctionKey;
|
|
case 0x61: return NSF6FunctionKey;
|
|
case 0x62: return NSF7FunctionKey;
|
|
case 0x64: return NSF8FunctionKey;
|
|
case 0x65: return NSF9FunctionKey;
|
|
case 0x6D: return NSF10FunctionKey;
|
|
case 0x67: return NSF11FunctionKey;
|
|
case 0x6F: return NSF12FunctionKey;
|
|
default: return 0x00;
|
|
}
|
|
}
|
|
|
|
NSString * stringForCharacter( const unsigned short aKeyCode, unichar aCharacter )
|
|
{
|
|
NSString * theString = nil;
|
|
switch( aCharacter )
|
|
{
|
|
case NSUpArrowFunctionKey:
|
|
aCharacter = 0x2191;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSDownArrowFunctionKey:
|
|
aCharacter = 0x2193;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSLeftArrowFunctionKey:
|
|
aCharacter = 0x2190;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSRightArrowFunctionKey:
|
|
aCharacter = 0x2192;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSF1FunctionKey: theString = @"F1"; break;
|
|
case NSF2FunctionKey: theString = @"F2"; break;
|
|
case NSF3FunctionKey: theString = @"F3"; break;
|
|
case NSF4FunctionKey: theString = @"F4"; break;
|
|
case NSF5FunctionKey: theString = @"F5"; break;
|
|
case NSF6FunctionKey: theString = @"F6"; break;
|
|
case NSF7FunctionKey: theString = @"F7"; break;
|
|
case NSF8FunctionKey: theString = @"F8"; break;
|
|
case NSF9FunctionKey: theString = @"F9"; break;
|
|
case NSF10FunctionKey: theString = @"F10"; break;
|
|
case NSF11FunctionKey: theString = @"F11"; break;
|
|
case NSF12FunctionKey: theString = @"F12"; break;
|
|
case NSF13FunctionKey: theString = @"F13"; break;
|
|
case NSF14FunctionKey: theString = @"F14"; break;
|
|
case NSF15FunctionKey: theString = @"F15"; break;
|
|
case NSF16FunctionKey: theString = @"F16"; break;
|
|
case NSF17FunctionKey: theString = @"F17"; break;
|
|
case NSF18FunctionKey: theString = @"F18"; break;
|
|
case NSF19FunctionKey: theString = @"F19"; break;
|
|
case NSF20FunctionKey: theString = @"F20"; break;
|
|
case NSF21FunctionKey: theString = @"F21"; break;
|
|
case NSF22FunctionKey: theString = @"F22"; break;
|
|
case NSF23FunctionKey: theString = @"F23"; break;
|
|
case NSF24FunctionKey: theString = @"F24"; break;
|
|
case NSF25FunctionKey: theString = @"F25"; break;
|
|
case NSF26FunctionKey: theString = @"F26"; break;
|
|
case NSF27FunctionKey: theString = @"F27"; break;
|
|
case NSF28FunctionKey: theString = @"F28"; break;
|
|
case NSF29FunctionKey: theString = @"F29"; break;
|
|
case NSF30FunctionKey: theString = @"F30"; break;
|
|
case NSF31FunctionKey: theString = @"F31"; break;
|
|
case NSF32FunctionKey: theString = @"F32"; break;
|
|
case NSF33FunctionKey: theString = @"F33"; break;
|
|
case NSF34FunctionKey: theString = @"F34"; break;
|
|
case NSF35FunctionKey: theString = @"F35"; break;
|
|
case NSInsertFunctionKey: theString = @"Ins"; break;
|
|
case NSDeleteFunctionKey: theString = @"Delete"; break;
|
|
case NSHomeFunctionKey:
|
|
aCharacter = 0x2196;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSBeginFunctionKey: theString = @"Begin"; break;
|
|
case NSEndFunctionKey: theString = @"End"; break;
|
|
case NSPageUpFunctionKey:
|
|
// aCharacter = 0x21DE;
|
|
// theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
theString = @"PgUp";
|
|
break;
|
|
case NSPageDownFunctionKey:
|
|
// aCharacter = 0x21DF;
|
|
// theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
theString = @"PgDn";
|
|
break;
|
|
case NSPrintScreenFunctionKey: theString = @"Print"; break;
|
|
case NSScrollLockFunctionKey: theString = @"ScrollLock"; break;
|
|
case NSPauseFunctionKey: theString = @"Pause"; break;
|
|
case NSSysReqFunctionKey: theString = @"SysReq"; break;
|
|
case NSBreakFunctionKey: theString = @"Break"; break;
|
|
case NSResetFunctionKey: theString = @"Reset"; break;
|
|
case NSStopFunctionKey: theString = @"Stop"; break;
|
|
case NSMenuFunctionKey: theString = @"Menu"; break;
|
|
case NSUserFunctionKey: theString = @"User"; break;
|
|
case NSSystemFunctionKey: theString = @"System"; break;
|
|
case NSPrintFunctionKey: theString = @"Print"; break;
|
|
case NSClearLineFunctionKey: theString = @"ClearLine"; break;
|
|
case NSClearDisplayFunctionKey: theString = @"ClearDisplay"; break;
|
|
case NSInsertLineFunctionKey: theString = @"InsertLine"; break;
|
|
case NSDeleteLineFunctionKey: theString = @"DeleteLine"; break;
|
|
case NSInsertCharFunctionKey: theString = @"InsertChar"; break;
|
|
case NSDeleteCharFunctionKey:
|
|
aCharacter = 0x2326;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case NSPrevFunctionKey: theString = @"Prev"; break;
|
|
case NSNextFunctionKey: theString = @"Next"; break;
|
|
case NSSelectFunctionKey: theString = @"Select"; break;
|
|
case NSExecuteFunctionKey: theString = @"Exec"; break;
|
|
case NSUndoFunctionKey: theString = @"Undo"; break;
|
|
case NSRedoFunctionKey: theString = @"Redo"; break;
|
|
case NSFindFunctionKey: theString = @"Find"; break;
|
|
case NSHelpFunctionKey: theString = @"Help"; break;
|
|
case NSModeSwitchFunctionKey: theString = @"ModeSwitch"; break;
|
|
case kEscapeCharCode: theString = @"Esc"; break;
|
|
case kTabCharCode:
|
|
aCharacter = 0x21E5;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case kSpaceCharCode: theString = @"Space"; break;
|
|
case kEnterCharCode:
|
|
aCharacter = 0x21B5;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case kReturnCharCode:
|
|
aCharacter = 0x21A9;
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
case kDeleteCharCode: theString = @"Del"; break;
|
|
case '0'...'9':
|
|
case '=':
|
|
case '/':
|
|
case '*':
|
|
case '-':
|
|
case '+':
|
|
case '.':
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
if( aKeyCode > 60 )
|
|
theString = [NSString stringWithFormat:@"[%@]", theString];
|
|
break;
|
|
default:
|
|
aCharacter = unicharForKeyCode(aKeyCode);
|
|
|
|
if( aCharacter >= 'a' && aCharacter <= 'z' ) // convert to uppercase
|
|
aCharacter = aCharacter + 'A' - 'a';
|
|
|
|
theString = [NSString stringWithCharacters:&aCharacter length:1];
|
|
break;
|
|
}
|
|
return theString;
|
|
}
|
|
|
|
NSString * stringForModifiers( unsigned int aModifierFlags )
|
|
{
|
|
NSMutableString * theString;
|
|
unichar theCharacter;
|
|
|
|
theString = [NSMutableString string];
|
|
if( aModifierFlags & NSControlKeyMask)
|
|
{
|
|
theCharacter = kControlUnicode;
|
|
[theString appendString:[NSString stringWithCharacters:&theCharacter length:1]];
|
|
}
|
|
|
|
if( aModifierFlags & NSAlternateKeyMask)
|
|
{
|
|
theCharacter = kOptionUnicode;
|
|
[theString appendString:[NSString stringWithCharacters:&theCharacter length:1]];
|
|
}
|
|
|
|
if( aModifierFlags & NSShiftKeyMask)
|
|
{
|
|
theCharacter = kShiftUnicode;
|
|
[theString appendString:[NSString stringWithCharacters:&theCharacter length:1]];
|
|
}
|
|
|
|
if( aModifierFlags & NSCommandKeyMask)
|
|
{
|
|
theCharacter = kCommandUnicode;
|
|
[theString appendString:[NSString stringWithCharacters:&theCharacter length:1]];
|
|
}
|
|
|
|
return theString;
|
|
}
|