jlstrecker
jlstrecker

Reputation: 5033

How to capture Unicode from key events without an NSTextView

I'd like to capture key events from any window in the application and interpret them as Unicode. For example, if the user types Option-e-e (on a default-configured US English keyboard), I would like to recognize that as "é".

I tried capturing keypress events and calling -[NSEvent characters]. However, as it says in the documentation, "This method returns an empty string for dead keys, such as Option-e." If I type Option-e-e, then it gives me nothing for the Option-e and plain "e" for the second e.

Is there a way to combine a series of keycodes (from -[NSEvent keyCode]) into a Unicode character?

Or a way to receive an event for each Unicode character typed (like Java's key-typed event)?

Upvotes: 7

Views: 2307

Answers (2)

jlstrecker
jlstrecker

Reputation: 5033

Here's a way to take a series of key press events and get the Unicode character(s) they'd type.

Basically, call UCKeyTranslate() for each key press event received. Use its deadKeyState argument to capture a dead key and pass it along to the subsequent call.

Example:

  • Receive key press event for Option-e.
  • Call UCKeyTranslate() with the virtual key code (for e), the modifier key state (for Option), and a variable to store the dead key state.
    • UCKeyTranslate() outputs an empty string and updates the dead key state.
  • Receive key press event for e.
  • Call UCKeyTranlate() with the virtual key code (for e) and the variable that holds the dead key state.
    • UCKeyTranslate() outputs "é".

Sample code (the function to call for each key press event):

/**
 * Returns the Unicode characters that would be typed by a key press.
 *
 * @param event A key press event.
 * @param deadKeyState To capture multi-keystroke characters (e.g. Option-E-E for "é"), pass a reference to the same
 *      variable on consecutive calls to this function. Before the first call, you should initialize the variable to 0.
 * @return One or more Unicode characters.
 */
CFStringRef getCharactersForKeyPress(NSEvent *event, UInt32 *deadKeyState)
{
    // http://stackoverflow.com/questions/12547007/convert-key-code-into-key-equivalent-string
    // http://stackoverflow.com/questions/8263618/convert-virtual-key-code-to-unicode-string

    TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
    CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
    const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);

    CGEventFlags flags = [event modifierFlags];
    UInt32 modifierKeyState = (flags >> 16) & 0xFF;

    const size_t unicodeStringLength = 4;
    UniChar unicodeString[unicodeStringLength];
    UniCharCount realLength;

    UCKeyTranslate(keyboardLayout,
                   [event keyCode],
                   kUCKeyActionDown,
                   modifierKeyState,
                   LMGetKbdType(),
                   0,
                   deadKeyState,
                   unicodeStringLength,
                   &realLength,
                   unicodeString);
    CFRelease(currentKeyboard);
    return CFStringCreateWithCharacters(kCFAllocatorDefault, unicodeString, realLength);
}

Upvotes: 13

Zag
Zag

Reputation: 819

subclass the view/window you want to capture the "é" event in and add this instance variable

BOOL optionE_Pressed;

then, override keyDown: with this

-(void) keyDown:(NSEvent *)theEvent {

    NSString *chars = theEvent.charactersIgnoringModifiers;
    unichar aChar = [chars characterAtIndex: 0];

    if (aChar==101 && [theEvent modifierFlags]&NSAlternateKeyMask) {
        optionE_Pressed=YES;
    }
    else if (aChar==101 && optionE_Pressed) {
        NSLog(@"spanish é pressed");
    }
    else {
        optionE_Pressed=NO;
    }

    [super keyDown:theEvent];
}

The Boolean variable "optionE_Pressed" is activated when the user holds down the option and e keys. If the next key that is pressed is e, meaning that they have effectively created a spanish é, then it will log "spanish é pressed." Otherwise, the Boolien is switched back to NO. The "super" call at the end allows the user to still be able to type all events as normal

Upvotes: 0

Related Questions