cmacosquartz-graphicskeypresskeycode

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

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