Improve NSEvent decoding for non-Latin key mappings

If control (⌃) is down and the system gives us a non-ASCII key string then we will disregard it and instead convert the virtual key code to its ANSI character.
This commit is contained in:
Allan Odgaard
2015-01-18 18:17:33 +07:00
parent 691a6b4086
commit b854201acf

View File

@@ -83,6 +83,117 @@ static bool is_ascii (std::string const& str)
return 0x20 < ch && ch < 0x7F;
}
static char char_for_key_code (CGKeyCode key, bool shift)
{
static std::map<CGKeyCode, char> const RegularMap =
{
{ kVK_ANSI_1, '1' },
{ kVK_ANSI_2, '2' },
{ kVK_ANSI_3, '3' },
{ kVK_ANSI_4, '4' },
{ kVK_ANSI_5, '5' },
{ kVK_ANSI_6, '6' },
{ kVK_ANSI_7, '7' },
{ kVK_ANSI_8, '8' },
{ kVK_ANSI_9, '9' },
{ kVK_ANSI_0, '0' },
{ kVK_ANSI_A, 'a' },
{ kVK_ANSI_B, 'b' },
{ kVK_ANSI_C, 'c' },
{ kVK_ANSI_D, 'd' },
{ kVK_ANSI_E, 'e' },
{ kVK_ANSI_F, 'f' },
{ kVK_ANSI_G, 'g' },
{ kVK_ANSI_H, 'h' },
{ kVK_ANSI_I, 'i' },
{ kVK_ANSI_J, 'j' },
{ kVK_ANSI_K, 'k' },
{ kVK_ANSI_L, 'l' },
{ kVK_ANSI_M, 'm' },
{ kVK_ANSI_N, 'n' },
{ kVK_ANSI_O, 'o' },
{ kVK_ANSI_P, 'p' },
{ kVK_ANSI_Q, 'q' },
{ kVK_ANSI_R, 'r' },
{ kVK_ANSI_S, 's' },
{ kVK_ANSI_T, 't' },
{ kVK_ANSI_U, 'u' },
{ kVK_ANSI_V, 'v' },
{ kVK_ANSI_W, 'w' },
{ kVK_ANSI_X, 'x' },
{ kVK_ANSI_Y, 'y' },
{ kVK_ANSI_Z, 'z' },
{ kVK_ANSI_LeftBracket, '[' },
{ kVK_ANSI_RightBracket, ']' },
{ kVK_ANSI_Slash, '/' },
{ kVK_ANSI_Backslash, '\\' },
{ kVK_ANSI_Comma, ',' },
{ kVK_ANSI_Period, '.' },
{ kVK_ANSI_Minus, '-' },
{ kVK_ANSI_Equal, '=' },
{ kVK_ANSI_Quote, '\'' },
{ kVK_ANSI_Grave, '`' },
{ kVK_ANSI_Semicolon, ';' },
};
static std::map<CGKeyCode, char> const ShiftedMap =
{
{ kVK_ANSI_1, '!' },
{ kVK_ANSI_2, '@' },
{ kVK_ANSI_3, '#' },
{ kVK_ANSI_4, '$' },
{ kVK_ANSI_5, '%' },
{ kVK_ANSI_6, '^' },
{ kVK_ANSI_7, '&' },
{ kVK_ANSI_8, '*' },
{ kVK_ANSI_9, '(' },
{ kVK_ANSI_0, ')' },
{ kVK_ANSI_A, 'A' },
{ kVK_ANSI_B, 'B' },
{ kVK_ANSI_C, 'C' },
{ kVK_ANSI_D, 'D' },
{ kVK_ANSI_E, 'E' },
{ kVK_ANSI_F, 'F' },
{ kVK_ANSI_G, 'G' },
{ kVK_ANSI_H, 'H' },
{ kVK_ANSI_I, 'I' },
{ kVK_ANSI_J, 'J' },
{ kVK_ANSI_K, 'K' },
{ kVK_ANSI_L, 'L' },
{ kVK_ANSI_M, 'M' },
{ kVK_ANSI_N, 'N' },
{ kVK_ANSI_O, 'O' },
{ kVK_ANSI_P, 'P' },
{ kVK_ANSI_Q, 'Q' },
{ kVK_ANSI_R, 'R' },
{ kVK_ANSI_S, 'S' },
{ kVK_ANSI_T, 'T' },
{ kVK_ANSI_U, 'U' },
{ kVK_ANSI_V, 'V' },
{ kVK_ANSI_W, 'W' },
{ kVK_ANSI_X, 'X' },
{ kVK_ANSI_Y, 'Y' },
{ kVK_ANSI_Z, 'Z' },
{ kVK_ANSI_LeftBracket, '{' },
{ kVK_ANSI_RightBracket, '}' },
{ kVK_ANSI_Slash, '?' },
{ kVK_ANSI_Backslash, '|' },
{ kVK_ANSI_Comma, '<' },
{ kVK_ANSI_Period, '>' },
{ kVK_ANSI_Minus, '_' },
{ kVK_ANSI_Equal, '+' },
{ kVK_ANSI_Quote, '"' },
{ kVK_ANSI_Grave, '~' },
{ kVK_ANSI_Semicolon, ':' },
};
auto const& map = shift ? ShiftedMap : RegularMap;
auto const it = map.find(key);
return it != map.end() ? it->second : 0;
}
/*
The “simple” heuristic is the following:
@@ -91,6 +202,8 @@ static bool is_ascii (std::string const& str)
if ⌘ changes key (Qwerty-Dvorak ⌘ hybrid):
if ⌥ is down: treat ⌥ as literal
if ⇧ is down and (changed) key is not a-z: treat ⇧ as literal else if key is a-z: upcase key string
else if ⌃ is down and key string is non-ASCII
ignore keymap and decode virtual key code to get ⌃ + «modifiers» + ASCII key
else
If ⌥ is down and character (with flags & ⌥⇧) is non-ASCII or if ⌥ doesnt affect key string: treat ⌥ as literal
if ⇧ is down and character (with flags & ⌥⇧) is non-ASCII or if ⇧ doesnt affect key string, treat ⇧ as literal else if key is a-z: upcase key string
@@ -139,30 +252,39 @@ std::string to_s (NSEvent* anEvent, bool preserveNumPadFlag)
}
else
{
if(flags & kCGEventFlagMaskAlternate)
char ch;
if((flags & kCGEventFlagMaskControl) && !is_ascii(keyStringNoFlags) && (ch = char_for_key_code(key, flags & kCGEventFlagMaskShift)))
{
std::string const keyStringAlternate = string_for(key, flags & (kCGEventFlagMaskAlternate|kCGEventFlagMaskShift));
if(!is_ascii(keyStringAlternate) || keyStringNoFlags == keyStringAlternate)
{
D(DBF_NSEvent, bug("option (⌥) is literal\n"););
newFlags |= kCGEventFlagMaskAlternate;
flags &= ~kCGEventFlagMaskAlternate;
}
keyString = std::string(1, ch);
newFlags |= flags & kCGEventFlagMaskAlternate;
}
if(flags & kCGEventFlagMaskShift)
else
{
std::string const keyStringShift = string_for(key, flags & (kCGEventFlagMaskAlternate|kCGEventFlagMaskShift));
if(!is_ascii(keyStringShift) || keyStringNoFlags == keyStringShift)
if(flags & kCGEventFlagMaskAlternate)
{
D(DBF_NSEvent, bug("shift (⇧) is literal\n"););
newFlags |= kCGEventFlagMaskShift;
flags &= ~kCGEventFlagMaskShift;
std::string const keyStringAlternate = string_for(key, flags & (kCGEventFlagMaskAlternate|kCGEventFlagMaskShift));
if(!is_ascii(keyStringAlternate) || keyStringNoFlags == keyStringAlternate)
{
D(DBF_NSEvent, bug("option (⌥) is literal\n"););
newFlags |= kCGEventFlagMaskAlternate;
flags &= ~kCGEventFlagMaskAlternate;
}
}
else
if(flags & kCGEventFlagMaskShift)
{
D(DBF_NSEvent, bug("use NSEvents uppercased version\n"););
keyString = keyStringShift;
std::string const keyStringShift = string_for(key, flags & (kCGEventFlagMaskAlternate|kCGEventFlagMaskShift));
if(!is_ascii(keyStringShift) || keyStringNoFlags == keyStringShift)
{
D(DBF_NSEvent, bug("shift (⇧) is literal\n"););
newFlags |= kCGEventFlagMaskShift;
flags &= ~kCGEventFlagMaskShift;
}
else
{
D(DBF_NSEvent, bug("use NSEvents uppercased version\n"););
keyString = keyStringShift;
}
}
}
}