Skip to content

Instantly share code, notes, and snippets.

@pilotmoon
Created April 8, 2025 09:36
Show Gist options
  • Save pilotmoon/a5c500aebc7fe24d183d822dc8ca501a to your computer and use it in GitHub Desktop.
Save pilotmoon/a5c500aebc7fe24d183d822dc8ca501a to your computer and use it in GitHub Desktop.
//
// 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
//
// 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