Losing the character code when using the control key

Q: Why is the control key modifying the character code?

A: You really should not care, but if you happen to, there's a solution.

Why you should not care

A modern Mac OS X application should not react to keyboard events but either to commands (when the Command key is down) or to high level Unicode text input, handling the kEventTextInputUnicodeForKeyEvent Carbon Event.

You should not care even if your application is older and still processes keyboard events the old-fashioned way handling the keyDown event.

In both cases, you should just grab the text or characters contained in the event and display them using whichever text rendering you chose, be it TextEdit, ATSUI, MLTE, etc...

That way, if the user presses the "L" key, he will see a "l" being displayed, and if he presses the "L" key in combination with the shift key, he will see a "L" being displayed. The user can press the "L" key with any combination of modifier keys (shift, option (aka alt), and control), and the system will work out its magic to give you the character code which corresponds to the combination of keys being pressed.

Even though usual applications don't need to know which combination of keys was pressed, some applications do and one particular combination will produce an unexpected result.

Back to Top 

Modifier keys expectations

The expectation is that the option and shift keys modify the character code, but the command and control keys do not.

Depending on the age of your code, you may be handling the keyDown event or a Text Input event or, if your code is modern, the kEventTextInputUnicodeForKeyEvent Carbon Event. The rest of this Q&A focuses on the latter.

When you type the key "L", you get the character code 0x6C in one of the parameters of the keyboard event generated by the keystroke.

When you type the same "L" key in combination with the shift key, you get the character code 0x4C in the kEventParamTextInputSendText parameter and, in addition, the kEventParamKeyModifiers parameter of the kEventParamTextInputSendKeyboardEvent original keyboard event has the shiftKeyBit bit on.

Similarly, if you type the "L" key with any combination of the option and shift keys, you also get the correct character code in the kEventParamTextInputSendText parameter with the appropriate bits turned on in the kEventParamKeyModifiers parameter.

But pressing the command key with the "L" key will give you the unmodified character code of 0x6C, and the appropriate bit turned on in the kEventParamKeyModifiers parameter.

Although the expectation is the same for the control key, if you type the "L" key in combination with the control key (and any other modifier keys), then you get the character 0x0C (the PageDown code) instead of 0x6C in the kEventParamTextInputSendText parameter.

Back to Top 

A historical reason

The reason behind this unexpected behavior is simply the support of Terminal-like applications which need to let the user enter codes below 32 (or 0x20, the Space character) and the control key is the convenient way chosen to let users enter those codes. Not all codes have significance or use in modern terminal applications (ie. code 7, aka Bell, obtained by control-G) but some still do such as control-C or control-Z which lets the user escape a mode. Thus the control key "strips" the 2 high bits (in Big Endian mode) of the character, converting the 0x6C in 0x0C.

Back to Top 

A solution

For most developers, as explained above, this behavior is irrelevant to their applications but if it makes sense in an application to handle, for example, a control-L as a control-and-L and not a control-and-PageDown, then you can use the following code (see Listing1) to inspect the relevant parameters of the kEventTextInputUnicodeForKeyEvent event and see how you can determine what was the original keystroke combination:

Listing 1: Inspecting the parameters of the TextInputUnicodeForKey Carbon Event.

