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