SIGPIPE 13

Deciphering an NSEvent

September 24th, 2005

Since I need to handle key equivalents myself, I need to mimic how the system does it.

After having spent some time investigating this, it seems to be impossible to do with the info provided by NSEvent, but I ended up creating a heuristic which works fairly well (but isn't perfect).

First let me briefly describe the problem. If you look in e.g. the menu of Xcode, you'll find that shift left is bound to ⌘[. NSEvent gives us 3 informations:

  1. The modifiers pressed.
  2. The string resulting from the key press.
  3. The string (characters) ignoring modifier keys.

So what should we look at? Using the US keymap this doesn't matter, since the string with and without modifiers is [. And we should of course check the modifiers for the ⌘ modifier key.

But if we're using the Danish keymap (or one of the others where [ is not a primary key) the picture is a little different. The [ character is obtained by pressing ⌥8. So to shift left, we press ⌥⌘8.

In this case we'd use the string (with modifiers) and ignore that the ⌥ key is set among the modifiers. But had the user instead pressed ⌥⌘R then the string (with modifiers) would be ®, which is clearly not what we want. So in that case we actually want the string ignoring the modifiers, and then look at all the modifiers.

After looking at a lot of key events I found that the only way to resolve this is, to use the string with modifiers, only if that string is an ASCII character. Additionally unset the ⌥ key from the modifiers, if we go for the string with modifiers and that string is different from the string ignoring the modifiers.

For your information, here is a table showing the information given when typing 8 on US and DK keymap with the miscellaneous modifiers:

Key PressUSDK
88888
⇧8**((
⌥88[8
⌥⇧8°*{(
⌘88888
⇧⌘88*8(
⌥⌘88[8
⌥⇧⌘8°*{(

There are however more problems, I'm ignoring here that ⌃ is another modifier which can also change the string (with modifiers) and that both ⌥ and ⌃ can be set, cause it gets worse.

There's a special Dvorak – Qwerty hybrid keymap. It works by letting the normal layout be Dvorak, but if you hold down the ⌘ key, the layout changes to Qwerty. That would mean that the string with modifiers should be different from that without modifiers, when the ⌘ key is pressed, and both strings should be ASCII, which sort of ruins our ⌥ heuristic from above.

It turns out that we don't have to worry about it, cause NSEvent is simply not up to handling this hybrid keymap. On Dvorak the S key gives an O, so if we try to press the (Qwerty) S with the miscellaneous modifiers, we should get variants of O when ⌘ is not pressed, otherwise we should get variants of S, but here is what we actually get (the second table shows how the A key behaves on plain Qwerty / US):

Key PressDQ
Soo
⇧SOO
⌥Søo
⌥⇧SØO
⌘Sso
⇧⌘SsO
⌥⌘Sßo
⌥⇧⌘SÍO
Key PressUS
Aaa
⇧AAA
⌥Aåa
⌥⇧AÅA
⌘Aaa
⇧⌘AaA
⌥⌘Aåa
⌥⇧⌘AÅA

From these tables it's clear that NSEvent is not up to the job of deciphering a key event to the “menu description” of that event (notice that by menu key I do not mean that the ⌘ modifier should necessary be pressed, e.g. I also need this for the general Cocoa key bindings system).

There are actual several additional problems I have neglected to mention, since I actually just wanted to put online the heuristic I ended up writing. So without further ado, here's the actual code:

// given anEvent (NSEvent*) figure out what key 
// and modifiers we actually want to look at, 
// to compare it with a menu key description

unsigned int quals = [anEvent modifierFlags];

NSString* str = [anEvent characters];
NSString* strWithout = [anEvent charactersIgnoringModifiers];

unichar ch = [str length] ? [str characterAtIndex:0] : 0;
unichar without = [strWithout length] ? [strWithout characterAtIndex:0] : 0;

if(!(quals & NSNumericPadKeyMask))
{
   if((quals & NSControlKeyMask))
   {
      ch = without;
   }
   else if(quals & NSAlternateKeyMask)
   {
      if(0x20 < ch && ch < 0x7f && ch != without)
            quals &= ~NSAlternateKeyMask;
      else  ch = without;
   }
   else if((quals & (NSCommandKeyMask | NSShiftKeyMask)) == (NSCommandKeyMask | NSShiftKeyMask))
   {
      ch = without;
   }

   if((0x20 < ch && ch < 0x7f) || ch == 0x19)
      quals &= ~NSShiftKeyMask;
}

// the resulting values
key = ch;
modifiers = quals & (NSNumericPadKeyMask | NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask);

This works fairly well with Western keymaps, it probably fails miserably with non-Western keymaps. And for the Dvorak – Qwerty hybrid, it only handles these when ⌘ is the only modifier set (but look at the table above, there's really no way to handle more).

[by Allan Odgaard]


5 Responses to “Deciphering an NSEvent”

  1. rentzsch Says:
    September 24th, 2005 at 07:00

    You have another piece of information at the NSEvent-level: the virtual key code of the actual pressed key (-[NSEvent keyCode]). They're somewhat esoteric (they almost harken back to the original Macintosh), and from reading the problem description I'm not altogether sure they'd actually help you, but they're there.

  2. Benjamin Stiglitz Says:
    March 27th, 2006 at 22:45

    You can use the InterpretKeyEvents API to figure out what key was actually pressed.

  3. Allan Odgaard Says:
    April 20th, 2006 at 04:33

    Benjamin: The interpretKeyEvents will translate an event to an action based on the key bindings, but that is not what I want.

    I have my own map of key bindings (which the user created in my application) and I need to compare the NSEvent with these, to figure out which one the event matches.

  4. Noitidart Says:
    October 11th, 2015 at 06:50

    Is deciphering keycode still an issue today? Any improvments on ease? Like for exapmle, if added a local monitor to try to catch os media keys but can't decipher them too well.

  5. Allan Odgaard Says:
    October 14th, 2015 at 12:52

    If Apple has added new API to improve things then I have not found it.

    The code in this blog post actually fails to handle non-Latin key maps. For example pressing control-ф on a Russian keyboard should be interpreted as control-A, similarly to how command-ф is interpreted as command-A, although using the Keyboard Viewer and holding command down, you will see the keys change, but that does not happen with control.

    You can see my fix for non-Latin key maps here.


Leave a Reply