pascal OSStatus Handle_TextInputUnicodeForKeyEvent
      (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
  {
  // gettting the Unicode char in the TextInputUnicodeForKey event
  UniChar uc;
  GetEventParameter(inEvent, kEventParamTextInputSendText,
      typeUnicodeText, NULL, sizeof(uc), NULL, &uc);
  printf("in Handle_TextInputUnicodeForKeyEvent, uniChar is %04x=%c", uc, (char)uc);

  // gettting the original keyDown event in the TextInputUnicodeForKey event
  EventRef origEvent;
  GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent,
      typeEventRef, NULL, sizeof(origEvent), NULL, &origEvent);

  // gettting the Unicode char in the original keyDown event
  GetEventParameter(origEvent, kEventParamKeyUnicodes,
      typeUnicodeText, NULL, sizeof(uc), NULL, &uc);
  printf(", in original Keyboard Event, uniChar is %04x=%c", uc, (char)uc);

  // gettting the Mac OS ASCII char in the original keyDown event (may be meaningless)
  unsigned char c;
  GetEventParameter(origEvent, kEventParamKeyMacCharCodes,
      typeChar, NULL, sizeof(c), NULL, &c);
  printf(", mac char is %04x=%c", c, c);

  // gettting the key code in the original keyDown event (may be meaningless)
  UInt32 key;
  GetEventParameter(origEvent, kEventParamKeyCode,
      typeUInt32, NULL, sizeof(key), NULL, &key);
  printf(", key code is %ld", key);

  // gettting the keyboard type in the original keyDown event
  UInt32 keyType = 0;
  GetEventParameter(origEvent, kEventParamKeyboardType,
      typeUInt32, NULL, sizeof(keyType), NULL, &keyType);

  // gettting the key modifiers in the original keyDown event
  UInt32 modifier;
  GetEventParameter(origEvent, kEventParamKeyModifiers,
      typeUInt32, NULL, sizeof(modifier), NULL, &modifier);
  printf(", modifier is %ld", modifier);
  if (modifier & controlKey)
    {
    long smv = GetScriptManagerVariable(smKeyScript);
    Handle uchrHandle = GetResource('uchr', GetScriptVariable(smv, smScriptKeys));
    UInt32 dummy = 0;
    UCKeyTranslate((UCKeyboardLayout*)*uchrHandle, key, kUCKeyActionDisplay,
        (modifier & ~controlKey) >> 8, keyType, kUCKeyTranslateNoDeadKeysMask, &dummy, 1, &dummy, &uc);
    printf(", it's a Control-%c", uc);
    }

  printf("\n");

  return eventNotHandledErr;
  }

After typing L, shift-L, option-L, command-L, control-L, and control-shift-L, you will get the following results (see Listing 2):

Listing 2: Results.

in Handle_TextInputUnicodeForKeyEvent, uniChar is 006c=l,
  in original Keyboard Event, uniChar is 006c=l, mac char is 006c=l, key code is 37, modifier is 0
in Handle_TextInputUnicodeForKeyEvent, uniChar is 004c=L,
  in original Keyboard Event, uniChar is 004c=L, mac char is 004c=L, key code is 37, modifier is 512
in Handle_TextInputUnicodeForKeyEvent, uniChar is 00ac=\254,
  in original Keyboard Event, uniChar is 00ac=\254, mac char is 00c2=\302, key code is 37, modifier is 2048
in Handle_TextInputUnicodeForKeyEvent, uniChar is 006c=l,
  in original Keyboard Event, uniChar is 006c=l, mac char is 006c=l, key code is 37, modifier is 256
in Handle_TextInputUnicodeForKeyEvent, uniChar is 000c=,
  in original Keyboard Event, uniChar is 000c=, mac char is 000c=, key code is 37, modifier is 4096,
  it's a Control-l
in Handle_TextInputUnicodeForKeyEvent, uniChar is 000c=,
  in original Keyboard Event, uniChar is 000c=, mac char is 000c=, key code is 37, modifier is 4608,
  it's a Control-L

Back to Top 

Accessible codes

On US keyboards, users can enter codes 1 to 29 and 31 (control-A to control-Z, control-[, control-\, control-], and control--). Codes 0 and 30 (which would be obtained by control-@ and control-^ are in fact unobtainable since @ and ^ are shifted characters and you get instead a control-2 and control-6 respectively).

On other keyboards, you can still get codes 1 to 26 with control-A to control-Z, but the layout of the other characters ( @, [, \, ], ^, - ) may prevent the obtention of codes 0 and 27 to 31.

Back to Top 

Document Revision History

DateNotes
2006-07-17First Version

Posted: 2006-07-17


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.