如何将ASCII字符转换为CGKeyCode?

38
我需要一个函数,根据给定的字符返回该字符在当前键盘布局中所对应的CGKeyCode。例如,如果使用美国QWERTY,则针对字母"b"应该返回kVK_ANSI_B,而如果使用Dvorak,则应该返回kVK_ANSI_N
Win32 API 中拥有 VkKeyScan() 函数可实现此功能;X11 中则有 XStringToKeySym() 函数。请问 CG API 是否有类似的函数?
我需要这个函数是为了将其作为参数传递给 CGEventCreateKeyboardEvent()。我已经尝试使用 CGEventKeyboardSetUnicodeString(),但它显然不支持修饰键(而我恰好需要支持修饰键)。

我已经进行了广泛的搜索,但是找不到一个合适的答案。目前我正在使用下面这段代码(在网上找到),它能够工作,但并不十分优雅(而且很难理解如何简化),我希望在生产代码中不使用它:

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

我是否可以使用标准函数(最好)或编写自己的更优雅的方法(几乎肯定)来解决这个问题?


1
@Sneakyness:我不想在代码中硬编码键盘常量。这些常量a.)由用户输入生成,b.)可能是多个键盘布局的结果。 - Michael
@Michael - 你有获取到可工作的键盘记录器版本吗?我正在寻找一个。我尝试了几个Git包,但是它们并没有按预期工作/无法捕获所有按键。或者你有什么建议吗?有任何开源软件包吗?这将非常有帮助。 - Dany
6个回答

29

这就是我最终使用的。更加简洁。

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

3
有人知道如何使用Cocoa而不是Carbon来完成这个吗? - Loknar
6
解决上述崩溃的方法似乎是将“TISCopyCurrentKeyboardInputSource”替换为“TISCopyCurrentKeyboardLayoutInputSource”。即使当前选择的键盘输入源是输入法或模式,该函数也会回退到当前键盘布局。请注意,翻译时尽量保持原意,使语言通俗易懂。 - pkamb
1
以上pkamb的评论修复了提到的崩溃。需要更明显,因为TISGetInputSourceProperty可能返回NULL。不确定如何投赞成票该评论。 - AndyTang
6
我也对这个更新后的(可能是Cocoa)版本很感兴趣。此外,即使使用上面提到的修复方法,这个版本还是会导致我的应用程序出现分段错误,有人有解决方法吗? - Théo Winterhalter
3
这段代码很危险!CGKeyCode code; 太小了,无法容纳 CFDictionaryGetValueIfPresent 返回的值。你应该使用一个大小大于等于 sizeof(void*) 的类型。例如 uintptr_t - user3647854
显示剩余6条评论

7

对于像我这样寻找更加更新的Michael提出的内容的人,这是我最终采用的做法(对我来说,它解决了一些段错误问题,可能是因为垃圾回收器在此版本中起作用)。

第一个函数来自将虚拟键代码转换为Unicode字符串

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

我使用NSNumber获取一个可为空的对象,然后可以将由charToKeyCode(c)返回的值与nil进行比较,并使用(CGKeyCode)[charToKeyCode(c) intValue]来访问该值。

1
你知道有没有不含碳的解决方案吗? - Jan
1
我认为这里存在内存泄漏问题——在 keyCodeToString() 函数中的两个返回语句之前,你需要加上 CFRelease(currentKeyboard); - s3cur3

6
+ (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);
}

以下是来自这里的原始代码:http://ritter.ist.psu.edu/projects/RUI/macosx/rui.c

(Note: 本翻译不包含解释,如需更多帮助,请提供详细信息)

17
这只适用于美国QWERTY键盘……不是最好的解决方案。 - pkamb
1
第10个案例似乎是美元符号($)。 - dalgard

6

我做了一个版本的设计,它不依赖于Carbon例程。

事实上,它更进一步,因为它将任何可键入的字符映射回其虚拟键代码和修改器组合。

- (NSDictionary *)characterToKeyCode:(NSString *)character {
    static NSDictionary * keyMapDict;

    if (keyMapDict == nil) {
        keyMapDict = [self makeKeyMap];
    }

    /*
     The returned dictionary contains entries for the virtual key code and boolen flags
     for modifier keys used for the character.
     */
    return [keyMapDict objectForKey:character];
}

