How to convert ASCII character to CGKeyCode?
I need a function that, given a character, returns the CGKeyCode
associated with the position of that character on the current keyboard layout. E.g., given "b", it should return kVK_ANSI_B
if using U.S. QWERTY, or kVK_ANSI_N
if using Dvorak.
The Win32 API has the function VkKeyScan()
for this purpose; X11 has the function XStringToKeySym()
. Is there such a function in the CG API?
I need this in order to pass a parameter to CGEventCreateKeyboardEvent()
. I've tried using CGEventKeyboardSetUnicodeString()
instead, but that apparently does not support modifier flags (which I need).
I have searched extensively for this but cannot find a decent answer. Currently I am using the following code (found online), which works, but is not exactly elegant (and rather difficult to decipher how to simplify) and I would prefer not to use it in production code:
#include <stdint.h>
#include <stdio.h>
#include <ApplicationServices/ApplicationServices.h>
CGKeyCode keyCodeForCharWithLayout(const char c,
const UCKeyboardLayout *uchrHeader);
CGKeyCode keyCodeForChar(const char c)
{
CFDataRef currentLayoutData;
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
if (currentKeyboard == NULL) {
fputs("Could not find keyboard layout\n", stderr);
return UINT16_MAX;
}
currentLayoutData = TISGetInputSourceProperty(currentKeyboard,
kTISPropertyUnicodeKeyLayoutData);
CFRelease(currentKeyboard);
if (currentLayoutData == NULL) {
fputs("Could not find layout data\n", stderr);
return UINT16_MAX;
}
return keyCodeForCharWithLayout(c,
(const UCKeyboardLayout *)CFDataGetBytePtr(currentLayoutData));
}
/* Beware! Messy, incomprehensible code ahead!
* TODO: XXX: FIXME! Please! */
CGKeyCode keyCodeForCharWithLayout(const char c,
const UCKeyboardLayout *uchrHeader)
{
uint8_t *uchrData = (uint8_t *)uchrHeader;
UCKeyboardTypeHeader *uchrKeyboardList = uchrHeader->keyboardTypeList;
/* Loop through the keyboard type list. */
ItemCount i, j;
for (i = 0; i < uchrHeader->keyboardTypeCount; ++i) {
/* Get a pointer to the keyToCharTable structure. */
UCKeyToCharTableIndex *uchrKeyIX = (UCKeyToCharTableIndex *)
(uchrData + (uchrKeyboardList[i].keyToCharTableIndexOffset));
/* Not sure what this is for but it appears to be a safeguard... */
UCKeyStateRecordsIndex *stateRecordsIndex;
if (uchrKeyboardList[i].keyStateRecordsIndexOffset != 0) {
stateRecordsIndex = (UCKeyStateRecordsIndex *)
(uchrData + (uchrKeyboardList[i].keyStateRecordsIndexOffset));
if ((stateRecordsIndex->keyStateRecordsIndexFormat) !=
kUCKeyStateRecordsIndexFormat) {
stateRecordsIndex = NULL;
}
} else {
stateRecordsIndex = NULL;
}
/* Make sure structure is a table that can be searched. */
if ((uchrKeyIX->keyToCharTableIndexFormat) != kUCKeyToCharTableIndexFormat) {
continue;
}
/* Check the table of each keyboard for character */
for (j = 0; j < uchrKeyIX->keyToCharTableCount; ++j) {
UCKeyOutput *keyToCharData =
(UCKeyOutput *)(uchrData + (uchrKeyIX->keyToCharTableOffsets[j]));
/* Check THIS table of the keyboard for the character. */
UInt16 k;
for (k = 0; k < uchrKeyIX->keyToCharTableSize; ++k) {
/* Here's the strange safeguard again... */
if ((keyToCharData[k] & kUCKeyOutputTestForIndexMask) ==
kUCKeyOutputStateIndexMask) {
long keyIndex = (keyToCharData[k] & kUCKeyOutputGetIndexMask);
if (stateRecordsIndex != NULL &&
keyIndex <= (stateRecordsIndex->keyStateRecordCount)) {
UCKeyStateRecord *stateRecord = (UCKeyStateRecord *)
(uchrData +
(stateRecordsIndex->keyStateRecordOffsets[keyIndex]));
if ((stateRecord->stateZeroCharData) == c) {
return (CGKeyCode)k;
}
} else if (keyToCharData[k] == c) {
return (CGKeyCode)k;
}
} else if (((keyToCharData[k] & kUCKeyOutputTestForIndexMask)
!= kUCKeyOutputSequenceIndexMask) &&
keyToCharData[k] != 0xFFFE &&
keyToCharData[k] != 0xFFFF &&
keyToCharData[k] == c) {
return (CGKeyCode)k;
}
}
}
}
return UINT16_MAX;
}
Is there a.) (preferably) a standard function I am overlooking, or b.) (almost certainly) a more elegant way write my own?
Solution 1:
This is what I ended up using. Much cleaner.
#include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */
/* Returns string representation of key, if it is printable.
* Ownership follows the Create Rule; that is, it is the caller's
* responsibility to release the returned object. */
CFStringRef createStringForKey(CGKeyCode keyCode)
{
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef layoutData =
TISGetInputSourceProperty(currentKeyboard,
kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout *keyboardLayout =
(const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
UInt32 keysDown = 0;
UniChar chars[4];
UniCharCount realLength;
UCKeyTranslate(keyboardLayout,
keyCode,
kUCKeyActionDisplay,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&keysDown,
sizeof(chars) / sizeof(chars[0]),
&realLength,
chars);
CFRelease(currentKeyboard);
return CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1);
}
/* Returns key code for given character via the above function, or UINT16_MAX
* on error. */
CGKeyCode keyCodeForChar(const char c)
{
static CFMutableDictionaryRef charToCodeDict = NULL;
CGKeyCode code;
UniChar character = c;
CFStringRef charStr = NULL;
/* Generate table of keycodes and characters. */
if (charToCodeDict == NULL) {
size_t i;
charToCodeDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
128,
&kCFCopyStringDictionaryKeyCallBacks,
NULL);
if (charToCodeDict == NULL) return UINT16_MAX;
/* Loop through every keycode (0 - 127) to find its current mapping. */
for (i = 0; i < 128; ++i) {
CFStringRef string = createStringForKey((CGKeyCode)i);
if (string != NULL) {
CFDictionaryAddValue(charToCodeDict, string, (const void *)i);
CFRelease(string);
}
}
}
charStr = CFStringCreateWithCharacters(kCFAllocatorDefault, &character, 1);
/* Our values may be NULL (0), so we need to use this function. */
if (!CFDictionaryGetValueIfPresent(charToCodeDict, charStr,
(const void **)&code)) {
code = UINT16_MAX;
}
CFRelease(charStr);
return code;
}
Solution 2:
For those like me who searched for a more up-to-date version of what Michael proposed, here is what I ended up doing myself (for me it solved some segfault issue, probably because the garbage collector is doing its job with this version).
The first function comes from Convert Virtual Key Code to unicode string.
NSString* keyCodeToString(CGKeyCode keyCode)
{
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr =
(CFDataRef)TISGetInputSourceProperty(currentKeyboard,
kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout *keyboardLayout =
(const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if(keyboardLayout)
{
UInt32 deadKeyState = 0;
UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
OSStatus status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, 0,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if (actualStringLength == 0 && deadKeyState)
{
status = UCKeyTranslate(keyboardLayout,
kVK_Space, kUCKeyActionDown, 0,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
}
if(actualStringLength > 0 && status == noErr)
return [[NSString stringWithCharacters:unicodeString
length:(NSUInteger)actualStringLength] lowercaseString];
}
return nil;
}
NSNumber* charToKeyCode(const char c)
{
static NSMutableDictionary* dict = nil;
if (dict == nil)
{
dict = [NSMutableDictionary dictionary];
// For every keyCode
size_t i;
for (i = 0; i < 128; ++i)
{
NSString* str = keyCodeToString((CGKeyCode)i);
if(str != nil && ![str isEqualToString:@""])
{
[dict setObject:[NSNumber numberWithInt:i] forKey:str];
}
}
}
NSString * keyChar = [NSString stringWithFormat:@"%c" , c];
return [dict objectForKey:keyChar];
}
I used NSNumber
to get a nullable object, the value returned by charToKeyCode(c)
can then be tested against nil
and then accessed with (CGKeyCode)[charToKeyCode(c) intValue]
.
Solution 3:
+ (NSString *)keyStringFormKeyCode:(CGKeyCode)keyCode
{
// Proper key detection seems to want a switch statement, unfortunately
switch (keyCode)
{
case 0: return @"a";
case 1: return @"s";
case 2: return @"d";
case 3: return @"f";
case 4: return @"h";
case 5: return @"g";
case 6: return @"z";
case 7: return @"x";
case 8: return @"c";
case 9: return @"v";
// what is 10?
case 11: return @"b";
case 12: return @"q";
case 13: return @"w";
case 14: return @"e";
case 15: return @"r";
case 16: return @"y";
case 17: return @"t";
case 18: return @"1";
case 19: return @"2";
case 20: return @"3";
case 21: return @"4";
case 22: return @"6";
case 23: return @"5";
case 24: return @"=";
case 25: return @"9";
case 26: return @"7";
case 27: return @"-";
case 28: return @"8";
case 29: return @"0";
case 30: return @"]";
case 31: return @"o";
case 32: return @"u";
case 33: return @"[";
case 34: return @"i";
case 35: return @"p";
case 36: return @"RETURN";
case 37: return @"l";
case 38: return @"j";
case 39: return @"'";
case 40: return @"k";
case 41: return @";";
case 42: return @"\\";
case 43: return @",";
case 44: return @"/";
case 45: return @"n";
case 46: return @"m";
case 47: return @".";
case 48: return @"TAB";
case 49: return @"SPACE";
case 50: return @"`";
case 51: return @"DELETE";
case 52: return @"ENTER";
case 53: return @"ESCAPE";
// some more missing codes abound, reserved I presume, but it would
// have been helpful for Apple to have a document with them all listed
case 65: return @".";
case 67: return @"*";
case 69: return @"+";
case 71: return @"CLEAR";
case 75: return @"/";
case 76: return @"ENTER"; // numberpad on full kbd
case 78: return @"-";
case 81: return @"=";
case 82: return @"0";
case 83: return @"1";
case 84: return @"2";
case 85: return @"3";
case 86: return @"4";
case 87: return @"5";
case 88: return @"6";
case 89: return @"7";
case 91: return @"8";
case 92: return @"9";
case 96: return @"F5";
case 97: return @"F6";
case 98: return @"F7";
case 99: return @"F3";
case 100: return @"F8";
case 101: return @"F9";
case 103: return @"F11";
case 105: return @"F13";
case 107: return @"F14";
case 109: return @"F10";
case 111: return @"F12";
case 113: return @"F15";
case 114: return @"HELP";
case 115: return @"HOME";
case 116: return @"PGUP";
case 117: return @"DELETE"; // full keyboard right side numberpad
case 118: return @"F4";
case 119: return @"END";
case 120: return @"F2";
case 121: return @"PGDN";
case 122: return @"F1";
case 123: return @"LEFT";
case 124: return @"RIGHT";
case 125: return @"DOWN";
case 126: return @"UP";
default:
return @"Unknown key";
// Unknown key, bail and note that RUI needs improvement
//fprintf(stderr, "%ld\tKey\t%c (DEBUG: %d)\n", currenttime, keyCode;
//exit(EXIT_FAILURE;
}
}
+ (CGKeyCode)keyCodeFormKeyString:(NSString *)keyString
{
if ([keyString isEqualToString:@"a"]) return 0;
if ([keyString isEqualToString:@"s"]) return 1;
if ([keyString isEqualToString:@"d"]) return 2;
if ([keyString isEqualToString:@"f"]) return 3;
if ([keyString isEqualToString:@"h"]) return 4;
if ([keyString isEqualToString:@"g"]) return 5;
if ([keyString isEqualToString:@"z"]) return 6;
if ([keyString isEqualToString:@"x"]) return 7;
if ([keyString isEqualToString:@"c"]) return 8;
if ([keyString isEqualToString:@"v"]) return 9;
// what is 10?
if ([keyString isEqualToString:@"b"]) return 11;
if ([keyString isEqualToString:@"q"]) return 12;
if ([keyString isEqualToString:@"w"]) return 13;
if ([keyString isEqualToString:@"e"]) return 14;
if ([keyString isEqualToString:@"r"]) return 15;
if ([keyString isEqualToString:@"y"]) return 16;
if ([keyString isEqualToString:@"t"]) return 17;
if ([keyString isEqualToString:@"1"]) return 18;
if ([keyString isEqualToString:@"2"]) return 19;
if ([keyString isEqualToString:@"3"]) return 20;
if ([keyString isEqualToString:@"4"]) return 21;
if ([keyString isEqualToString:@"6"]) return 22;
if ([keyString isEqualToString:@"5"]) return 23;
if ([keyString isEqualToString:@"="]) return 24;
if ([keyString isEqualToString:@"9"]) return 25;
if ([keyString isEqualToString:@"7"]) return 26;
if ([keyString isEqualToString:@"-"]) return 27;
if ([keyString isEqualToString:@"8"]) return 28;
if ([keyString isEqualToString:@"0"]) return 29;
if ([keyString isEqualToString:@"]"]) return 30;
if ([keyString isEqualToString:@"o"]) return 31;
if ([keyString isEqualToString:@"u"]) return 32;
if ([keyString isEqualToString:@"["]) return 33;
if ([keyString isEqualToString:@"i"]) return 34;
if ([keyString isEqualToString:@"p"]) return 35;
if ([keyString isEqualToString:@"RETURN"]) return 36;
if ([keyString isEqualToString:@"l"]) return 37;
if ([keyString isEqualToString:@"j"]) return 38;
if ([keyString isEqualToString:@"'"]) return 39;
if ([keyString isEqualToString:@"k"]) return 40;
if ([keyString isEqualToString:@";"]) return 41;
if ([keyString isEqualToString:@"\\"]) return 42;
if ([keyString isEqualToString:@","]) return 43;
if ([keyString isEqualToString:@"/"]) return 44;
if ([keyString isEqualToString:@"n"]) return 45;
if ([keyString isEqualToString:@"m"]) return 46;
if ([keyString isEqualToString:@"."]) return 47;
if ([keyString isEqualToString:@"TAB"]) return 48;
if ([keyString isEqualToString:@"SPACE"]) return 49;
if ([keyString isEqualToString:@"`"]) return 50;
if ([keyString isEqualToString:@"DELETE"]) return 51;
if ([keyString isEqualToString:@"ENTER"]) return 52;
if ([keyString isEqualToString:@"ESCAPE"]) return 53;
// some more missing codes abound, reserved I presume, but it would
// have been helpful for Apple to have a document with them all listed
if ([keyString isEqualToString:@"."]) return 65;
if ([keyString isEqualToString:@"*"]) return 67;
if ([keyString isEqualToString:@"+"]) return 69;
if ([keyString isEqualToString:@"CLEAR"]) return 71;
if ([keyString isEqualToString:@"/"]) return 75;
if ([keyString isEqualToString:@"ENTER"]) return 76; // numberpad on full kbd
if ([keyString isEqualToString:@"="]) return 78;
if ([keyString isEqualToString:@"="]) return 81;
if ([keyString isEqualToString:@"0"]) return 82;
if ([keyString isEqualToString:@"1"]) return 83;
if ([keyString isEqualToString:@"2"]) return 84;
if ([keyString isEqualToString:@"3"]) return 85;
if ([keyString isEqualToString:@"4"]) return 86;
if ([keyString isEqualToString:@"5"]) return 87;
if ([keyString isEqualToString:@"6"]) return 88;
if ([keyString isEqualToString:@"7"]) return 89;
if ([keyString isEqualToString:@"8"]) return 91;
if ([keyString isEqualToString:@"9"]) return 92;
if ([keyString isEqualToString:@"F5"]) return 96;
if ([keyString isEqualToString:@"F6"]) return 97;
if ([keyString isEqualToString:@"F7"]) return 98;
if ([keyString isEqualToString:@"F3"]) return 99;
if ([keyString isEqualToString:@"F8"]) return 100;
if ([keyString isEqualToString:@"F9"]) return 101;
if ([keyString isEqualToString:@"F11"]) return 103;
if ([keyString isEqualToString:@"F13"]) return 105;
if ([keyString isEqualToString:@"F14"]) return 107;
if ([keyString isEqualToString:@"F10"]) return 109;
if ([keyString isEqualToString:@"F12"]) return 111;
if ([keyString isEqualToString:@"F15"]) return 113;
if ([keyString isEqualToString:@"HELP"]) return 114;
if ([keyString isEqualToString:@"HOME"]) return 115;
if ([keyString isEqualToString:@"PGUP"]) return 116;
if ([keyString isEqualToString:@"DELETE"]) return 117;
if ([keyString isEqualToString:@"F4"]) return 118;
if ([keyString isEqualToString:@"END"]) return 119;
if ([keyString isEqualToString:@"F2"]) return 120;
if ([keyString isEqualToString:@"PGDN"]) return 121;
if ([keyString isEqualToString:@"F1"]) return 122;
if ([keyString isEqualToString:@"LEFT"]) return 123;
if ([keyString isEqualToString:@"RIGHT"]) return 124;
if ([keyString isEqualToString:@"DOWN"]) return 125;
if ([keyString isEqualToString:@"UP"]) return 126;
return 0;
//fprintf(stderr, "keyString %s Not Found. Aborting...\n", keyString);
//exit(EXIT_FAILURE);
}
original code from here: http://ritter.ist.psu.edu/projects/RUI/macosx/rui.c