How to convert ASCII character to CGKeyCode?

c, keycode, keypress, macos, quartz-graphics

Solution

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;
}

Problem

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?

Original source

Related problems