- (NSDictionary *)makeKeyMap {
    CGKeyCode keyCode;
    CGEventRef coreEvent;
    NSEvent * keyEvent;
    NSUInteger modifiers = 0;
    NSMutableDictionary * subDict, * keyMapDict;
    NSString *character;
    BOOL modKeyIsUsed;

    static NSDictionary * modFlagDict;
    static NSArray * modFlags;

    // create dictionary of modifier names and keys. I've used all the modifiers, but I doubt theya re all needed.
    if (modFlagDict == nil) {
        modFlagDict = @{@"option":@(NSEventModifierFlagOption),
                        @"shift": @(NSEventModifierFlagShift),
                        @"function":@(NSEventModifierFlagFunction),
                        @"control": @(NSEventModifierFlagControl),
                        @"command":@(NSEventModifierFlagCommand)};
        modFlags = [modFlagDict allValues];
    }
    keyMapDict = [NSMutableDictionary dictionary];

    // run through 128 base key codes to see what they produce
    for (keyCode = 0; keyCode < 128; ++keyCode) {
        // create dummy NSEvent from a CGEvent for a keypress
        coreEvent = CGEventCreateKeyboardEvent(NULL, keyCode, true);
        keyEvent = [NSEvent eventWithCGEvent:coreEvent];
        CFRelease(coreEvent);

        if (keyEvent.type == NSEventTypeKeyDown) {
            // this do/while loop through every permutation of modifier keys for a given key code
            do {
                subDict = [NSMutableDictionary dictionary];
                // cerate dictionary containing current modifier keys and virtual key code
                for (NSString * key in modFlagDict) {
                    modKeyIsUsed = ([(NSNumber *)[modFlagDict objectForKey:key] unsignedLongValue] & modifiers) ? YES : NO;
                    [subDict setObject:[NSNumber numberWithBool:modKeyIsUsed]
                                forKey:key];
                }
                [subDict setObject:[NSNumber numberWithUnsignedLong:(unsigned long)keyCode]
                            forKey:@"virtKeyCode"];

                // manipulate the NSEvent to get character produce by virtual key code and modifiers
                if (modifiers == 0) {
                    character = [keyEvent characters];
                } else {
                    character = [keyEvent charactersByApplyingModifiers:modifiers];
                }

                // add sub-dictionary to main dictionary using character as key
                if (![keyMapDict objectForKey:character]) {
                    [keyMapDict setObject:[NSDictionary dictionaryWithDictionary:subDict]
                                forKey:character];
                }

                // permutate the modifiers
                modifiers = [self permutatateMods:modFlags];
            } while (modifiers != 0);
        }
    }

    return [NSDictionary dictionaryWithDictionary:keyMapDict];
}

- (NSUInteger)permutatateMods:(NSArray *) modFlags {
    static NSMutableIndexSet * idxSet;
    NSArray * modArray;
    NSEnumerator * e;
    NSNumber * modObj;
    NSUInteger modifiers = 0, idx = 0;

    if (idxSet == nil) {
        idxSet = [NSMutableIndexSet indexSet];
    }

    /*
     Starting at 0, if the index exists, remove it and move up; if the index doesn't exist, add it. Will
     cycle through a standard binary progression. Indexes are then applied to the passed array, and the
     selected elements are 'OR'ed together
     */
    BOOL doneFlag = NO;
    while (!doneFlag) {
        if ([idxSet containsIndex:idx]) {
            [idxSet removeIndex:idx++];
            continue;
        }
        if (idx < [modFlags count]) {
            [idxSet addIndex:idx];
        } else {
            [idxSet removeAllIndexes];
        }
        doneFlag = YES;
    }

    modArray = [modFlags objectsAtIndexes:idxSet];
    e = [modArray objectEnumerator];
    while (modObj = [e nextObject]) {
        modifiers |= [modObj unsignedIntegerValue];
    }

    return modifiers;
}

例如,如果你使用以下代码调用它:
NSDictionary * d = [self characterToKeyCode:@"π"];

它将返回一个读取以下内容的字典:
Dictionary: {
    command = 0;
    control = 0;
    function = 0;
    option = 1;
    shift = 0;
    virtKeyCode = 35;
}

这段话表明,“pi”符号是通过虚拟键码35和选项键(在标准的美式键盘上为⌥P)同时按下所产生的。


5

针对这个问题,似乎有三种类型的键可生成 CGKeyCode:

  1. 普通Unicode字符,例如 A
  2. 修饰键,例如 Shift
  3. 特殊键,例如 F1。

