Created
April 8, 2025 09:36
-
-
Save pilotmoon/a5c500aebc7fe24d183d822dc8ca501a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NMKeyConverter.h | |
// NMKit | |
// | |
// Created by Nicholas Moore on 23/12/2011. | |
// Copyright (c) 2011 __MyCompanyName__. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
#include <Carbon/Carbon.h> | |
@interface NMKeyConverter : NSObject | |
@property (readonly) TISInputSourceRef sourceRef; | |
@property (readonly) NSString *keyboardLayoutName; | |
@property (readonly) NSString *inputSourceId; | |
+ (NMKeyConverter *)currentKeyConverter; | |
+ (NMKeyConverter *)qwertyKeyConverter; | |
- (id)initWithInputSourceRef:(TISInputSourceRef) sourceRef; | |
- (NSString *)charForKeyCode:(CGKeyCode)keyCode; | |
- (NSNumber *)keyCodeForChar:(NSString *)character commandDown:(BOOL)commandDown; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NMKeyConverter.m | |
// NMKit | |
// | |
// Created by Nicholas Moore on 23/12/2011. | |
// Copyright (c) 2011 __MyCompanyName__. All rights reserved. | |
// | |
#import "NMKeyConverter.h" | |
#import "NMRegexUtils.h" | |
@interface NMKeyConverter () | |
@property TISInputSourceRef sourceRef; | |
@property NSString *keyboardLayoutName; | |
@property NSData *keyboardLayoutData; | |
@property NSString *inputSourceId; | |
@property NSDictionary *charToKeyCode; | |
@end | |
@implementation NMKeyConverter | |
+ (NMKeyConverter *)currentKeyConverter | |
{ | |
static NMKeyConverter *currentKeyConverter=nil; | |
// get the current input source | |
const TISInputSourceRef sourceRef = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); | |
if (sourceRef) { | |
if (!currentKeyConverter) { | |
currentKeyConverter=[[NMKeyConverter alloc] initWithInputSourceRef:sourceRef]; | |
} | |
else if ([currentKeyConverter sourceRef] != sourceRef) { | |
currentKeyConverter=[[NMKeyConverter alloc] initWithInputSourceRef:sourceRef]; | |
} | |
CFRelease(sourceRef); | |
} | |
return currentKeyConverter; | |
} | |
// get a stable QWERTY layout for when input source is Dvorak - QWERTY ⌘ | |
// https://stackoverflow.com/questions/28184241/tis-services-selecting-czech | |
+ (NMKeyConverter *)qwertyKeyConverter | |
{ | |
static NMKeyConverter *qwertyKeyConverter=nil; | |
if (!qwertyKeyConverter) { | |
NSDictionary* properties = @{ (__bridge NSString*)kTISPropertyInputSourceID: @"com.apple.keylayout.ABC" }; | |
NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)properties, NO)); | |
TISInputSourceRef sourceRef = (__bridge TISInputSourceRef)sources.firstObject; | |
if (sourceRef) { | |
qwertyKeyConverter=[[NMKeyConverter alloc] initWithInputSourceRef:sourceRef]; | |
} | |
} | |
return qwertyKeyConverter; | |
} | |
- (id)initWithInputSourceRef:(TISInputSourceRef) sourceRef | |
{ | |
self=[super init]; | |
if (self) | |
{ | |
if (!sourceRef) { | |
NMLogError(@"No keyboard input source."); | |
return nil; | |
} | |
if (sourceRef) { | |
CFRetain(sourceRef); | |
} | |
self.sourceRef=sourceRef; | |
self.keyboardLayoutName=(__bridge NSString*)TISGetInputSourceProperty(_sourceRef, kTISPropertyLocalizedName); | |
self.inputSourceId=(__bridge NSString*)TISGetInputSourceProperty(_sourceRef, kTISPropertyInputSourceID); | |
self.keyboardLayoutData=(__bridge NSData*)TISGetInputSourceProperty(_sourceRef, kTISPropertyUnicodeKeyLayoutData); | |
if (!self.keyboardLayoutData) { | |
NMLogError(@"No keyboard layout data. Name: %@", _keyboardLayoutName); | |
return nil; | |
} | |
// now build the lookup dictionary | |
const int KEYCODE_COUNT=128; | |
_charToKeyCode=[NSMutableDictionary dictionaryWithCapacity:KEYCODE_COUNT]; | |
for (int i=0; i<KEYCODE_COUNT; i++) { | |
NSString *c=[self charForKeyCode:i]; | |
if (c) { | |
((NSMutableDictionary *)_charToKeyCode)[c] = @(i); | |
} | |
} | |
NMLogFine(@"Created KeyConverter with ID %@ and layout name %@", self.inputSourceId, self.keyboardLayoutName); | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
if (_sourceRef) { | |
CFRelease(_sourceRef); | |
} | |
} | |
- (NSString *)charForKeyCode:(CGKeyCode)keyCode | |
{ | |
NSString *result=nil; | |
const int MAX_STRING_LENGTH=4; | |
UniChar unicodeString[MAX_STRING_LENGTH]; | |
UniCharCount actualStringLength=0; | |
UInt32 deadKeyState=0; | |
UCKeyTranslate((UCKeyboardLayout *)[_keyboardLayoutData bytes], | |
keyCode, | |
kUCKeyActionDisplay, | |
0, | |
LMGetKbdType(), | |
kUCKeyTranslateNoDeadKeysBit, | |
&deadKeyState, | |
MAX_STRING_LENGTH, | |
&actualStringLength, | |
unicodeString); | |
if (actualStringLength==1) { | |
result=[[NSString stringWithCharacters:unicodeString length:1] uppercaseString]; | |
} | |
return result; | |
} | |
- (NSNumber *)keyCodeForChar:(NSString *)character commandDown:(BOOL)commandDown | |
{ | |
if (commandDown&&[self.inputSourceId isEqualToString:@"com.apple.keylayout.DVORAK-QWERTYCMD"]) { | |
return [[NMKeyConverter qwertyKeyConverter] keyCodeForChar:character commandDown:NO]; | |
} | |
return _charToKeyCode[character]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment