2009-12-17 4 views
25

Мне нужна функция, которая при задании символа возвращает CGKeyCode, связанный с положением этого символа в текущей раскладке клавиатуры. Например, учитывая «b», он должен возвращать kVK_ANSI_B, если используется U.S. QWERTY, или kVK_ANSI_N при использовании Dvorak.Как преобразовать символ ASCII в CGKeyCode?

Для этой цели API Win32 имеет функцию VkKeyScan(); X11 имеет функцию XStringToKeySym(). Есть ли такая функция в CG API?

Мне нужно это, чтобы передать параметр CGEventCreateKeyboardEvent(). Я пробовал использовать CGEventKeyboardSetUnicodeString() вместо этого, но, видимо, does not support флаги-модификаторы (которые мне нужны).

Я много искал для этого, но не могу найти достойный ответ. В настоящее время я использую следующий код (found online), который работает, но это не совсем элегантно (и довольно трудно расшифровать, как упростить), и я предпочел бы не использовать его в производстве код:

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

Есть a.) (желательно) стандартную функцию, которую я пропускаю, или б). (почти наверняка) более элегантный способ написать мой собственный?

+0

http://www.manytricks.com/keycodes/ – Sneakyness

+1

@Sneakyness: я не хочу жестко кодировать константы ключа. Они генерируются пользовательским вводом, а b.) Может быть результатом множественного раскладки клавиатуры. – Michael

+0

@Michael - Вы получили рабочую версию кейлоггера. Я ищу его. Я попробовал несколько пакетов git. Но не работает как ожидалось/не фиксирует все нажатия клавиш. Или у вас есть предложения по этому поводу? любые пакеты с открытым исходным кодом?Было бы очень полезно – Dany

ответ

27

Это то, что я в конечном итоге использовал. Гораздо чище.

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

Это не работает для непечатаемых ключей, правда? У меня точно такая же проблема - мне нужно моделировать нажатия клавиш, учитывая нажатые символы и клавиши-модификаторы. Есть идеи? – Thomi

+1

Для непечатаемых ключей я использовал константы кода ключа, определенные в разделе (в Carbon). Например, kVK_Delete для ключа удаления, kVK_F # для функциональных клавиш и т. Д.). Это единственный метод, который я мог бы найти, что сработает. – Michael

+0

Я попробовал kVK_Command с указанным выше ключом ключа команды, но не смог заставить его работать. Техника в этом вопросе с 'CGEventSetFlags' работала для меня, хотя! http://stackoverflow.com/questions/4705748/send-a-keyboard-shortcut-to-a-mac-os-x-window/4706181#4706181 –

4

Ваше собственное решение отлично работает под Qt тоже после небольшого участка (литье в CFDataRef):

Замены

CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 

с

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

позволяет избежать ошибок:

invalid conversion from 'void*' to '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++, но я могу ошибаться и не знаю специфики) , –

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

+5

Это будет работать только для США QWERTY ... не лучшее решение. – pkamb

+3

+1 для хардкора – fnc12

2

Для тех, кто, как я искал более уточненный версия о том, что Майкл предложил, вот что я в конечном итоге делает сам (для меня это решило проблему segfault, вероятно, потому, что сборщик мусора выполняет свою работу с этой версией).

Первая функция исходит от 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]; 
} 

Я использовал NSNumber чтобы получить обнуляемый объект, значение, возвращаемое charToKeyCode(c) может быть затем испытана против nil, а затем доступа с (CGKeyCode)[charToKeyCode(c) intValue].

Смежные вопросы