我根据Ted Wrigley的答案进行了改进,为所有三种类型创建了适当的Swift初始化器。现在可以使用CGEvent(keyboardEventSource:virtualKey:keyDown:)的示例编写如下:

guard let shiftKeyCode = CGKeyCode(modifierFlag: .shift) else { fatalError() }
guard let zKeyCode = CGKeyCode(character: "z") else { fatalError() }

let event1 = CGEvent(keyboardEventSource: nil, virtualKey: shiftKeyCode, keyDown: true)
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: zKeyCode, keyDown: true)
let event3 = CGEvent(keyboardEventSource: nil, virtualKey: zKeyCode, keyDown: false)
let event4 = CGEvent(keyboardEventSource: nil, virtualKey: shiftKeyCode, keyDown: false)

//
//  CGKeyCodeInitializers.swift
//
//  Created by John Scott on 09/02/2022.
//

import Foundation
import AppKit

extension CGKeyCode {
    public init?(character: String) {
        if let keyCode = Initializers.shared.characterKeys[character] {
            self = keyCode
        } else {
            return nil
        }
    }

    public init?(modifierFlag: NSEvent.ModifierFlags) {
        if let keyCode = Initializers.shared.modifierFlagKeys[modifierFlag] {
            self = keyCode
        } else {
            return nil
        }
    }
    
    public init?(specialKey: NSEvent.SpecialKey) {
        if let keyCode = Initializers.shared.specialKeys[specialKey] {
            self = keyCode
        } else {
            return nil
        }
    }
    
    private struct Initializers {
        let specialKeys: [NSEvent.SpecialKey:CGKeyCode]
        let characterKeys: [String:CGKeyCode]
        let modifierFlagKeys: [NSEvent.ModifierFlags:CGKeyCode]
        
        static let shared = Initializers()
        
        init() {
            var specialKeys = [NSEvent.SpecialKey:CGKeyCode]()
            var characterKeys = [String:CGKeyCode]()
            var modifierFlagKeys = [NSEvent.ModifierFlags:CGKeyCode]()

            for keyCode in (0..<128).map({ CGKeyCode($0) }) {
                guard let cgevent = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(keyCode), keyDown: true) else { continue }
                guard let nsevent = NSEvent(cgEvent: cgevent) else { continue }

                var hasHandledKeyCode = false
                if nsevent.type == .keyDown {
                    if let specialKey = nsevent.specialKey {
                        hasHandledKeyCode = true
                        specialKeys[specialKey] = keyCode
                    } else if let characters = nsevent.charactersIgnoringModifiers, !characters.isEmpty && characters != "\u{0010}" {
                        hasHandledKeyCode = true
                        characterKeys[characters] = keyCode
                    }
                } else if nsevent.type == .flagsChanged, let modifierFlag = nsevent.modifierFlags.first(.capsLock, .shift, .control, .option, .command, .help, .function) {
                    hasHandledKeyCode = true
                    modifierFlagKeys[modifierFlag] = keyCode
                }
                if !hasHandledKeyCode {
                    #if DEBUG
                    print("Unhandled keycode \(keyCode): \(nsevent)")
                    #endif
                }
            }
            self.specialKeys = specialKeys
            self.characterKeys = characterKeys
            self.modifierFlagKeys = modifierFlagKeys
        }
    }

}

extension NSEvent.ModifierFlags: Hashable { }

extension OptionSet {
    public func first(_ options: Self.Element ...) -> Self.Element? {
        for option in options {
            if contains(option) {
                return option
            }
        }
        return nil
    }
}

4

在Qt下,您自己的解决方案也可以正常工作,只需进行小修补(将其转换为CFDataRef):

替换

CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);

使用

CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);

避免错误:

将 'void*' 无效转换为 'const __CFData*'


2
文档说明(http://developer.apple.com/library/mac/documentation/TextFonts/Reference/TextInputSourcesReference/Reference/reference.html#//apple_ref/doc/c_ref/kTISPropertyUnicodeKeyLayoutData)指出,“kTISPropertyUnicodeKeyLayoutData”的值为“CFDataRef”,因此此处转换是正确的。错误与Qt无关;它是由您的设置(编译器的保护级别)或语言选择引起的(我认为您在C++中更可能遇到此错误,但我可能是错的并且不知道具体细节)。 - Peter Hosey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接