openMSX
SDLKey.cc
Go to the documentation of this file.
1#include "SDLKey.hh"
2#include "StringOp.hh"
3#include "one_of.hh"
4#include "ranges.hh"
5#include <array>
6
7namespace openmsx {
8
9// We prefer to use 'SDL_GetKeyFromName()' to translate a name into a Keycode.
10// That way when new keys are added to SDL2, these immediately work in openMSX.
11// In the past we had our own mapping, and to keep backwards compatible (e.g.
12// because key-names can end up in settings.xml) we retain (part of) that old
13// mapping.
14static SDL_Keycode getKeyFromOldOpenmsxName(std::string_view name)
15{
16 // These (old) openMSX names are the same in SDL2. Except for
17 // capitalization (e.g. "PAGEUP" vs "PageUp"), but we anyway
18 // do a case-insensitive search.
19 // "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
20 // "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
21 // "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
22 // "RETURN", "ESCAPE", "BACKSPACE", "TAB", "SPACE", "CAPSLOCK",
23 // "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9",
24 // "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17",
25 // "F18", "F19", "F20", "F21", "F22", "F23", "F24",
26 // "SCROLLLOCK", "PAUSE", "INSERT", "HOME", "PAGEUP", "DELETE",
27 // "END", "PAGEDOWN", "RIGHT", "LEFT", "DOWN", "UP", "NUMLOCK",
28 // "POWER", "HELP", "MENU", "UNDO", "SYSREQ", "CLEAR",
29
30 // For these (old) openMSX names it's not clear which SDL2 keycode
31 // corresponds to it. So, at least for now, these are no longer
32 // supported.
33 // "BACK", "ZENKAKU_HENKAKU", "MUHENKAN", "HENKAN_MODE", "HIRAGANA_KATAKANA",
34
35 struct M {
36 std::string_view name;
37 SDL_Keycode code;
38 };
39 static constexpr std::array unsortedMap = {
40 // Old openMSX name, SDL-key-code, new SDL2 name
41 M{"EXCLAIM", SDLK_EXCLAIM}, // !
42 M{"QUOTEDBL", SDLK_QUOTEDBL}, // "
43 M{"HASH", SDLK_HASH}, // #
44 M{"DOLLAR", SDLK_DOLLAR}, // $
45 M{"AMPERSAND", SDLK_AMPERSAND}, // &
46 M{"QUOTE", SDLK_QUOTE}, // '
47 M{"LEFTPAREN", SDLK_LEFTPAREN}, // (
48 M{"RIGHTPAREN", SDLK_RIGHTPAREN}, // )
49 M{"ASTERISK", SDLK_ASTERISK}, // *
50 M{"PLUS", SDLK_PLUS}, // +
51 M{"COMMA", SDLK_COMMA}, // ,
52 M{"MINUS", SDLK_MINUS}, // -
53 M{"PERIOD", SDLK_PERIOD}, // .
54 M{"SLASH", SDLK_SLASH}, // /
55 M{"COLON", SDLK_COLON}, // :
56 M{"SEMICOLON", SDLK_SEMICOLON}, // ;
57 M{"LESS", SDLK_LESS}, // <
58 M{"EQUALS", SDLK_EQUALS}, // =
59 M{"GREATER", SDLK_GREATER}, // >
60 M{"QUESTION", SDLK_QUESTION}, // ?
61 M{"AT", SDLK_AT}, // @
62 M{"LEFTBRACKET", SDLK_LEFTBRACKET}, // [
63 M{"BACKSLASH", SDLK_BACKSLASH}, //
64 M{"RIGHTBRACKET",SDLK_RIGHTBRACKET},// ]
65 M{"CARET", SDLK_CARET}, // ^
66 M{"UNDERSCORE", SDLK_UNDERSCORE}, // _
67 M{"BACKQUOTE", SDLK_BACKQUOTE}, // `
68 M{"PRINT", SDLK_PRINTSCREEN}, // PrintScreen
69 M{"SCROLLOCK", SDLK_SCROLLLOCK}, // ScrollLock backwards compat for typo
70 M{"KP_DIVIDE", SDLK_KP_DIVIDE}, // Keypad /
71 M{"KP_MULTIPLY", SDLK_KP_MULTIPLY}, // Keypad *
72 M{"KP_MINUS", SDLK_KP_MINUS}, // Keypad -
73 M{"KP_PLUS", SDLK_KP_PLUS}, // Keypad +
74 M{"KP_ENTER", SDLK_KP_ENTER}, // Keypad Enter
75 M{"KP0", SDLK_KP_0}, // Keypad 0
76 M{"KP1", SDLK_KP_1}, // Keypad 1
77 M{"KP2", SDLK_KP_2}, // Keypad 2
78 M{"KP3", SDLK_KP_3}, // Keypad 3
79 M{"KP4", SDLK_KP_4}, // Keypad 4
80 M{"KP5", SDLK_KP_5}, // Keypad 5
81 M{"KP6", SDLK_KP_6}, // Keypad 6
82 M{"KP7", SDLK_KP_7}, // Keypad 7
83 M{"KP8", SDLK_KP_8}, // Keypad 8
84 M{"KP9", SDLK_KP_9}, // Keypad 9
85 M{"KP_PERIOD", SDLK_KP_PERIOD}, // Keypad .
86 M{"KP_EQUALS", SDLK_KP_EQUALS}, // Keypad =
87 M{"LCTRL", SDLK_LCTRL}, // Left Ctrl
88 M{"LSHIFT", SDLK_LSHIFT}, // Left Shift
89 M{"LALT", SDLK_LALT}, // Left Alt
90 M{"LSUPER", SDLK_LGUI}, // Left GUI Left "Windows" key
91 M{"RCTRL", SDLK_RCTRL}, // Right Ctrl
92 M{"RSHIFT", SDLK_RSHIFT}, // Right Shift
93 M{"RALT", SDLK_RALT}, // Right Alt
94 M{"RSUPER", SDLK_RGUI}, // Right GUI Right "Windows" key
95 M{"RMODE", SDLK_MODE}, // ModeSwitch "Alt Gr" key
96 };
97 static constexpr auto map = []{
98 auto result = unsortedMap;
99 ranges::sort(result, {}, &M::name);
100 return result;
101 }();
102
103 if (const auto* p = binary_find(map, name, StringOp::caseless{}, &M::name)) {
104 return p->code;
105 }
106 return SDLK_UNKNOWN;
107}
108
110{
111 auto result = SDL_GetKeyFromName(name.c_str());
112 if (result == SDLK_UNKNOWN) {
113 // for backwards compatibility
114 result = getKeyFromOldOpenmsxName(name);
115 }
116 return result;
117}
118
119std::optional<SDLKey> SDLKey::fromString(std::string_view name)
120{
121 // Some remarks:
122 //
123 // SDL2 offers the following function to convert a string to a
124 // SDL_Keycode.
125 // SDL_Keycode SDL_GetKeyFromName(const char* name)
126 // This works for all keys known to SDL2. Meaning we don't have to
127 // maintain our own string->key mapping in openMSX.
128 //
129 // In the past we did have our own mapping. And unfortunately our
130 // mapping was not exactly the same as the (new) SDL2 mapping. Some
131 // examples:
132 // We named the '=' key "EQUALS". The SDL2 name is simply "=".
133 // We named the '1' on the numeric keypad "KP1", the SDL2 name is "Keypad 1".
134 // Our name "RCTRL" is "Right Ctrl" in SDL2.
135 // Our name "ESCAPE" is "Escape" in SDL2 (different capitalization).
136 // ...
137 // (IMHO) The SDL2 names are better (and much more complete), but to
138 // maintain backwards compatibility we keep a list of (old) names (for
139 // some keys).
140 //
141 // The 'SDL_GetKeyFromName() function' is only concerned with naming a
142 // single key. In openMSX we also want to name key combinations like
143 // "A+CTRL" (a single key combined with one or more modifiers). Or
144 // "A,Release" to distinguish a key-press from a release. This function
145 // extends the SDL2 function to also parse these such annotations.
146 //
147 // One complication of these annotations are the separator characters
148 // between the base-key and the annotations. OpenMSX interprets these
149 // three characters as separators:
150 // , + /
151 // Unfortunately these three characters can also appear in the new SDL2
152 // names. For example:
153 // Keypad , Keypad + Keypad /
154 // or simply , + /
155 // Luckily these characters only(?) appear as the last character in the
156 // base name, so we can extend our parser to correctly recognize stuff
157 // like:
158 // ++CTRL ,+SHIFT Keypad /,Release Shift++
159
160 SDLKey result = {};
161 result.sym.scancode = SDL_SCANCODE_UNKNOWN;
162 result.sym.sym = SDLK_UNKNOWN;
163 result.sym.mod = KMOD_NONE;
164 result.sym.unused = 0; // unicode
165 result.down = true;
166
167 while (!name.empty()) {
168 std::string_view part = [&] {
169 auto pos = name.find_first_of(",+/");
170 if (pos == std::string_view::npos) {
171 // no separator character
172 return std::exchange(name, std::string_view{});
173 } else if ((pos + 1) == name.size()) {
174 // ends with a separator, assume it's part of the name
175 return std::exchange(name, std::string_view{});
176 } else {
177 if (name[pos + 1] == one_of(',', '+', '/')) {
178 // next character is also a separator, assume
179 // the character at 'pos' is part of the name
180 ++pos;
181 }
182 auto r = name.substr(0, pos);
183 name.remove_prefix(pos + 1);
184 return r;
185 }
186 }();
187
189 if (cmp(part, "SHIFT")) {
190 result.sym.mod |= KMOD_SHIFT;
191 } else if (cmp(part, "CTRL")) {
192 result.sym.mod |= KMOD_CTRL;
193 } else if (cmp(part, "ALT")) {
194 result.sym.mod |= KMOD_ALT;
195 } else if (cmp(part, "GUI") ||
196 cmp(part, "META")) { // bw-compat
197 result.sym.mod |= KMOD_GUI;
198 } else if (cmp(part, "MODE")) {
199 result.sym.mod |= KMOD_MODE;
200
201 } else if (cmp(part, "PRESS")) {
202 result.down = true;
203 } else if (cmp(part, "RELEASE")) {
204 result.down = false;
205
206 } else {
207 if (result.sym.sym != SDLK_UNKNOWN) {
208 // more than one non-modifier component is not allowed
209 return {};
210 }
211 result.sym.sym = keycodeFromString(std::string(part));
212 if (result.sym.sym == SDLK_UNKNOWN) {
213 return {};
214 }
215 }
216 }
217 if (result.sym.sym == SDLK_UNKNOWN) {
218 return {};
219 }
220 return result;
221}
222
223std::string SDLKey::toString(SDL_Keycode code)
224{
225 std::string result = SDL_GetKeyName(code);
226 if (result.empty()) result = "unknown";
227 return result;
228}
229
230std::string SDLKey::toString() const
231{
232 std::string result = toString(sym.sym);
233 if (sym.mod & KMOD_CTRL) {
234 result += "+CTRL";
235 }
236 if (sym.mod & KMOD_SHIFT) {
237 result += "+SHIFT";
238 }
239 if (sym.mod & KMOD_ALT) {
240 result += "+ALT";
241 }
242 if (sym.mod & KMOD_GUI) {
243 result += "+GUI";
244 }
245 if (sym.mod & KMOD_MODE) {
246 result += "+MODE";
247 }
248 if (!down) {
249 result += ",RELEASE";
250 }
251 return result;
252}
253
254} // namespace openmsx
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:51
auto * binary_find(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition ranges.hh:448
static SDL_Keycode keycodeFromString(zstring_view name)
Definition SDLKey.cc:109
SDL_Keysym sym
Definition SDLKey.hh:19
static std::optional< SDLKey > fromString(std::string_view name)
Definition SDLKey.cc:119
std::string toString() const
Definition SDLKey.cc:230