cog/Frameworks/NDHotKey/NDHotKey/NDKeyboardLayout.m

484 lines
15 KiB
Objective-C

/*
NDKeyboardLayout.m
Created by Nathan Day on 01.18.10 under a MIT-style license.
Copyright (c) 2010 Nathan Day
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "NDKeyboardLayout.h"
#include <libkern/OSAtomic.h>
NSString * const NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification = @"NDKeyboardLayoutSelectedKeyboardInputSourceChanged";
NSString * const NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey = @"NDKeyboardLayoutPreviousKeyboardLayout";
struct ReverseMappingEntry
{
UniChar character;
BOOL keypad;
UInt16 keyCode;
};
struct UnmappedEntry
{
UniChar character;
UInt16 keyCode;
unichar description[4];
};
struct UnmappedEntry unmappedKeys[] =
{
{NSDeleteFunctionKey, 0x33, {0x232B,'\0','\0','\0'}},
{NSF17FunctionKey, 0x40, {'F','1','7','\0'}},
{NSClearDisplayFunctionKey, 0x47, {0x2327,'\0','\0','\0'}},
{NSF18FunctionKey, 0x4F, {'F','1','8','\0'}},
{NSF19FunctionKey, 0x50, {'F','1','9','\0'}},
{NSF5FunctionKey, 0x60, {'F','5','\0','\0'}},
{NSF6FunctionKey, 0x61, {'F','6','\0','\0'}},
{NSF7FunctionKey, 0x62, {'F','7','\0','\0'}},
{NSF3FunctionKey, 0x63, {'F','3','\0','\0'}},
{NSF8FunctionKey, 0x64, {'F','8','\0','\0'}},
{NSF9FunctionKey, 0x65, {'F','9','\0','\0'}},
{NSF11FunctionKey, 0x67, {'F','1','1','\0'}},
{NSF14FunctionKey, 0x68, {'F','1','4','\0'}},
{NSF13FunctionKey, 0x69, {'F','1','3','\0'}},
{NSF16FunctionKey, 0x6A, {'F','1','6','\0'}},
{NSF10FunctionKey, 0x6D, {'F','1','0','\0'}},
{NSF12FunctionKey, 0x6F, {'F','1','2','\0'}},
{NSF15FunctionKey, 0x71, {'F','1','5','\0'}},
{NSHomeFunctionKey, 0x73, {0x21F1,'\0','\0','\0'}},
{NSPageUpFunctionKey, 0x74, {0x21DE,'\0','\0','\0'}},
{NSDeleteCharFunctionKey, 0x75, {0x2326,'\0','\0','\0'}},
{NSF4FunctionKey, 0x76, {'F','4','\0','\0'}},
{NSEndFunctionKey, 0x77, {0x21F2,'\0','\0','\0'}},
{NSF2FunctionKey, 0x78, {'F','2','\0','\0'}},
{NSPageDownFunctionKey, 0x79, {0x21DF,'\0','\0','\0'}},
{NSF1FunctionKey, 0x7A, {'F','1','\0','\0'}},
{NSLeftArrowFunctionKey, 0x7B, {0x2190,'\0','\0','\0'}},
{NSRightArrowFunctionKey, 0x7C, {0x2192,'\0','\0','\0'}},
{NSDownArrowFunctionKey, 0x7D, {0x2193,'\0','\0','\0'}},
{NSUpArrowFunctionKey, 0x7E, {0x2191,'\0','\0','\0'}}
// {NSF20FunctionKey, 0xXXXX},
// {NSF21FunctionKey, 0xXXXX},
// {NSF22FunctionKey, 0xXXXX},
// {NSF23FunctionKey, 0xXXXX},
// {NSF24FunctionKey, 0xXXXX},
// {NSF25FunctionKey, 0xXXXX},
// {NSF26FunctionKey, 0xXXXX},
// {NSF27FunctionKey, 0xXXXX},
// {NSF28FunctionKey, 0xXXXX},
// {NSF29FunctionKey, 0xXXXX},
// {NSF30FunctionKey, 0xXXXX},
// {NSF31FunctionKey, 0xXXXX},
// {NSF32FunctionKey, 0xXXXX},
// {NSF33FunctionKey, 0xXXXX},
// {NSF34FunctionKey, 0xXXXX},
// {NSF35FunctionKey, 0xXXXX},
// {NSInsertFunctionKey, 0xXXXX},
// {NSBeginFunctionKey, 0xXXXX},
// {NSPrintScreenFunctionKey, 0xXXXX},
// {NSScrollLockFunctionKey, 0xXXXX},
// {NSPauseFunctionKey, 0xXXXX},
// {NSSysReqFunctionKey, 0xXXXX},
// {NSBreakFunctionKey, 0xXXXX},
// {NSResetFunctionKey, 0xXXXX},
// {NSStopFunctionKey, 0xXXXX},
// {NSMenuFunctionKey, 0xXXXX},
// {NSUserFunctionKey, 0xXXXX},
// {NSSystemFunctionKey, 0xXXXX},
// {NSPrintFunctionKey, 0xXXXX},
// {NSClearLineFunctionKey, 0xXXXX},
// {NSInsertLineFunctionKey, 0xXXXX},
// {NSDeleteLineFunctionKey, 0xXXXX},
// {NSInsertCharFunctionKey, 0xXXXX},
// {NSPrevFunctionKey, 0xXXXX},
// {NSNextFunctionKey, 0xXXXX},
// {NSSelectFunctionKey, 0xXXXX},
// {NSExecuteFunctionKey, 0xXXXX},
// {NSUndoFunctionKey, 0xXXXX},
// {NSRedoFunctionKey, 0xXXXX},
// {NSFindFunctionKey, 0xXXXX},
// {NSHelpFunctionKey, 0xXXXX},
// {NSModeSwitchFunctionKey, 0xXXXX}
};
@interface NDKeyboardLayout ()
@property(readonly,nonatomic) const UCKeyboardLayout * keyboardLayoutPtr;
@end
static int _reverseMappingEntryCmpFunc( const void * a, const void * b )
{
struct ReverseMappingEntry * theA = (struct ReverseMappingEntry*)a,
* theB = (struct ReverseMappingEntry*)b;
return theA->character != theB->character ? theA->character - theB->character : theA->keypad - theB->keypad;
}
static struct ReverseMappingEntry * _searchreverseMapping( struct ReverseMappingEntry * aMapping, NSUInteger aLength, struct ReverseMappingEntry * aSearchValue )
{
NSInteger low = 0,
high = aLength - 1,
mid,
result;
while( low <= high )
{
mid = (low + high)>>1;
result = _reverseMappingEntryCmpFunc( &aMapping[mid], aSearchValue );
if( result > 0 )
high = mid - 1;
else if( result < 0 )
low = mid + 1;
else
return &aMapping[mid];
}
return NULL;
}
static struct UnmappedEntry * _unmappedEntryForKeyCode( UInt16 aKeyCode )
{
NSInteger low = 0,
high = sizeof(unmappedKeys)/sizeof(*unmappedKeys) - 1,
mid,
result;
while( low <= high )
{
mid = (low + high)>>1;
result = unmappedKeys[mid].keyCode - aKeyCode;
if( result > 0 )
high = mid - 1;
else if( result < 0 )
low = mid + 1;
else
return &unmappedKeys[mid];
}
return '\0';
}
static const size_t kBufferSize = 4;
static NSUInteger _characterForModifierFlags( unichar aBuff[kBufferSize], UInt32 aModifierFlags )
{
NSUInteger thePos = 0;
memset( aBuff, 0, kBufferSize );
if(aModifierFlags & NSControlKeyMask)
aBuff[thePos++] = kControlUnicode;
if(aModifierFlags & NSAlternateKeyMask)
aBuff[thePos++] = kOptionUnicode;
if(aModifierFlags & NSShiftKeyMask)
aBuff[thePos++] = kShiftUnicode;
if(aModifierFlags & NSCommandKeyMask)
aBuff[thePos++] = kCommandUnicode;
return thePos;
}
/*
* NDCocoaModifierFlagsForCarbonModifierFlags()
*/
NSUInteger NDCocoaModifierFlagsForCarbonModifierFlags( NSUInteger aModifierFlags )
{
NSUInteger theCocoaModifierFlags = 0;
if(aModifierFlags & shiftKey)
theCocoaModifierFlags |= NSShiftKeyMask;
if(aModifierFlags & controlKey)
theCocoaModifierFlags |= NSControlKeyMask;
if(aModifierFlags & optionKey)
theCocoaModifierFlags |= NSAlternateKeyMask;
if(aModifierFlags & cmdKey)
theCocoaModifierFlags |= NSCommandKeyMask;
return theCocoaModifierFlags;
}
/*
* NDCarbonModifierFlagsForCocoaModifierFlags()
*/
NSUInteger NDCarbonModifierFlagsForCocoaModifierFlags( NSUInteger aModifierFlags )
{
NSUInteger theCarbonModifierFlags = 0;
if(aModifierFlags & NSShiftKeyMask)
theCarbonModifierFlags |= shiftKey;
if(aModifierFlags & NSControlKeyMask)
theCarbonModifierFlags |= controlKey;
if(aModifierFlags & NSAlternateKeyMask)
theCarbonModifierFlags |= optionKey;
if(aModifierFlags & NSCommandKeyMask)
theCarbonModifierFlags |= cmdKey;
return theCarbonModifierFlags;
}
@implementation NDKeyboardLayout
#pragma mark Utility Methods
- (void)generateMappings
{
mappings = (struct ReverseMappingEntry*)calloc( 128 + sizeof(unmappedKeys)/sizeof(*unmappedKeys), sizeof(struct ReverseMappingEntry) );
numberOfMappings = 0;
for( NSUInteger i = 0; i < 128; i++ )
{
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
if( UCKeyTranslate( self.keyboardLayoutPtr,
i,
kUCKeyActionDisplay,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&theDeadKeyState,
1,
&theLength,
&mappings[numberOfMappings].character ) == noErr && theLength > 0 && isprint(mappings[numberOfMappings].character) )
{
mappings[numberOfMappings].keyCode = i;
numberOfMappings++;
}
}
/* add unmapped keys */
for( NSUInteger i = 0; i < sizeof(unmappedKeys)/sizeof(*unmappedKeys); i++ )
{
mappings[numberOfMappings].character = unmappedKeys[i].character;
mappings[numberOfMappings].keyCode = unmappedKeys[i].keyCode;
numberOfMappings++;
}
mappings = (struct ReverseMappingEntry*)realloc( (void*)mappings, numberOfMappings*sizeof(struct ReverseMappingEntry) );
// sort so we can perform binary searches
qsort( (void *)mappings, numberOfMappings, sizeof(struct ReverseMappingEntry), _reverseMappingEntryCmpFunc );
/* find keypad keys and set the keypad flag */
for( NSUInteger i = 1; i < numberOfMappings; i++ )
{
NSParameterAssert( mappings[i-1].keyCode != mappings[i].keyCode );
if( mappings[i-1].character == mappings[i].character ) // assume large keycode is a keypad
{
if( mappings[i-1].keyCode > mappings[i].keyCode ) // make the keypad entry is second
{
UInt16 theTemp = mappings[i-1].keyCode;
mappings[i-1].keyCode = mappings[i].keyCode;
mappings[i].keyCode = theTemp;
}
mappings[i].keypad = YES;
}
}
#ifdef DEBUGGING_CODE
for( NSUInteger i = 1; i < numberOfMappings; i++ )
{
fprintf( stderr, "%d -> %c[%d]%s\n",
mappings[i].keyCode,
(char)mappings[i].character,
mappings[i].character,
mappings[i].keypad ? " keypad" : ""
);
NSAssert3( mappings[i-1].character <= mappings[i].character, @"[%d] %d <= %d", i, mappings[i-1].character, mappings[i].character );
}
#endif
}
#pragma mark Constructor Methods
static volatile NDKeyboardLayout * kCurrentKeyboardLayout = nil;
static void NDKeyboardLayoutNotificationCallback( CFNotificationCenterRef aCenter, void * self, CFStringRef aName, const void * anObj, CFDictionaryRef aUserInfo )
{
NSDictionary * theUserInfo = [NSDictionary dictionaryWithObject:kCurrentKeyboardLayout forKey:NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey];
@synchronized(self) { kCurrentKeyboardLayout = nil; }
[[NSNotificationCenter defaultCenter] postNotificationName:NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification object:(__bridge id _Nullable)(self) userInfo:theUserInfo];
}
+ (void)initialize
{
CFNotificationCenterAddObserver( CFNotificationCenterGetLocalCenter(),
(const void *)self,
NDKeyboardLayoutNotificationCallback,
kTISNotifySelectedKeyboardInputSourceChanged,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately
);
}
+ (id)keyboardLayout
{
if( kCurrentKeyboardLayout == nil )
{
@synchronized(self)
{ /*
Try different method until we succeed.
*/
TISInputSourceRef (*theInputSourceFunctions[])() = {
TISCopyInputMethodKeyboardLayoutOverride,
TISCopyCurrentKeyboardLayoutInputSource,
TISCopyCurrentASCIICapableKeyboardLayoutInputSource
};
for( NSUInteger i = 0; i < sizeof(theInputSourceFunctions)/sizeof(*theInputSourceFunctions) && kCurrentKeyboardLayout == nil; i++ )
{
TISInputSourceRef theInputSource = theInputSourceFunctions[i]();
if( theInputSource != NULL )
{
kCurrentKeyboardLayout = [[self alloc] initWithInputSource:theInputSource];
CFRelease(theInputSource);
}
}
}
}
return kCurrentKeyboardLayout;
}
- (id)init
{
return [NDKeyboardLayout keyboardLayout];
}
- (id)initWithLanguage:(NSString *)aLangauge { return [self initWithInputSource:TISCopyInputSourceForLanguage((__bridge CFStringRef)aLangauge)]; }
- (id)initWithInputSource:(TISInputSourceRef)aSource
{
if( (self = [super init]) != nil )
{
if( aSource != NULL && (keyboardLayoutData = (CFDataRef)TISGetInputSourceProperty(aSource, kTISPropertyUnicodeKeyLayoutData)) != nil )
{
CFRetain( keyboardLayoutData );
}
else
self = nil;
}
return self;
}
- (void)dealloc
{
if( mappings != NULL )
free( (void*)mappings );
if( keyboardLayoutData != NULL )
CFRelease( keyboardLayoutData );
}
- (NSString*)stringForCharacter:(unichar)aCharacter modifierFlags:(UInt32)aModifierFlags
{
return [self stringForKeyCode:[self keyCodeForCharacter:aCharacter numericPad:(aModifierFlags&NSNumericPadKeyMask) != 0] modifierFlags:aModifierFlags];
}
- (NSString*)stringForKeyCode:(UInt16)aKeyCode modifierFlags:(UInt32)aModifierFlags
{
NSString * theResult = nil;
struct UnmappedEntry * theEntry = _unmappedEntryForKeyCode( aKeyCode ); // is it one of the unmapped values
if( theEntry != NULL )
{
unichar theCharacter[sizeof(theEntry->description)/sizeof(*theEntry->description)+4+1];
memset( theCharacter, 0, sizeof(theCharacter) );
NSUInteger thePos = _characterForModifierFlags(theCharacter,aModifierFlags);
memcpy( theCharacter+thePos, theEntry->description, sizeof(theEntry->description) );
theResult = [NSString stringWithCharacters:theCharacter length:sizeof(theEntry->description)/sizeof(*theEntry->description)+thePos];
}
else
{
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
UniChar theCharacter[260];
NSUInteger thePos = _characterForModifierFlags(theCharacter,aModifierFlags);
if( UCKeyTranslate( self.keyboardLayoutPtr, aKeyCode,
kUCKeyActionDisplay,
(UInt32)NDCarbonModifierFlagsForCocoaModifierFlags(aModifierFlags),
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&theDeadKeyState,
sizeof(theCharacter)/sizeof(*theCharacter)-thePos,
&theLength,
theCharacter+thePos ) == noErr )
{
theResult = [[NSString stringWithCharacters:theCharacter length:theLength+thePos] uppercaseString];
}
}
return theResult;
}
- (unichar)characterForKeyCode:(UInt16)aKeyCode
{
unichar theChar = 0;
struct UnmappedEntry * theEntry = _unmappedEntryForKeyCode( aKeyCode );
if( theEntry == NULL ) // is it one of the unmapped values
{
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
UniChar theCharacter[256];
if( UCKeyTranslate( self.keyboardLayoutPtr, aKeyCode,
kUCKeyActionDisplay,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&theDeadKeyState,
sizeof(theCharacter)/sizeof(*theCharacter),
&theLength,
theCharacter ) == noErr )
{
theChar = theCharacter[0];
}
}
else
theChar = theEntry->character;
return toupper(theChar);
}
- (UInt16)keyCodeForCharacter:(unichar)aCharacter { return [self keyCodeForCharacter:aCharacter numericPad:NO]; }
- (UInt16)keyCodeForCharacter:(unichar)aCharacter numericPad:(BOOL)aNumericPad
{
struct ReverseMappingEntry theSearchValue = { tolower(aCharacter), aNumericPad, 0 };
struct ReverseMappingEntry * theEntry = NULL;
if( mappings == NULL )
[self generateMappings];
theEntry = _searchreverseMapping( mappings, numberOfMappings, &theSearchValue );
return theEntry ? theEntry->keyCode : '\0';
}
#pragma mark - private
- (const UCKeyboardLayout *)keyboardLayoutPtr { return (const UCKeyboardLayout *)CFDataGetBytePtr(keyboardLayoutData); }
@end