OXYGENHUB
◆ main openmsx / src / input / Keyboard.cc
83.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "Keyboard.hh"
2
3
#include "CommandController.hh"
4
#include "CommandException.hh"
5
#include "DeviceConfig.hh"
6
#include "Event.hh"
7
#include "EventDistributor.hh"
8
#include "InputEventFactory.hh"
9
#include "MSXEventDistributor.hh"
10
#include "MSXMotherBoard.hh"
11
#include "ReverseManager.hh"
12
#include "SDLKey.hh"
13
#include "StateChange.hh"
14
#include "StateChangeDistributor.hh"
15
#include "TclArgParser.hh"
16
#include "TclObject.hh"
17
#include "UnicodeKeymap.hh"
18
#include "serialize.hh"
19
#include "serialize_meta.hh"
20
#include "serialize_stl.hh"
21
22
#include "enumerate.hh"
23
#include "one_of.hh"
24
#include "outer.hh"
25
#include "ranges.hh"
26
#include "stl.hh"
27
#include "unreachable.hh"
28
#include "utf8_checked.hh"
29
#include "utf8_unchecked.hh"
30
#include "xrange.hh"
31
32
#include <SDL.h>
33
34
#include <algorithm>
35
#include <array>
36
#include <cassert>
37
#include <cstdarg>
38
#include <cstdint>
39
#include <cstdio>
40
#include <functional>
41
#include <ranges>
42
#include <type_traits>
43
44
#if defined(_WIN32)
45
46
#ifndef WIN32_LEAN_AND_MEAN
47
#define WIN32_LEAN_AND_MEAN
48
#endif
49
50
#ifdef _MSC_VER
51
#pragma comment(lib, "imm32")
52
#endif
53
#include "SDL_syswm.h"
54
55
#endif // _WIN32
56
57
58
namespace openmsx {
59
60
// How does the CAPSLOCK key behave?
61
#ifdef __APPLE__
62
// See the comments in this issue:
63
//    https://github.com/openMSX/openMSX/issues/1261
64
// Basically it means on apple:
65
//   when the host capslock key is pressed,       SDL sends capslock-pressed
66
//   when the host capslock key is released,      SDL sends nothing
67
//   when the host capslock key is pressed again, SDL sends capslock-released
68
//   when the host capslock key is released,      SDL sends nothing
69
static constexpr bool SANE_CAPSLOCK_BEHAVIOR = false;
70
#else
71
// We get sane capslock events from SDL:
72
//  when the host capslock key is pressed,  SDL sends capslock-pressed
73
//  when the host capslock key is released, SDL sends capslock-released
74
static constexpr bool SANE_CAPSLOCK_BEHAVIOR = true;
75
#endif
76
77
78
static constexpr uint8_t TRY_AGAIN = 0x80; // see pressAscii()
79
80
using KeyInfo = UnicodeKeymap::KeyInfo;
81
82
static constexpr array_with_enum_index<Keyboard::Matrix, std::string_view> defaultKeymapForMatrix = {
83
	"int", // Matrix::MSX
84
	"svi", // Matrix::SVI
85
	"cvjoy", // Matrix::CVJOY
86
	"sega_int", // Matrix::SEGA
87
};
88
89
using ModifierArray = array_with_enum_index<UnicodeKeymap::KeyInfo::Modifier, KeyMatrixPosition>;
90
static constexpr array_with_enum_index<Keyboard::Matrix, ModifierArray> modifierPosForMatrix = {
91
	ModifierArray{ // Matrix::MSX
92
		KeyMatrixPosition(6, 0), // SHIFT
93
		KeyMatrixPosition(6, 1), // CTRL
94
		KeyMatrixPosition(6, 2), // GRAPH
95
		KeyMatrixPosition(6, 3), // CAPS
96
		KeyMatrixPosition(6, 4), // CODE
97
	},
98
	ModifierArray{ // Matrix::SVI
99
		KeyMatrixPosition(6, 0), // SHIFT
100
		KeyMatrixPosition(6, 1), // CTRL
101
		KeyMatrixPosition(6, 2), // LGRAPH
102
		KeyMatrixPosition(8, 3), // CAPS
103
		KeyMatrixPosition(6, 3), // RGRAPH
104
	},
105
	ModifierArray{ // Matrix::CVJOY
106
	},
107
	ModifierArray{ // Matrix::SEGA
108
		KeyMatrixPosition(13, 3), // SHIFT
109
		KeyMatrixPosition(13, 2), // CTRL
110
		KeyMatrixPosition(13, 1), // GRAPH
111
		KeyMatrixPosition(),      // CAPS
112
		KeyMatrixPosition( 0, 4), // ENG/DIER'S
113
	},
114
};
115
116
/** Keyboard bindings ****************************************/
117
118
struct MsxKeyScanMapping {
119
	KeyMatrixPosition msx;
120
	std::array<SDL_Keycode, 3> hostKeyCodes;
121
	std::array<SDL_Scancode, 3> hostScanCodes;
122
};
123
124
template<typename Proj>
125
static constexpr size_t count(std::span<const MsxKeyScanMapping> mapping, Proj proj)
126
{
127
	using Array = std::remove_cvref_t<decltype(std::invoke(proj, mapping[0]))>;
128
	using Code = typename Array::value_type;
129
130
	size_t result = 0;
131
	for (const auto& m : mapping) {
132
		for (const auto& c : std::invoke(proj, m)) {
133
			if (c != Code{}) ++result;
134
		}
135
	}
136
	return result;
137
}
138
139
template<typename GetMapping>
140
static constexpr auto extractKeyCodeMapping(GetMapping getMapping)
141
{
142
	constexpr auto mapping = getMapping();
143
	constexpr size_t N = count(mapping, &MsxKeyScanMapping::hostKeyCodes);
144
	std::array<KeyCodeMsxMapping, N> result;
145
	size_t i = 0;
146
	for (const auto& m : mapping) {
147
		for (const auto& k : m.hostKeyCodes) {
148
			if (k != SDLK_UNKNOWN) {
149
				result[i++] = {k, m.msx};
150
			}
151
		}
152
	}
153
	assert(i == N);
154
	std::ranges::sort(result, {}, &KeyCodeMsxMapping::hostKeyCode);
155
	return result;
156
}
157
template<typename GetMapping>
158
static constexpr auto extractScanCodeMapping(GetMapping getMapping)
159
{
160
	constexpr auto mapping = getMapping();
161
	constexpr size_t N = count(mapping, &MsxKeyScanMapping::hostScanCodes);
162
	std::array<ScanCodeMsxMapping, N> result;
163
	size_t i = 0;
164
	for (const auto& m : mapping) {
165
		for (const auto& k : m.hostScanCodes) {
166
			if (k != SDL_SCANCODE_UNKNOWN) {
167
				result[i++] = {k, m.msx};
168
			}
169
		}
170
	}
171
	assert(i == N);
172
	std::ranges::sort(result, {}, &ScanCodeMsxMapping::hostScanCode);
173
	return result;
174
}
175
176
static constexpr auto getMSXMapping()
177
{
178
	// MSX Key-Matrix table
179
	//
180
	// row/bit  7     6     5     4     3     2     1     0
181
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
182
	//   0   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
183
	//   1   |  ;  |  ]  |  [  |  \  |  =  |  -  |  9  |  8  |
184
	//   2   |  B  |  A  | Acc |  /  |  .  |  ,  |  `  |  '  |
185
	//   3   |  J  |  I  |  H  |  G  |  F  |  E  |  D  |  C  |
186
	//   4   |  R  |  Q  |  P  |  O  |  N  |  M  |  L  |  K  |
187
	//   5   |  Z  |  Y  |  X  |  W  |  V  |  U  |  T  |  S  |
188
	//   6   |  F3 |  F2 |  F1 | code| caps|graph| ctrl|shift|
189
	//   7   | ret |selec|  bs | stop| tab | esc |  F5 |  F4 |
190
	//   8   |right| down|  up | left| del | ins | hom |space|
191
	//   9   |  4  |  3  |  2  |  1  |  0  |  /  |  +  |  *  |
192
	//  10   |  .  |  ,  |  -  |  9  |  8  |  7  |  6  |  5  |
193
	//  11   |     |     |     |     | 'NO'|     |'YES'|     |
194
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
195
	using M = MsxKeyScanMapping;
196
	using P = KeyMatrixPosition;
197
	using K = std::array<SDL_Keycode, 3>;
198
	using S = std::array<SDL_Scancode, 3>;
199
	std::array mapping = {
200
		M{P{0x00}, K{SDLK_0},           S{SDL_SCANCODE_0}},
201
		M{P{0x01}, K{SDLK_1},           S{SDL_SCANCODE_1}},
202
		M{P{0x02}, K{SDLK_2},           S{SDL_SCANCODE_2}},
203
		M{P{0x03}, K{SDLK_3},           S{SDL_SCANCODE_3}},
204
		M{P{0x04}, K{SDLK_4},           S{SDL_SCANCODE_4}},
205
		M{P{0x05}, K{SDLK_5},           S{SDL_SCANCODE_5}},
206
		M{P{0x06}, K{SDLK_6},           S{SDL_SCANCODE_6}},
207
		M{P{0x07}, K{SDLK_7},           S{SDL_SCANCODE_7}},
208
209
		M{P{0x10}, K{SDLK_8},           S{SDL_SCANCODE_8}},
210
		M{P{0x11}, K{SDLK_9},           S{SDL_SCANCODE_9}},
211
		M{P{0x12}, K{SDLK_MINUS},       S{SDL_SCANCODE_MINUS}},
212
		M{P{0x13}, K{SDLK_EQUALS},      S{SDL_SCANCODE_EQUALS}},
213
		M{P{0x14}, K{SDLK_BACKSLASH},   S{SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_INTERNATIONAL3}}, // on Japanese keyboard, the YEN = SDL_SCANCODE_INTERNATIONAL3, but RIGHTBRACKET = SDL_SCANCODE_BACKSLASH
214
		M{P{0x15}, K{SDLK_LEFTBRACKET}, S{SDL_SCANCODE_LEFTBRACKET}},
215
		M{P{0x16}, K{SDLK_RIGHTBRACKET},S{SDL_SCANCODE_RIGHTBRACKET}},
216
		M{P{0x17}, K{SDLK_SEMICOLON},   S{SDL_SCANCODE_SEMICOLON}},
217
218
		M{P{0x20}, K{SDLK_QUOTE},       S{SDL_SCANCODE_APOSTROPHE}},
219
		M{P{0x21}, K{SDLK_BACKQUOTE},   S{SDL_SCANCODE_GRAVE}},
220
		M{P{0x22}, K{SDLK_COMMA},       S{SDL_SCANCODE_COMMA}},
221
		M{P{0x23}, K{SDLK_PERIOD},      S{SDL_SCANCODE_PERIOD}},
222
		M{P{0x24}, K{SDLK_SLASH},       S{SDL_SCANCODE_SLASH}},
223
		M{P{0x25}, K{SDLK_RCTRL},       S{SDL_SCANCODE_RCTRL, SDL_SCANCODE_NONUSBACKSLASH}}, // Acc / BACKSLASH on Japanese keyboard
224
		M{P{0x26}, K{SDLK_a},           S{SDL_SCANCODE_A}},
225
		M{P{0x27}, K{SDLK_b},           S{SDL_SCANCODE_B}},
226
227
		M{P{0x30}, K{SDLK_c},           S{SDL_SCANCODE_C}},
228
		M{P{0x31}, K{SDLK_d},           S{SDL_SCANCODE_D}},
229
		M{P{0x32}, K{SDLK_e},           S{SDL_SCANCODE_E}},
230
		M{P{0x33}, K{SDLK_f},           S{SDL_SCANCODE_F}},
231
		M{P{0x34}, K{SDLK_g},           S{SDL_SCANCODE_G}},
232
		M{P{0x35}, K{SDLK_h},           S{SDL_SCANCODE_H}},
233
		M{P{0x36}, K{SDLK_i},           S{SDL_SCANCODE_I}},
234
		M{P{0x37}, K{SDLK_j},           S{SDL_SCANCODE_J}},
235
236
		M{P{0x40}, K{SDLK_k},           S{SDL_SCANCODE_K}},
237
		M{P{0x41}, K{SDLK_l},           S{SDL_SCANCODE_L}},
238
		M{P{0x42}, K{SDLK_m},           S{SDL_SCANCODE_M}},
239
		M{P{0x43}, K{SDLK_n},           S{SDL_SCANCODE_N}},
240
		M{P{0x44}, K{SDLK_o},           S{SDL_SCANCODE_O}},
241
		M{P{0x45}, K{SDLK_p},           S{SDL_SCANCODE_P}},
242
		M{P{0x46}, K{SDLK_q},           S{SDL_SCANCODE_Q}},
243
		M{P{0x47}, K{SDLK_r},           S{SDL_SCANCODE_R}},
244
245
		M{P{0x50}, K{SDLK_s},           S{SDL_SCANCODE_S}},
246
		M{P{0x51}, K{SDLK_t},           S{SDL_SCANCODE_T}},
247
		M{P{0x52}, K{SDLK_u},           S{SDL_SCANCODE_U}},
248
		M{P{0x53}, K{SDLK_v},           S{SDL_SCANCODE_V}},
249
		M{P{0x54}, K{SDLK_w},           S{SDL_SCANCODE_W}},
250
		M{P{0x55}, K{SDLK_x},           S{SDL_SCANCODE_X}},
251
		M{P{0x56}, K{SDLK_y},           S{SDL_SCANCODE_Y}},
252
		M{P{0x57}, K{SDLK_z},           S{SDL_SCANCODE_Z}},
253
254
		M{P{0x60}, K{SDLK_LSHIFT, SDLK_RSHIFT}, S{SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT}},
255
		M{P{0x61}, K{SDLK_LCTRL},       S{SDL_SCANCODE_LCTRL}},
256
		M{P{0x62}, K{SDLK_LALT},        S{SDL_SCANCODE_LALT}}, // GRAPH
257
		M{P{0x63}, K{SDLK_CAPSLOCK},    S{SDL_SCANCODE_CAPSLOCK}},
258
		M{P{0x64}, K{SDLK_RALT},        S{SDL_SCANCODE_RALT}}, // CODE
259
		M{P{0x65}, K{SDLK_F1},          S{SDL_SCANCODE_F1}},
260
		M{P{0x66}, K{SDLK_F2},          S{SDL_SCANCODE_F2}},
261
		M{P{0x67}, K{SDLK_F3},          S{SDL_SCANCODE_F3}},
262
263
		M{P{0x70}, K{SDLK_F4},          S{SDL_SCANCODE_F4}},
264
		M{P{0x71}, K{SDLK_F5},          S{SDL_SCANCODE_F5}},
265
		M{P{0x72}, K{SDLK_ESCAPE},      S{SDL_SCANCODE_ESCAPE}},
266
		M{P{0x73}, K{SDLK_TAB},         S{SDL_SCANCODE_TAB}},
267
		M{P{0x74}, K{SDLK_F8},          S{SDL_SCANCODE_F8}}, // STOP
268
		M{P{0x75}, K{SDLK_BACKSPACE},   S{SDL_SCANCODE_BACKSPACE}},
269
		M{P{0x76}, K{SDLK_F7},          S{SDL_SCANCODE_F7}}, // SELECT
270
		M{P{0x77}, K{SDLK_RETURN,         SDLK_KP_ENTER},
271
				   S{SDL_SCANCODE_RETURN, SDL_SCANCODE_KP_ENTER}},
272
273
		M{P{0x80}, K{SDLK_SPACE},       S{SDL_SCANCODE_SPACE}},
274
		M{P{0x81}, K{SDLK_HOME},        S{SDL_SCANCODE_HOME}},
275
		M{P{0x82}, K{SDLK_INSERT},      S{SDL_SCANCODE_INSERT}},
276
		M{P{0x83}, K{SDLK_DELETE},      S{SDL_SCANCODE_DELETE}},
277
		M{P{0x84}, K{SDLK_LEFT},        S{SDL_SCANCODE_LEFT}},
278
		M{P{0x85}, K{SDLK_UP},          S{SDL_SCANCODE_UP}},
279
		M{P{0x86}, K{SDLK_DOWN},        S{SDL_SCANCODE_DOWN}},
280
		M{P{0x87}, K{SDLK_RIGHT},       S{SDL_SCANCODE_RIGHT}},
281
282
		M{P{0x90}, K{SDLK_KP_MULTIPLY}, S{SDL_SCANCODE_KP_MULTIPLY}},
283
		M{P{0x91}, K{SDLK_KP_PLUS},     S{SDL_SCANCODE_KP_PLUS}},
284
		M{P{0x92}, K{SDLK_KP_DIVIDE},   S{SDL_SCANCODE_KP_DIVIDE}},
285
		M{P{0x93}, K{SDLK_KP_0},        S{SDL_SCANCODE_KP_0}},
286
		M{P{0x94}, K{SDLK_KP_1},        S{SDL_SCANCODE_KP_1}},
287
		M{P{0x95}, K{SDLK_KP_2},        S{SDL_SCANCODE_KP_2}},
288
		M{P{0x96}, K{SDLK_KP_3},        S{SDL_SCANCODE_KP_3}},
289
		M{P{0x97}, K{SDLK_KP_4},        S{SDL_SCANCODE_KP_4}},
290
291
		M{P{0xA0}, K{SDLK_KP_5},        S{SDL_SCANCODE_KP_5}},
292
		M{P{0xA1}, K{SDLK_KP_6},        S{SDL_SCANCODE_KP_6}},
293
		M{P{0xA2}, K{SDLK_KP_7},        S{SDL_SCANCODE_KP_7}},
294
		M{P{0xA3}, K{SDLK_KP_8},        S{SDL_SCANCODE_KP_8}},
295
		M{P{0xA4}, K{SDLK_KP_9},        S{SDL_SCANCODE_KP_9}},
296
		M{P{0xA5}, K{SDLK_KP_MINUS},    S{SDL_SCANCODE_KP_MINUS}},
297
		M{P{0xA6}, K{SDLK_KP_COMMA},    S{SDL_SCANCODE_KP_COMMA}},
298
		M{P{0xA7}, K{SDLK_KP_PERIOD},   S{SDL_SCANCODE_KP_PERIOD}},
299
300
		M{P{0xB1}, K{SDLK_RGUI},        S{SDL_SCANCODE_RGUI}},
301
		M{P{0xB3}, K{SDLK_LGUI},        S{SDL_SCANCODE_LGUI}},
302
	};
303
	return mapping;
304
}
305
306
static constexpr auto getSVIMapping()
307
{
308
	// SVI Keyboard Matrix
309
	//
310
	// row/bit  7     6     5     4     3     2     1     0
311
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
312
	//   0   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
313
	//   1   |  /  |  .  |  =  |  ,  |  '  |  :  |  9  |  8  |
314
	//   2   |  G  |  F  |  E  |  D  |  C  |  B  |  A  |  -  |
315
	//   3   |  O  |  N  |  M  |  L  |  K  |  J  |  I  |  H  |
316
	//   4   |  W  |  V  |  U  |  T  |  S  |  R  |  Q  |  P  |
317
	//   5   | UP  | BS  |  ]  |  \  |  [  |  Z  |  Y  |  X  |
318
	//   6   |LEFT |ENTER|STOP | ESC |RGRAP|LGRAP|CTRL |SHIFT|
319
	//   7   |DOWN | INS | CLS | F5  | F4  | F3  | F2  | F1  |
320
	//   8   |RIGHT|     |PRINT| SEL |CAPS | DEL | TAB |SPACE|
321
	//   9   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |  Numerical keypad
322
	//  10   |  ,  |  .  |  /  |  *  |  -  |  +  |  9  |  8  |   SVI-328 only
323
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
324
	using M = MsxKeyScanMapping;
325
	using P = KeyMatrixPosition;
326
	using K = std::array<SDL_Keycode, 3>;
327
	using S = std::array<SDL_Scancode, 3>;
328
	std::array mapping = {
329
		M{P{0x00}, K{SDLK_0},           S{SDL_SCANCODE_0}},
330
		M{P{0x01}, K{SDLK_1},           S{SDL_SCANCODE_1}},
331
		M{P{0x02}, K{SDLK_2},           S{SDL_SCANCODE_2}},
332
		M{P{0x03}, K{SDLK_3},           S{SDL_SCANCODE_3}},
333
		M{P{0x04}, K{SDLK_4},           S{SDL_SCANCODE_4}},
334
		M{P{0x05}, K{SDLK_5},           S{SDL_SCANCODE_5}},
335
		M{P{0x06}, K{SDLK_6},           S{SDL_SCANCODE_6}},
336
		M{P{0x07}, K{SDLK_7},           S{SDL_SCANCODE_7}},
337
338
		M{P{0x10}, K{SDLK_8},           S{SDL_SCANCODE_8}},
339
		M{P{0x11}, K{SDLK_9},           S{SDL_SCANCODE_9}},
340
		M{P{0x12}, K{SDLK_SEMICOLON},   S{SDL_SCANCODE_SEMICOLON}},
341
		M{P{0x13}, K{SDLK_QUOTE},       S{SDL_SCANCODE_APOSTROPHE}},
342
		M{P{0x14}, K{SDLK_COMMA},       S{SDL_SCANCODE_COMMA}},
343
		M{P{0x15}, K{SDLK_EQUALS},      S{SDL_SCANCODE_EQUALS}},
344
		M{P{0x16}, K{SDLK_PERIOD},      S{SDL_SCANCODE_PERIOD}},
345
		M{P{0x17}, K{SDLK_SLASH},       S{SDL_SCANCODE_SLASH}},
346
347
		M{P{0x20}, K{SDLK_MINUS},       S{SDL_SCANCODE_MINUS}},
348
		M{P{0x21}, K{SDLK_a},           S{SDL_SCANCODE_A}},
349
		M{P{0x22}, K{SDLK_b},           S{SDL_SCANCODE_B}},
350
		M{P{0x23}, K{SDLK_c},           S{SDL_SCANCODE_C}},
351
		M{P{0x24}, K{SDLK_d},           S{SDL_SCANCODE_D}},
352
		M{P{0x25}, K{SDLK_e},           S{SDL_SCANCODE_E}},
353
		M{P{0x26}, K{SDLK_f},           S{SDL_SCANCODE_F}},
354
		M{P{0x27}, K{SDLK_g},           S{SDL_SCANCODE_G}},
355
356
		M{P{0x30}, K{SDLK_h},           S{SDL_SCANCODE_H}},
357
		M{P{0x31}, K{SDLK_i},           S{SDL_SCANCODE_I}},
358
		M{P{0x32}, K{SDLK_j},           S{SDL_SCANCODE_J}},
359
		M{P{0x33}, K{SDLK_k},           S{SDL_SCANCODE_K}},
360
		M{P{0x34}, K{SDLK_l},           S{SDL_SCANCODE_L}},
361
		M{P{0x35}, K{SDLK_m},           S{SDL_SCANCODE_M}},
362
		M{P{0x36}, K{SDLK_n},           S{SDL_SCANCODE_N}},
363
		M{P{0x37}, K{SDLK_o},           S{SDL_SCANCODE_O}},
364
365
		M{P{0x40}, K{SDLK_p},           S{SDL_SCANCODE_P}},
366
		M{P{0x41}, K{SDLK_q},           S{SDL_SCANCODE_Q}},
367
		M{P{0x42}, K{SDLK_r},           S{SDL_SCANCODE_R}},
368
		M{P{0x43}, K{SDLK_s},           S{SDL_SCANCODE_S}},
369
		M{P{0x44}, K{SDLK_t},           S{SDL_SCANCODE_T}},
370
		M{P{0x45}, K{SDLK_u},           S{SDL_SCANCODE_U}},
371
		M{P{0x46}, K{SDLK_v},           S{SDL_SCANCODE_V}},
372
		M{P{0x47}, K{SDLK_w},           S{SDL_SCANCODE_W}},
373
374
		M{P{0x50}, K{SDLK_x},           S{SDL_SCANCODE_X}},
375
		M{P{0x51}, K{SDLK_y},           S{SDL_SCANCODE_Y}},
376
		M{P{0x52}, K{SDLK_z},           S{SDL_SCANCODE_Z}},
377
		M{P{0x53}, K{SDLK_LEFTBRACKET}, S{SDL_SCANCODE_LEFTBRACKET}},
378
		M{P{0x54}, K{SDLK_BACKSLASH},   S{SDL_SCANCODE_BACKSLASH}},
379
		M{P{0x55}, K{SDLK_RIGHTBRACKET},S{SDL_SCANCODE_RIGHTBRACKET}},
380
		M{P{0x56}, K{SDLK_BACKSPACE},   S{SDL_SCANCODE_BACKSPACE}},
381
		M{P{0x57}, K{SDLK_UP},          S{SDL_SCANCODE_UP}},
382
383
		M{P{0x60}, K{SDLK_LSHIFT, SDLK_RSHIFT}, S{SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT}},
384
		M{P{0x61}, K{SDLK_LCTRL},       S{SDL_SCANCODE_LCTRL}},
385
		M{P{0x62}, K{SDLK_LALT},        S{SDL_SCANCODE_LALT}},
386
		M{P{0x63}, K{SDLK_RALT},        S{SDL_SCANCODE_RALT}},
387
		M{P{0x64}, K{SDLK_ESCAPE},      S{SDL_SCANCODE_ESCAPE}},
388
		M{P{0x65}, K{SDLK_F8},          S{SDL_SCANCODE_F8}}, // STOP
389
		M{P{0x66}, K{SDLK_RETURN,         SDLK_KP_ENTER},
390
				   S{SDL_SCANCODE_RETURN, SDL_SCANCODE_KP_ENTER}},
391
		M{P{0x67}, K{SDLK_LEFT},        S{SDL_SCANCODE_LEFT}},
392
393
		M{P{0x70}, K{SDLK_F1},          S{SDL_SCANCODE_F1}},
394
		M{P{0x71}, K{SDLK_F2},          S{SDL_SCANCODE_F2}},
395
		M{P{0x72}, K{SDLK_F3},          S{SDL_SCANCODE_F3}},
396
		M{P{0x73}, K{SDLK_F4},          S{SDL_SCANCODE_F4}},
397
		M{P{0x74}, K{SDLK_F5},          S{SDL_SCANCODE_F5}},
398
		M{P{0x75}, K{SDLK_F6},          S{SDL_SCANCODE_F6}}, // CLS
399
		M{P{0x76}, K{SDLK_INSERT},      S{SDL_SCANCODE_INSERT}},
400
		M{P{0x77}, K{SDLK_DOWN},        S{SDL_SCANCODE_DOWN}},
401
402
		M{P{0x80}, K{SDLK_SPACE},       S{SDL_SCANCODE_SPACE}},
403
		M{P{0x81}, K{SDLK_TAB},         S{SDL_SCANCODE_TAB}},
404
		M{P{0x82}, K{SDLK_DELETE},      S{SDL_SCANCODE_DELETE}},
405
		M{P{0x83}, K{SDLK_CAPSLOCK},    S{SDL_SCANCODE_CAPSLOCK}},
406
		M{P{0x84}, K{SDLK_F7},          S{SDL_SCANCODE_F7}}, // SELECT
407
		M{P{0x85}, K{SDLK_PRINTSCREEN}, S{SDL_SCANCODE_PRINTSCREEN}}, // PRINT
408
		// no key on 0x86 ?
409
		M{P{0x87}, K{SDLK_RIGHT},       S{SDL_SCANCODE_RIGHT}},
410
411
		M{P{0x90}, K{SDLK_KP_0},        S{SDL_SCANCODE_KP_0}},
412
		M{P{0x91}, K{SDLK_KP_1},        S{SDL_SCANCODE_KP_1}},
413
		M{P{0x92}, K{SDLK_KP_2},        S{SDL_SCANCODE_KP_2}},
414
		M{P{0x93}, K{SDLK_KP_3},        S{SDL_SCANCODE_KP_3}},
415
		M{P{0x94}, K{SDLK_KP_4},        S{SDL_SCANCODE_KP_4}},
416
		M{P{0x95}, K{SDLK_KP_5},        S{SDL_SCANCODE_KP_5}},
417
		M{P{0x96}, K{SDLK_KP_6},        S{SDL_SCANCODE_KP_6}},
418
		M{P{0x97}, K{SDLK_KP_7},        S{SDL_SCANCODE_KP_7}},
419
420
		M{P{0xA0}, K{SDLK_KP_8},        S{SDL_SCANCODE_KP_8}},
421
		M{P{0xA1}, K{SDLK_KP_9},        S{SDL_SCANCODE_KP_9}},
422
		M{P{0xA2}, K{SDLK_KP_PLUS},     S{SDL_SCANCODE_KP_PLUS}},
423
		M{P{0xA3}, K{SDLK_KP_MINUS},    S{SDL_SCANCODE_KP_MINUS}},
424
		M{P{0xA4}, K{SDLK_KP_MULTIPLY}, S{SDL_SCANCODE_KP_MULTIPLY}},
425
		M{P{0xA5}, K{SDLK_KP_DIVIDE},   S{SDL_SCANCODE_KP_DIVIDE}},
426
		M{P{0xA6}, K{SDLK_KP_PERIOD},   S{SDL_SCANCODE_KP_PERIOD}},
427
		M{P{0xA7}, K{SDLK_KP_COMMA},    S{SDL_SCANCODE_KP_COMMA}},
428
	};
429
	return mapping;
430
}
431
432
static constexpr auto getCvJoyMapping()
433
{
434
	// ColecoVision Joystick "Matrix"
435
	//
436
	// The hardware consists of 2 controllers that each have 2 triggers
437
	// and a 12-key keypad. They're not actually connected in a matrix,
438
	// but a ghosting-free matrix is the easiest way to model it in openMSX.
439
	//
440
	// row/bit  7     6     5     4     3     2     1     0
441
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
442
	//   0   |TRIGB|TRIGA|     |     |LEFT |DOWN |RIGHT| UP  |  controller 1
443
	//   1   |TRIGB|TRIGA|     |     |LEFT |DOWN |RIGHT| UP  |  controller 2
444
	//   2   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |  controller 1
445
	//   3   |     |     |     |     |  #  |  *  |  9  |  8  |  controller 1
446
	//   4   |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |  controller 2
447
	//   5   |     |     |     |     |  #  |  *  |  9  |  8  |  controller 2
448
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
449
	using M = MsxKeyScanMapping;
450
	using P = KeyMatrixPosition;
451
	using K = std::array<SDL_Keycode, 3>;
452
	using S = std::array<SDL_Scancode, 3>;
453
	std::array mapping = {
454
		M{P{0x00}, K{SDLK_UP},      S{SDL_SCANCODE_UP}},
455
		M{P{0x01}, K{SDLK_RIGHT},   S{SDL_SCANCODE_RIGHT}},
456
		M{P{0x02}, K{SDLK_DOWN},    S{SDL_SCANCODE_DOWN}},
457
		M{P{0x03}, K{SDLK_LEFT},    S{SDL_SCANCODE_LEFT}},
458
		M{P{0x06}, K{SDLK_SPACE,        SDLK_RCTRL},
459
		           S{SDL_SCANCODE_SPACE, SDL_SCANCODE_RCTRL}},
460
		M{P{0x07}, K{SDLK_RSHIFT,         SDLK_RALT,         SDLK_LALT},
461
		           S{SDL_SCANCODE_RSHIFT, SDL_SCANCODE_RALT, SDL_SCANCODE_LALT}},
462
463
		M{P{0x10}, K{SDLK_w},       S{SDL_SCANCODE_W}},
464
		M{P{0x11}, K{SDLK_d},       S{SDL_SCANCODE_D}},
465
		M{P{0x12}, K{SDLK_s},       S{SDL_SCANCODE_S}},
466
		M{P{0x13}, K{SDLK_a},       S{SDL_SCANCODE_A}},
467
		M{P{0x16}, K{SDLK_LCTRL},   S{SDL_SCANCODE_LCTRL}},
468
		M{P{0x17}, K{SDLK_LSHIFT},  S{SDL_SCANCODE_LSHIFT}},
469
470
		M{P{0x20}, K{SDLK_0, SDLK_KP_0},       S{SDL_SCANCODE_0}},
471
		M{P{0x21}, K{SDLK_1, SDLK_KP_1},       S{SDL_SCANCODE_1}},
472
		M{P{0x22}, K{SDLK_2, SDLK_KP_2},       S{SDL_SCANCODE_2}},
473
		M{P{0x23}, K{SDLK_3, SDLK_KP_3},       S{SDL_SCANCODE_3}},
474
		M{P{0x24}, K{SDLK_4, SDLK_KP_4},       S{SDL_SCANCODE_4}},
475
		M{P{0x25}, K{SDLK_5, SDLK_KP_5},       S{SDL_SCANCODE_5}},
476
		M{P{0x26}, K{SDLK_6, SDLK_KP_6}, S{SDL_SCANCODE_6}},
477
		M{P{0x27}, K{SDLK_7, SDLK_KP_7}, S{SDL_SCANCODE_7}},
478
479
		M{P{0x30}, K{SDLK_8, SDLK_KP_8}, S{SDL_SCANCODE_8}},
480
		M{P{0x31}, K{SDLK_9, SDLK_KP_9}, S{SDL_SCANCODE_9}},
481
		M{P{0x32}, K{SDLK_MINUS,         SDLK_KP_MULTIPLY,         SDLK_KP_MINUS},
482
		           S{SDL_SCANCODE_MINUS, SDL_SCANCODE_KP_MULTIPLY, SDL_SCANCODE_KP_MINUS}}, // *
483
		M{P{0x33}, K{SDLK_EQUALS,         SDLK_KP_DIVIDE,         SDLK_KP_PLUS},
484
		           S{SDL_SCANCODE_EQUALS, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_KP_PLUS}},// #
485
486
		M{P{0x40}, K{SDLK_u},       S{SDL_SCANCODE_U}}, // 0
487
		M{P{0x41}, K{SDLK_v},       S{SDL_SCANCODE_V}}, // 1
488
		M{P{0x42}, K{SDLK_b},       S{SDL_SCANCODE_B}}, // 2
489
		M{P{0x43}, K{SDLK_n},       S{SDL_SCANCODE_N}}, // 3
490
		M{P{0x44}, K{SDLK_f},       S{SDL_SCANCODE_F}}, // 4
491
		M{P{0x45}, K{SDLK_g},       S{SDL_SCANCODE_G}}, // 5
492
		M{P{0x46}, K{SDLK_h},       S{SDL_SCANCODE_H}}, // 6
493
		M{P{0x47}, K{SDLK_r},       S{SDL_SCANCODE_R}}, // 7
494
495
		M{P{0x50}, K{SDLK_t},       S{SDL_SCANCODE_T}}, // 8
496
		M{P{0x51}, K{SDLK_y},       S{SDL_SCANCODE_Y}}, // 9
497
		M{P{0x52}, K{SDLK_j},       S{SDL_SCANCODE_J}}, // *
498
		M{P{0x53}, K{SDLK_m},       S{SDL_SCANCODE_M}}, // #
499
	};
500
	return mapping;
501
}
502
503
static constexpr auto getSegaMapping()
504
{
505
	// Sega SC-3000 / SK-1100 Keyboard Matrix
506
	//
507
	// row/bit  7     6     5     4     3     2     1     0
508
	//       +-----+-----+-----+-----+-----+-----+-----+-----+  PPI
509
	//   0   |  I  |  K  |  ,  | eng |  Z  |  A  |  Q  |  1  |  A0
510
	//   1   |  O  |  L  |  .  |space|  X  |  S  |  W  |  2  |  A1
511
	//   2   |  P  |  ;  |  /  |home |  C  |  D  |  E  |  3  |  A2
512
	//   3   |  @  |  :  | pi  |ins  |  V  |  F  |  R  |  4  |  A3
513
	//   4   |  [  |  ]  |down |     |  B  |  G  |  T  |  5  |  A4
514
	//   5   |     | cr  |left |     |  N  |  H  |  Y  |  6  |  A5
515
	//   6   |     | up  |right|     |  M  |  J  |  U  |  7  |  A6
516
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
517
	//   7   |     |     |     |     |     |     |     |  8  |  B0
518
	//   8   |     |     |     |     |     |     |     |  9  |  B1
519
	//   9   |     |     |     |     |     |     |     |  0  |  B2
520
	//   A   |     |     |     |     |     |     |     |  -  |  B3
521
	//   B   |     |     |     |     |     |     |     |  ^  |  B4
522
	//   C   |     |     |     |     |func |     |     | cur |  B5
523
	//   D   |     |     |     |     |shift|ctrl |graph|break|  B6
524
	//       +-----+-----+-----+-----+-----+-----+-----+-----+
525
	// Issues:
526
	// - graph is a lock key and gets pressed when using alt-tab
527
	// - alt-F7 is bound to quick-load
528
	using M = MsxKeyScanMapping;
529
	using P = KeyMatrixPosition;
530
	using K = std::array<SDL_Keycode, 3>;
531
	using S = std::array<SDL_Scancode, 3>;
532
	std::array mapping = {
533
		M{P{0x00}, K{SDLK_1,         SDLK_KP_1},
534
		           S{SDL_SCANCODE_1, SDL_SCANCODE_KP_1}},
535
		M{P{0x01}, K{SDLK_q},           S{SDL_SCANCODE_Q}},
536
		M{P{0x02}, K{SDLK_a},           S{SDL_SCANCODE_A}},
537
		M{P{0x03}, K{SDLK_z},           S{SDL_SCANCODE_Z}},
538
		M{P{0x04}, K{SDLK_RALT},        S{SDL_SCANCODE_RALT}},  // eng
539
		M{P{0x05}, K{SDLK_COMMA},       S{SDL_SCANCODE_COMMA}},
540
		M{P{0x06}, K{SDLK_k},           S{SDL_SCANCODE_K}},
541
		M{P{0x07}, K{SDLK_i},           S{SDL_SCANCODE_I}},
542
543
		M{P{0x10}, K{SDLK_2,         SDLK_KP_2},
544
		          S{SDL_SCANCODE_2, SDL_SCANCODE_KP_2}},
545
		M{P{0x11}, K{SDLK_w},           S{SDL_SCANCODE_W}},
546
		M{P{0x12}, K{SDLK_s},           S{SDL_SCANCODE_S}},
547
		M{P{0x13}, K{SDLK_x},           S{SDL_SCANCODE_X}},
548
		M{P{0x14}, K{SDLK_SPACE},       S{SDL_SCANCODE_SPACE}},
549
		M{P{0x15}, K{SDLK_PERIOD,         SDLK_KP_PERIOD},
550
		           S{SDL_SCANCODE_PERIOD, SDL_SCANCODE_KP_PERIOD}},
551
		M{P{0x16}, K{SDLK_l},           S{SDL_SCANCODE_L}},
552
		M{P{0x17}, K{SDLK_o},           S{SDL_SCANCODE_O}},
553
554
		M{P{0x20}, K{SDLK_3,         SDLK_KP_3},
555
		           S{SDL_SCANCODE_3, SDL_SCANCODE_KP_3}},
556
		M{P{0x21}, K{SDLK_e},           S{SDL_SCANCODE_E}},
557
		M{P{0x22}, K{SDLK_d},           S{SDL_SCANCODE_D}},
558
		M{P{0x23}, K{SDLK_c},           S{SDL_SCANCODE_C}},
559
		M{P{0x24}, K{SDLK_HOME},        S{SDL_SCANCODE_HOME}},
560
		M{P{0x25}, K{SDLK_SLASH,         SDLK_KP_DIVIDE},
561
		           S{SDL_SCANCODE_SLASH, SDL_SCANCODE_KP_DIVIDE}},
562
		M{P{0x26}, K{SDLK_SEMICOLON,         SDLK_KP_PLUS},
563
		           S{SDL_SCANCODE_SEMICOLON, SDL_SCANCODE_KP_PLUS}},
564
		M{P{0x27}, K{SDLK_p},           S{SDL_SCANCODE_P}},
565
566
		M{P{0x30}, K{SDLK_4,         SDLK_KP_4},
567
		           S{SDL_SCANCODE_4, SDL_SCANCODE_KP_4}},
568
		M{P{0x31}, K{SDLK_r},           S{SDL_SCANCODE_R}},
569
		M{P{0x32}, K{SDLK_f},           S{SDL_SCANCODE_F}},
570
		M{P{0x33}, K{SDLK_v},           S{SDL_SCANCODE_V}},
571
		M{P{0x34}, K{SDLK_INSERT},      S{SDL_SCANCODE_INSERT}},
572
		M{P{0x35}, K{SDLK_F7},          S{SDL_SCANCODE_F7}},         // pi
573
		M{P{0x36}, K{SDLK_QUOTE,              SDLK_KP_MULTIPLY},
574
		           S{SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_KP_MULTIPLY}}, // :
575
		M{P{0x37}, K{SDLK_BACKQUOTE},   S{SDL_SCANCODE_GRAVE}},      // @
576
577
		M{P{0x40}, K{SDLK_5,         SDLK_KP_5},
578
		           S{SDL_SCANCODE_5, SDL_SCANCODE_KP_5}},
579
		M{P{0x41}, K{SDLK_t},           S{SDL_SCANCODE_T}},
580
		M{P{0x42}, K{SDLK_g},           S{SDL_SCANCODE_G}},
581
		M{P{0x43}, K{SDLK_b},           S{SDL_SCANCODE_B}},
582
		// nothing on 0x44
583
		M{P{0x45}, K{SDLK_DOWN},        S{SDL_SCANCODE_DOWN}},
584
		M{P{0x46}, K{SDLK_RIGHTBRACKET},S{SDL_SCANCODE_RIGHTBRACKET}},
585
		M{P{0x47}, K{SDLK_LEFTBRACKET}, S{SDL_SCANCODE_LEFTBRACKET}},
586
587
		M{P{0x50}, K{SDLK_6,         SDLK_KP_6},
588
		           S{SDL_SCANCODE_6, SDL_SCANCODE_KP_6}},
589
		M{P{0x51}, K{SDLK_y},           S{SDL_SCANCODE_Y}},
590
		M{P{0x52}, K{SDLK_h},           S{SDL_SCANCODE_H}},
591
		M{P{0x53}, K{SDLK_n},           S{SDL_SCANCODE_N}},
592
		// nothing on 0x54
593
		M{P{0x55}, K{SDLK_LEFT},        S{SDL_SCANCODE_LEFT}},
594
		M{P{0x56}, K{SDLK_RETURN,         SDLK_KP_ENTER},
595
		           S{SDL_SCANCODE_RETURN, SDL_SCANCODE_KP_ENTER}},
596
		//P{ not}hing on 0x57
597
598
		M{P{0x60}, K{SDLK_7,         SDLK_KP_7},
599
		           S{SDL_SCANCODE_7, SDL_SCANCODE_KP_7}},
600
		M{P{0x61}, K{SDLK_u},           S{SDL_SCANCODE_U}},
601
		M{P{0x62}, K{SDLK_j},           S{SDL_SCANCODE_J}},
602
		M{P{0x63}, K{SDLK_m},           S{SDL_SCANCODE_M}},
603
		// nothing on 0x64
604
		M{P{0x65}, K{SDLK_RIGHT},       S{SDL_SCANCODE_RIGHT}},
605
		M{P{0x66}, K{SDLK_UP},          S{SDL_SCANCODE_UP}},
606
		// nothing on 0x67
607
608
		M{P{0x70}, K{SDLK_8,         SDLK_KP_8},
609
		           S{SDL_SCANCODE_8, SDL_SCANCODE_KP_8}},
610
		M{P{0x80}, K{SDLK_9,         SDLK_KP_9},
611
		           S{SDL_SCANCODE_9, SDL_SCANCODE_KP_9}},
612
		M{P{0x90}, K{SDLK_0,         SDLK_KP_0},
613
		           S{SDL_SCANCODE_0, SDL_SCANCODE_KP_0}},
614
		M{P{0xA0}, K{SDLK_MINUS,         SDLK_KP_MINUS},
615
		           S{SDL_SCANCODE_MINUS, SDL_SCANCODE_KP_MINUS}},
616
		M{P{0xB0}, K{SDLK_EQUALS},      S{SDL_SCANCODE_EQUALS}}, // ^
617
618
		M{P{0xC0}, K{SDLK_BACKSLASH},   S{SDL_SCANCODE_BACKSLASH}}, // cur
619
		M{P{0xC3}, K{SDLK_TAB},         S{SDL_SCANCODE_TAB}},       // func
620
621
		M{P{0xD0}, K{SDLK_ESCAPE,         SDLK_F8},
622
		           S{SDL_SCANCODE_ESCAPE, SDL_SCANCODE_F8}},   // break
623
		M{P{0xD1}, K{SDLK_LALT},        S{SDL_SCANCODE_LALT}}, // graph
624
		M{P{0xD2}, K{SDLK_LCTRL},       S{SDL_SCANCODE_LCTRL}},
625
		M{P{0xD3}, K{SDLK_LSHIFT,         SDLK_RSHIFT},
626
		        S{SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT}},
627
	};
628
	return mapping;
629
//// 0    1    2    3    4    5    6    7    8    9    a    b    c    d    e    f
630
//   x  , x  , x  , x  , x  , x  , x  , x  ,0x34,0xC3, x  , x  , x  ,0x56, x  , x  , //000
631
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  ,0xD0, x  , x  , x  , x  , //010
632
//  0x14, x  , x  , x  , x  , x  , x  ,0x36, x  , x  , x  , x  ,0x05,0xA0,0x15,0x25, //020
633
//  0x90,0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x36,0x26, x  ,0xB0, x  , x  , //030
634
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //040
635
//   x  ,0x55,0x66,0x65,0x45, x  , x  , x  , x  , x  , x  ,0x47,0xC0,0x46, x  , x  , //050
636
//  0x37,0x02,0x43,0x23,0x22,0x21,0x32,0x42,0x52,0x07,0x62,0x06,0x16,0x63,0x53,0x17, //060
637
//  0x27,0x01,0x31,0x12,0x41,0x61,0x33,0x11,0x13,0x51,0x03, x  , x  , x  , x  ,0x34, //070
638
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //080
639
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //090
640
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0A0
641
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0B0
642
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0C0
643
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0D0
644
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0E0
645
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //0F0
646
//  0x90,0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x15,0x25,0x36,0xA0,0x26,0x56, //100
647
//   x  ,0x66,0x45,0x65,0x55,0x34,0x24, x  , x  , x  , x  , x  , x  , x  , x  , x  , //110
648
//  0x35,0xD0, x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  ,0xD3, //120
649
//  0xD3, x  ,0xD2,0x04,0xD1, x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //130
650
//   x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , x  , //140
651
}
652
653
static constexpr auto msxKeyCodeMapping    = extractKeyCodeMapping ([] { return getMSXMapping(); });
654
static constexpr auto msxScanCodeMapping   = extractScanCodeMapping([] { return getMSXMapping(); });
655
static constexpr auto sviKeyCodeMapping    = extractKeyCodeMapping ([] { return getSVIMapping(); });
656
static constexpr auto sviScanCodeMapping   = extractScanCodeMapping([] { return getSVIMapping(); });
657
static constexpr auto cvJoyKeyCodeMapping  = extractKeyCodeMapping ([] { return getCvJoyMapping(); });
658
static constexpr auto cvJoyScanCodeMapping = extractScanCodeMapping([] { return getCvJoyMapping(); });
659
static constexpr auto segaKeyCodeMapping   = extractKeyCodeMapping ([] { return getSegaMapping(); });
660
static constexpr auto segaScanCodeMapping  = extractScanCodeMapping([] { return getSegaMapping(); });
661
662
static constexpr array_with_enum_index<Keyboard::Matrix, std::span<const KeyCodeMsxMapping>> defaultKeyCodeMappings = {
663
	msxKeyCodeMapping,
664
	sviKeyCodeMapping,
665
	cvJoyKeyCodeMapping,
666
	segaKeyCodeMapping,
667
};
668
static constexpr array_with_enum_index<Keyboard::Matrix, std::span<const ScanCodeMsxMapping>> defaultScanCodeMappings = {
669
	msxScanCodeMapping,
670
	sviScanCodeMapping,
671
	cvJoyScanCodeMapping,
672
	segaScanCodeMapping,
673
};
674
675
Keyboard::Keyboard(MSXMotherBoard& motherBoard,
676
                   Scheduler& scheduler_,
677
                   CommandController& commandController_,
678
                   EventDistributor& eventDistributor_,
679
                   MSXEventDistributor& msxEventDistributor_,
680
                   StateChangeDistributor& stateChangeDistributor_,
681
                   Matrix matrix,
682
                   const DeviceConfig& config)
683
	: Schedulable(scheduler_)
684
	, commandController(commandController_)
685
	, msxEventDistributor(msxEventDistributor_)
686
	, stateChangeDistributor(stateChangeDistributor_)
687
	, eventDistributor(eventDistributor_)
688
	, keyCodeTab (defaultKeyCodeMappings [matrix])
689
	, scanCodeTab(defaultScanCodeMappings[matrix])
690
	, modifierPos(modifierPosForMatrix[matrix])
691
	, keyMatrixUpCmd  (commandController, stateChangeDistributor, scheduler_)
692
	, keyMatrixDownCmd(commandController, stateChangeDistributor, scheduler_)
693
	, keyTypeCmd      (commandController, stateChangeDistributor, scheduler_)
694
	, msxcode2UnicodeCmd(commandController)
695
	, unicode2MsxcodeCmd(commandController)
696
	, capsLockAligner(eventDistributor, scheduler_)
697
#if defined(_WIN32)
698
	, imeManager(eventDistributor)
699
#endif
700
	, keyboardSettings(commandController)
701
	, msxKeyEventQueue(scheduler_, commandController.getInterpreter())
702
	, keybDebuggable(motherBoard)
703
	, unicodeKeymap(config.getChildData(
704
		"keyboard_type", defaultKeymapForMatrix[matrix]))
705
	, hasKeypad(config.getChildDataAsBool("has_keypad", true))
706
	, blockRow11(matrix == Matrix::MSX
707
		&& !config.getChildDataAsBool("has_yesno_keys", false))
708
	, keyGhosting(config.getChildDataAsBool("key_ghosting", true))
709
	, keyGhostingSGCprotected(config.getChildDataAsBool(
710
		"key_ghosting_sgc_protected", true))
711
	, modifierIsLock(KeyInfo::CAPS_MASK
712
		| (config.getChildDataAsBool("code_kana_locks", false) ? KeyInfo::CODE_MASK : 0)
713
		| (config.getChildDataAsBool("graph_locks", false) ? KeyInfo::GRAPH_MASK : 0))
714
{
715
	std::ranges::fill(keyMatrix,     255);
716
	std::ranges::fill(cmdKeyMatrix,  255);
717
	std::ranges::fill(typeKeyMatrix, 255);
718
	std::ranges::fill(userKeyMatrix, 255);
719
	std::ranges::fill(hostKeyMatrix, 255);
720
721
	msxEventDistributor.registerEventListener(*this);
722
	stateChangeDistributor.registerListener(*this);
723
	// We do not listen for CONSOLE_OFF_EVENTS because rescanning the
724
	// keyboard can have unwanted side effects
725
726
	eventDistributor.registerEventListener(EventType::KEYMAP_CHANGED, *this);
727
728
	motherBoard.registerKeyboard(*this);
729
}
730
731
Keyboard::~Keyboard()
732
{
733
	auto& motherBoard = keybDebuggable.getMotherBoard();
734
	motherBoard.unregisterKeyboard(*this);
735
736
	eventDistributor.unregisterEventListener(EventType::KEYMAP_CHANGED, *this);
737
738
	stateChangeDistributor.unregisterListener(*this);
739
	msxEventDistributor.unregisterEventListener(*this);
740
}
741
742
const MsxChar2Unicode& Keyboard::getMsxChar2Unicode() const
743
{
744
	return unicodeKeymap.getMsxChars();
745
}
746
747
static constexpr void doKeyGhosting(std::span<uint8_t, KeyMatrixPosition::NUM_ROWS> matrix,
748
                                    bool protectRow6)
749
{
750
	// This routine enables key-ghosting as seen on a real MSX
751
	//
752
	// If on a real MSX in the keyboard matrix the
753
	// real buttons are pressed as in the left matrix
754
	//           then the matrix to the
755
	// 10111111  right will be read by   10110101
756
	// 11110101  because of the simple   10110101
757
	// 10111101  electrical connections  10110101
758
	//           that are established  by
759
	// the closed switches
760
	// However, some MSX models have protection against
761
	// key-ghosting for SHIFT, GRAPH and CODE keys
762
	// On those models, SHIFT, GRAPH and CODE are
763
	// connected to row 6 via a diode. It prevents that
764
	// SHIFT, GRAPH and CODE get ghosted to another
765
	// row.
766
	bool changedSomething = false;
767
	do {
768
		changedSomething = false;
769
		// TODO: On Sega keyboards, ghosting should probably be done separately
770
		//       for rows 0..6 and 7..14, since they're connected to different
771
		//       PPI ports.
772
		for (auto i : xrange(KeyMatrixPosition::NUM_ROWS - 1)) {
773
			auto row1 = matrix[i];
774
			for (auto j : xrange(i + 1, KeyMatrixPosition::NUM_ROWS)) {
775
				auto row2 = matrix[j];
776
				if ((row1 != row2) && ((row1 | row2) != 0xff)) {
777
					auto rowIold = matrix[i];
778
					auto rowJold = matrix[j];
779
					// TODO: The shift/graph/code key ghosting protection
780
					//       implementation is only correct for MSX.
781
					if (protectRow6 && i == 6) {
782
						matrix[i] = row1 & row2;
783
						matrix[j] = (row1 | 0x15) & row2;
784
						row1 &= row2;
785
					} else if (protectRow6 && j == 6) {
786
						matrix[i] = row1 & (row2 | 0x15);
787
						matrix[j] = row1 & row2;
788
						row1 &= (row2 | 0x15);
789
					} else {
790
						// not same and some common zero's
791
						//  --> inherit other zero's
792
						uint8_t newRow = row1 & row2;
793
						matrix[i] = newRow;
794
						matrix[j] = newRow;
795
						row1 = newRow;
796
					}
797
					if (rowIold != matrix[i] ||
798
					    rowJold != matrix[j]) {
799
						changedSomething = true;
800
					}
801
				}
802
			}
803
		}
804
	} while (changedSomething);
805
}
806
807
std::span<const uint8_t, KeyMatrixPosition::NUM_ROWS> Keyboard::getKeys() const
808
{
809
	if (keysChanged) {
810
		keysChanged = false;
811
		std::span matrix = keyTypeCmd.isActive() ? typeKeyMatrix : userKeyMatrix;
812
		for (auto row : xrange(KeyMatrixPosition::NUM_ROWS)) {
813
			keyMatrix[row] = cmdKeyMatrix[row] & matrix[row];
814
		}
815
		if (keyGhosting) {
816
			doKeyGhosting(keyMatrix, keyGhostingSGCprotected);
817
		}
818
	}
819
	return keyMatrix;
820
}
821
822
void Keyboard::transferHostKeyMatrix(const Keyboard& source)
823
{
824
	// This mechanism exists to solve the following problem:
825
	//   - play a game where the spacebar is constantly pressed (e.g.
826
	//     Road Fighter)
827
	//   - go back in time (press the reverse hotkey) while keeping the
828
	//     spacebar pressed
829
	//   - interrupt replay by pressing the cursor keys, still while
830
	//     keeping spacebar pressed
831
	// At the moment replay is interrupted, we need to resynchronize the
832
	// msx keyboard with the host keyboard. In the past we assumed the host
833
	// keyboard had no keys pressed. But this is wrong in the above
834
	// scenario. Now we remember the state of the host keyboard and
835
	// transfer that to the new keyboard(s) that get created for reverse.
836
	// When replay is stopped we restore this host keyboard state, see
837
	// stopReplay().
838
839
	for (auto row : xrange(KeyMatrixPosition::NUM_ROWS)) {
840
		hostKeyMatrix[row] = source.hostKeyMatrix[row];
841
	}
842
}
843
844
void Keyboard::setFocus(bool newFocus, EmuTime time)
845
{
846
	if (newFocus == focus) return;
847
	focus = newFocus;
848
849
	if (!stateChangeDistributor.isReplaying()) {
850
		syncHostKeyMatrix(time); // release all keys on lost focus
851
	}
852
}
853
854
/* Received an MSX event
855
 * Following events get processed:
856
 *  EventType::KEY_DOWN
857
 *  EventType::KEY_UP
858
 */
859
void Keyboard::signalMSXEvent(const Event& event,
860
                              EmuTime time) noexcept
861
{
862
	if (getType(event) == one_of(EventType::KEY_DOWN, EventType::KEY_UP)) {
863
		const auto& keyEvent = get_event<KeyEvent>(event);
864
		if (keyEvent.getRepeat()) return;
865
		// Ignore possible console on/off events:
866
		// we do not re-scan the keyboard since this may lead to
867
		// an unwanted pressing of <return> in MSX after typing
868
		// "set console off" in the console.
869
		msxKeyEventQueue.process_asap(time, event);
870
	}
871
}
872
873
void Keyboard::signalStateChange(const StateChange& event)
874
{
875
	const auto* kms = std::get_if<KeyMatrixState>(&event);
876
	if (!kms) return;
877
878
	userKeyMatrix[kms->getRow()] &= uint8_t(~kms->getPress());
879
	userKeyMatrix[kms->getRow()] |=          kms->getRelease();
880
	keysChanged = true; // do ghosting at next getKeys()
881
}
882
883
void Keyboard::stopReplay(EmuTime time) noexcept
884
{
885
	syncHostKeyMatrix(time);
886
}
887
888
void Keyboard::syncHostKeyMatrix(EmuTime time)
889
{
890
	for (auto [row, hkm] : enumerate(hostKeyMatrix)) {
891
		changeKeyMatrixEvent(time, uint8_t(row), hkm);
892
	}
893
	msxModifiers = 0xff;
894
	msxKeyEventQueue.clear();
895
	lastUnicodeForScancode.clear();
896
}
897
898
uint8_t Keyboard::needsLockToggle(const UnicodeKeymap::KeyInfo& keyInfo) const
899
{
900
	return modifierIsLock
901
	     & (locksOn ^ keyInfo.modMask)
902
	     & unicodeKeymap.getRelevantMods(keyInfo);
903
}
904
905
bool Keyboard::isKeyMatrixPressed(KeyMatrixPosition pos) const
906
{
907
	if (!pos.isValid()) return false;
908
	auto row = pos.getRow();
909
	auto mask = pos.getMask();
910
	return ((hostKeyMatrix[row] & mask) == 0) &&
911
	       ((userKeyMatrix[row] & mask) == 0);
912
}
913
914
void Keyboard::pressKeyMatrixEvent(EmuTime time, KeyMatrixPosition pos)
915
{
916
	if (!pos.isValid()) {
917
		// No such key.
918
		return;
919
	}
920
	if (isKeyMatrixPressed(pos)) {
921
		// Won't have any effect, ignore.
922
		return;
923
	}
924
	auto row = pos.getRow();
925
	changeKeyMatrixEvent(time, row, hostKeyMatrix[row] & ~pos.getMask());
926
}
927
928
void Keyboard::releaseKeyMatrixEvent(EmuTime time, KeyMatrixPosition pos)
929
{
930
	if (!pos.isValid()) {
931
		// No such key.
932
		return;
933
	}
934
	auto row = pos.getRow();
935
	auto release = pos.getMask();
936
	if (((hostKeyMatrix[row] & release) == release) &&
937
	    ((userKeyMatrix[row] & release) == release)) {
938
		// Won't have any effect, ignore.
939
		// Test scenario: during replay, exit the openmsx console with
940
		// the 'toggle console' command. The 'enter,release' event will
941
		// end up here. But it shouldn't stop replay.
942
		return;
943
	}
944
	changeKeyMatrixEvent(time, row, hostKeyMatrix[row] | release);
945
}
946
947
void Keyboard::changeKeyMatrixEvent(EmuTime time, uint8_t row, uint8_t newValue)
948
{
949
	if (!focus) newValue = 0xff;
950
951
	// This method already updates hostKeyMatrix[],
952
	// userKeyMatrix[] will soon be updated via KeyMatrixState events.
953
	hostKeyMatrix[row] = newValue;
954
955
	uint8_t diff = userKeyMatrix[row] ^ newValue;
956
	if (diff == 0) return;
957
	uint8_t press   = userKeyMatrix[row] & diff;
958
	uint8_t release = newValue           & diff;
959
	stateChangeDistributor.distributeNew<KeyMatrixState>(
960
		time, row, press, release);
961
}
962
963
/*
964
 * @return True iff a release event for the CODE/KANA key must be scheduled.
965
 */
966
bool Keyboard::processQueuedEvent(const Event& event, EmuTime time)
967
{
968
	auto mode = keyboardSettings.getMappingMode();
969
970
	const auto& keyEvent = get_event<KeyEvent>(event);
971
	bool down = getType(event) == EventType::KEY_DOWN;
972
	auto key = keyEvent.getKey();
973
974
	// Check if the host keyboard is a Japanese keyboard. If so, ignore the GRAVE key.
975
	// Reason:
976
	// On Windows with a Japanese keyboard layout, the "Half-width/Full-width" key
977
	// does not send a release event. This causes the key to get stuck in a pressed state.
978
	// This key is for switching input modes, but its physical location is the GRAVE key.
979
	// Japanese MSX does not have a GRAVE key and does not use it, so we simply ignore it.
980
	// Previously, we could easily ignore it because the scancode was UNKNOWN.
981
	// Recently, it started reporting as a GRAVE key scancode, so we changed the condition here.
982
	if (hostKeyMap == HostKeyMap::UNKNOWN) {
983
		if (SDL_GetKeyFromScancode(SDL_SCANCODE_LEFTBRACKET) == SDLK_AT) {
984
			hostKeyMap = HostKeyMap::JP;
985
		} else {
986
			hostKeyMap = HostKeyMap::EN;
987
		}
988
	}
989
990
	if (down) {
991
		auto codepointToUtf8 = [](uint32_t cp) {
992
			std::array<char, 4> buffer;
993
			auto* start = buffer.data();
994
			auto* end = utf8::unchecked::append(cp, start);
995
			return std::string(start, end);
996
		};
997
998
		// TODO: refactor debug(...) method to expect a std::string and then adapt
999
		// all invocations of it to provide a properly formatted string, using the C++
1000
		// features for it.
1001
		// Once that is done, debug(...) can pass the c_str() version of that string
1002
		// to ad_printf(...) so that I don't have to make an explicit ad_printf(...)
1003
		// invocation for each debug(...) invocation
1004
		debug("Key pressed, unicode: 0x%04x (%s), keyCode: 0x%05x (%s), scanCode: 0x%03x (%s), keyName: %s\n",
1005
		      keyEvent.getUnicode(),
1006
		      codepointToUtf8(keyEvent.getUnicode()).c_str(),
1007
		      keyEvent.getKeyCode(),
1008
		      SDL_GetKeyName(keyEvent.getKeyCode()),
1009
		      keyEvent.getScanCode(),
1010
		      SDL_GetScancodeName(keyEvent.getScanCode()),
1011
		      key.toString().c_str());
1012
	} else {
1013
		debug("Key released, keyCode: 0x%05x (%s), scanCode: 0x%03x (%s), keyName: %s\n",
1014
		      keyEvent.getKeyCode(),
1015
		      SDL_GetKeyName(keyEvent.getKeyCode()),
1016
		      keyEvent.getScanCode(),
1017
		      SDL_GetScancodeName(keyEvent.getScanCode()),
1018
		      key.toString().c_str());
1019
	}
1020
1021
	// Process dead keys.
1022
	if (mode == KeyboardSettings::MappingMode::CHARACTER) {
1023
		for (auto n : xrange(3)) {
1024
			if (key.sym.sym == keyboardSettings.getDeadKeyHostKey(n)) {
1025
				UnicodeKeymap::KeyInfo deadKey = unicodeKeymap.getDeadKey(n);
1026
				if (deadKey.isValid()) {
1027
					updateKeyMatrix(time, down, deadKey.pos);
1028
					return false;
1029
				}
1030
			}
1031
		}
1032
	}
1033
1034
	if (key.sym.sym == SDLK_CAPSLOCK) {
1035
		processCapslockEvent(time, down);
1036
		return false;
1037
	} else if (key.sym.sym == keyboardSettings.getCodeKanaHostKey()) {
1038
		processCodeKanaChange(time, down);
1039
		return false;
1040
	} else if (key.sym.sym == SDLK_LALT) {
1041
		processGraphChange(time, down);
1042
		return false;
1043
	// KEYPAD_RETURN and KEYPAD_COMMA exist at the same physical position on the host side.
1044
	} else if ((key.sym.sym == SDLK_KP_ENTER) || (key.sym.sym == SDLK_KP_COMMA)) {
1045
		processKeypadEnterKey(time, down);
1046
		return false;
1047
	} else {
1048
		// To work around a Japanese keyboard Kanji mode bug. (Multi-character
1049
		// input makes a keydown event without keyrelease message.)
1050
		if (keyEvent.getScanCode() == SDL_SCANCODE_UNKNOWN) {
1051
			return false;
1052
		}
1053
		if ((keyEvent.getScanCode() == SDL_SCANCODE_GRAVE) && (hostKeyMap == HostKeyMap::JP)) {
1054
			// To work around a Japanese keyboard Kanji mode bug. (Multi-character
1055
			// input makes a keydown event without keyrelease message.)
1056
			return false;
1057
		}
1058
		return processKeyEvent(time, down, keyEvent);
1059
	}
1060
}
1061
1062
/*
1063
 * Process a change (up or down event) of the CODE/KANA key
1064
 * It presses or releases the key in the MSX keyboard matrix
1065
 * and changes the kana-lock state in case of a press
1066
 */
1067
void Keyboard::processCodeKanaChange(EmuTime time, bool down)
1068
{
1069
	if (down) {
1070
		locksOn ^= KeyInfo::CODE_MASK;
1071
	}
1072
	updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::CODE]);
1073
}
1074
1075
/*
1076
 * Process a change (up or down event) of the GRAPH key
1077
 * It presses or releases the key in the MSX keyboard matrix
1078
 * and changes the graph-lock state in case of a press
1079
 */
1080
void Keyboard::processGraphChange(EmuTime time, bool down)
1081
{
1082
	if (down) {
1083
		locksOn ^= KeyInfo::GRAPH_MASK;
1084
	}
1085
	updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::GRAPH]);
1086
}
1087
1088
/*
1089
 * Process a change (up or down event) of the CAPSLOCK key
1090
 * It presses or releases the key in the MSX keyboard matrix
1091
 * and changes the capslock state in case of a press
1092
 */
1093
void Keyboard::processCapslockEvent(EmuTime time, bool down)
1094
{
1095
	if (SANE_CAPSLOCK_BEHAVIOR) {
1096
		debug("Changing CAPS lock state according to SDL request\n");
1097
		if (down) {
1098
			locksOn ^= KeyInfo::CAPS_MASK;
1099
		}
1100
		updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::CAPS]);
1101
	} else {
1102
		debug("Pressing CAPS lock and scheduling a release\n");
1103
		locksOn ^= KeyInfo::CAPS_MASK;
1104
		updateKeyMatrix(time, true, modifierPos[KeyInfo::Modifier::CAPS]);
1105
		setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (in MSX time)
1106
	}
1107
}
1108
1109
void Keyboard::executeUntil(EmuTime time)
1110
{
1111
	debug("Releasing CAPS lock\n");
1112
	updateKeyMatrix(time, false, modifierPos[KeyInfo::Modifier::CAPS]);
1113
}
1114
1115
bool Keyboard::signalEvent(const Event& event)
1116
{
1117
	if (getType(event) == EventType::KEYMAP_CHANGED) {
1118
		// check hostKeyMap at Next keyEvent
1119
		hostKeyMap = HostKeyMap::UNKNOWN;
1120
	}
1121
	return false;
1122
}
1123
1124
void Keyboard::processKeypadEnterKey(EmuTime time, bool down)
1125
{
1126
	if (!hasKeypad && !keyboardSettings.getAlwaysEnableKeypad()) {
1127
		// User entered on host keypad but this MSX model does not have one
1128
		// Ignore the keypress/release
1129
		return;
1130
	}
1131
	processSdlKey(time,
1132
		keyboardSettings.getKpEnterMode() == KeyboardSettings::KpEnterMode::MSX_KP_COMMA ?
1133
		SDLKey::create(SDLK_KP_COMMA, SDL_SCANCODE_KP_COMMA, down) :
1134
		SDLKey::create(SDLK_KP_ENTER, SDL_SCANCODE_KP_ENTER, down));
1135
}
1136
1137
/*
1138
 * Process an SDL key press/release event. It concerns a
1139
 * special key (e.g. SHIFT, UP, DOWN, F1, F2, ...) that can not
1140
 * be unambiguously derived from a unicode character;
1141
 *  Map the SDL key to an equivalent MSX key press/release event
1142
 */
1143
void Keyboard::processSdlKey(EmuTime time, SDLKey key)
1144
{
1145
	auto process = [&](KeyMatrixPosition pos) {
1146
		assert(pos.isValid());
1147
		if (pos.getRow() == 11 && blockRow11) {
1148
			// do not process row 11 if we have no Yes/No keys
1149
			return;
1150
		}
1151
		updateKeyMatrix(time, key.down, pos);
1152
	};
1153
1154
	if (keyboardSettings.getMappingMode() == KeyboardSettings::MappingMode::POSITIONAL) {
1155
		if ((key.sym.sym == SDLK_RIGHTBRACKET) && (key.sym.scancode == SDL_SCANCODE_BACKSLASH))
1156
		{
1157
			// host: Japanese keyboard "]" -> position as -> US keyboard "GRAVE"
1158
			if (const auto* mapping = binary_find(scanCodeTab, SDL_SCANCODE_GRAVE, {}, &ScanCodeMsxMapping::hostScanCode)) {
1159
				process(mapping->msx);
1160
			}
1161
		} else if (const auto* mapping = binary_find(scanCodeTab, key.sym.scancode, {}, &ScanCodeMsxMapping::hostScanCode)) {
1162
			process(mapping->msx);
1163
		}
1164
	} else {
1165
		if (const auto* mapping = binary_find(keyCodeTab, key.sym.sym, {}, &KeyCodeMsxMapping::hostKeyCode)) {
1166
			process(mapping->msx);
1167
		}
1168
	}
1169
}
1170
1171
/*
1172
 * Update the MSX keyboard matrix
1173
 */
1174
void Keyboard::updateKeyMatrix(EmuTime time, bool down, KeyMatrixPosition pos)
1175
{
1176
	if (!pos.isValid()) {
1177
		// No such key.
1178
		return;
1179
	}
1180
	if (down) {
1181
		pressKeyMatrixEvent(time, pos);
1182
		// Keep track of the MSX modifiers.
1183
		// The MSX modifiers sometimes get overruled by the unicode character
1184
		// processing, in which case the unicode processing must be able to
1185
		// restore them to the real key-combinations pressed by the user.
1186
		for (auto [i, mp] : enumerate(modifierPos)) {
1187
			if (pos == mp) {
1188
				msxModifiers &= uint8_t(~(1 << i));
1189
			}
1190
		}
1191
	} else {
1192
		releaseKeyMatrixEvent(time, pos);
1193
		for (auto [i, mp] : enumerate(modifierPos)) {
1194
			if (pos == mp) {
1195
				msxModifiers |= 1 << i;
1196
			}
1197
		}
1198
	}
1199
}
1200
1201
/*
1202
 * Process an SDL key event;
1203
 *  Check if it is a special key, in which case it can be directly
1204
 *  mapped to the MSX matrix.
1205
 *  Otherwise, retrieve the unicode character value for the event
1206
 *  and map the unicode character to the key-combination that must
1207
 *  be pressed to generate the equivalent character on the MSX
1208
 * @return True iff a release event for the CODE/KANA key must be scheduled.
1209
 */
1210
bool Keyboard::processKeyEvent(EmuTime time, bool down, const KeyEvent& keyEvent)
1211
{
1212
	auto mode = keyboardSettings.getMappingMode();
1213
1214
	auto keyCode = keyEvent.getKeyCode();
1215
	auto scanCode = keyEvent.getScanCode();
1216
	auto key = keyEvent.getKey();
1217
1218
	bool isOnKeypad =
1219
		(SDLK_KP_1 <= keyCode && keyCode <= SDLK_KP_0) || // note order is 1-9,0
1220
		(keyCode == one_of(SDLK_KP_PERIOD, SDLK_KP_DIVIDE, SDLK_KP_MULTIPLY,
1221
		                   SDLK_KP_MINUS,  SDLK_KP_PLUS));
1222
1223
	if (isOnKeypad && !hasKeypad &&
1224
	    !keyboardSettings.getAlwaysEnableKeypad()) {
1225
		// User entered on host keypad but this MSX model does not have one
1226
		// Ignore the keypress/release
1227
		return false;
1228
	}
1229
#ifdef __APPLE__
1230
	bool positional = mode == KeyboardSettings::MappingMode::POSITIONAL;
1231
	if ((key.sym.mod & KMOD_GUI) &&
1232
	    (( positional && (keyEvent.getScanCode() == SDL_SCANCODE_I)) ||
1233
	     (!positional && (keyCode == SDLK_i)))) {
1234
		// Apple keyboards don't have an Insert key, use Cmd+I as an alternative.
1235
		keyCode = SDLK_INSERT;
1236
		key.sym.sym = SDLK_INSERT;
1237
		key.sym.scancode = SDL_SCANCODE_INSERT;
1238
		key.sym.mod &= ~KMOD_GUI;
1239
		key.sym.unused = 0; // unicode
1240
	}
1241
#endif
1242
1243
	if (down) {
1244
		UnicodeKeymap::KeyInfo keyInfo;
1245
		unsigned unicode;
1246
		if (isOnKeypad ||
1247
		    mode == one_of(KeyboardSettings::MappingMode::KEY,
1248
		                   KeyboardSettings::MappingMode::POSITIONAL)) {
1249
			// User entered a key on numeric keypad or the driver is in
1250
			// KEY/POSITIONAL mapping mode.
1251
			// First option (keypad) maps to same unicode as some other key
1252
			// combinations (e.g. digit on main keyboard or TAB/DEL).
1253
			// Use unicode to handle the more common combination and use direct
1254
			// matrix to matrix mapping for the exceptional cases (keypad).
1255
			unicode = 0;
1256
		} else {
1257
			unicode = keyEvent.getUnicode();
1258
			if ((unicode < 0x20) || ((0x7F <= unicode) && (unicode < 0xA0))) {
1259
				// Control character in C0 or C1 range.
1260
				// Use SDL's interpretation instead.
1261
				unicode = 0;
1262
			} else if (utf8::is_pua(unicode)) {
1263
				// Code point in Private Use Area: undefined by Unicode,
1264
				// so we rely on SDL's interpretation instead.
1265
				// For example the Mac's cursor keys are in this range.
1266
				unicode = 0;
1267
			} else {
1268
				keyInfo = unicodeKeymap.get(unicode);
1269
				if (!keyInfo.isValid()) {
1270
					// Unicode does not exist in our mapping; try to process
1271
					// the key using its keycode instead.
1272
					unicode = 0;
1273
				}
1274
			}
1275
		}
1276
1277
		// Remember which unicode character is currently derived
1278
		// from this SDL key. It must be stored here (during key-press)
1279
		// because during key-release SDL never returns the unicode
1280
		// value (it always returns the value 0). But we must know
1281
		// the unicode value in order to be able to perform the correct
1282
		// key-combination-release in the MSX
1283
		if (auto it = std::ranges::lower_bound(lastUnicodeForScancode, scanCode, {}, &std::pair<int32_t, uint32_t>::first);
1284
		    (it != lastUnicodeForScancode.end()) && (it->first == scanCode)) {
1285
			// after a while we can overwrite existing elements, and
1286
			// then we stop growing/reallocating this vector
1287
			it->second = unicode;
1288
		} else {
1289
			// insert new element (in the right location)
1290
			lastUnicodeForScancode.emplace(it, scanCode, unicode);
1291
		}
1292
1293
		if (unicode == 0) {
1294
			// It was an ambiguous key (numeric key-pad, CTRL+character)
1295
			// or a special key according to SDL (like HOME, INSERT, etc)
1296
			// or a first keystroke of a composed key
1297
			// (e.g. altr-gr + = on azerty keyboard) or driver is in
1298
			// direct SDL mapping mode:
1299
			// Perform direct SDL matrix to MSX matrix mapping
1300
			// But only when it is not a first keystroke of a
1301
			// composed key
1302
			if (!(key.sym.mod & KMOD_MODE)) {
1303
				processSdlKey(time, key);
1304
			}
1305
			return false;
1306
		} else {
1307
			// It is a unicode character; map it to the right key-combination
1308
			return pressUnicodeByUser(time, keyInfo, unicode, true);
1309
		}
1310
	} else {
1311
		// key was released
1312
		auto it = std::ranges::lower_bound(lastUnicodeForScancode, scanCode, {}, &std::pair<int32_t, uint32_t>::first);
1313
		unsigned unicode = ((it != lastUnicodeForScancode.end()) && (it->first == scanCode))
1314
		                 ? it->second // get the unicode that was derived from this key
1315
		                 : 0;
1316
		if (unicode == 0) {
1317
			// It was a special key, perform matrix to matrix mapping
1318
			// But only when it is not a first keystroke of a
1319
			// composed key
1320
			if (!(key.sym.mod & KMOD_MODE)) {
1321
				processSdlKey(time, key);
1322
			}
1323
		} else {
1324
			// It was a unicode character; map it to the right key-combination
1325
			pressUnicodeByUser(time, unicodeKeymap.get(unicode), unicode, false);
1326
		}
1327
		return false;
1328
	}
1329
}
1330
1331
void Keyboard::processCmd(Interpreter& interp, std::span<const TclObject> tokens, bool up)
1332
{
1333
	unsigned row  = tokens[1].getInt(interp);
1334
	unsigned mask = tokens[2].getInt(interp);
1335
	if (row >= KeyMatrixPosition::NUM_ROWS) {
1336
		throw CommandException("Invalid row");
1337
	}
1338
	if (mask >= 256) {
1339
		throw CommandException("Invalid mask");
1340
	}
1341
	if (up) {
1342
		cmdKeyMatrix[row] |= narrow_cast<uint8_t>(mask);
1343
	} else {
1344
		cmdKeyMatrix[row] &= narrow_cast<uint8_t>(~mask);
1345
	}
1346
	keysChanged = true;
1347
}
1348
1349
/*
1350
 * This routine processes unicode characters. It maps a unicode character
1351
 * to the correct key-combination on the MSX.
1352
 *
1353
 * There are a few caveats with respect to the MSX and Host modifier keys
1354
 * that you must be aware about if you want to understand why the routine
1355
 * works as it works.
1356
 *
1357
 * Row 6 of the MSX keyboard matrix contains the MSX modifier keys:
1358
 *   CTRL, CODE, GRAPH and SHIFT
1359
 *
1360
 * The SHIFT key is also a modifier key on the host machine. However, the
1361
 * SHIFT key behaviour can differ between HOST and MSX for all 'special'
1362
 * characters (anything but A-Z).
1363
 * For example, on AZERTY host keyboard, user presses SHIFT+& to make the '1'
1364
 * On MSX QWERTY keyboard, the same key-combination leads to '!'.
1365
 * So this routine must not only PRESS the SHIFT key when required according
1366
 * to the unicode mapping table but it must also RELEASE the SHIFT key for all
1367
 * these special keys when the user PRESSES the key/character.
1368
 *
1369
 * On the other hand, for A-Z, this routine must not touch the SHIFT key at all.
1370
 * Otherwise it might give strange behaviour when CAPS lock is on (which also
1371
 * acts as a key-modifier for A-Z). The routine can rely on the fact that
1372
 * SHIFT+A-Z behaviour is the same on all host and MSX keyboards. It is
1373
 * approximately the only part of keyboards that is de-facto standardized :-)
1374
 *
1375
 * For the other modifiers (CTRL, CODE and GRAPH), the routine must be able to
1376
 * PRESS them when required but there is no need to RELEASE them during
1377
 * character press. On the contrary; the host keys that map to CODE and GRAPH
1378
 * do not work as modifiers on the host itself, so if the routine would release
1379
 * them, it would give wrong result.
1380
 * For example, 'ALT-A' on Host will lead to unicode character 'a', just like
1381
 * only pressing the 'A' key. The MSX however must know about the difference.
1382
 *
1383
 * As a reminder: here is the build-up of row 6 of the MSX key matrix
1384
 *              7    6     5     4    3      2     1    0
1385
 * row  6   |  F3 |  F2 |  F1 | code| caps|graph| ctrl|shift|
1386
 */
1387
bool Keyboard::pressUnicodeByUser(
1388
		EmuTime time, UnicodeKeymap::KeyInfo keyInfo, unsigned unicode,
1389
		bool down)
1390
{
1391
	bool insertCodeKanaRelease = false;
1392
	if (down) {
1393
		if ((needsLockToggle(keyInfo) & KeyInfo::CODE_MASK) &&
1394
				keyboardSettings.getAutoToggleCodeKanaLock() &&
1395
				!isKeyMatrixPressed(modifierPos[KeyInfo::Modifier::CODE])) {
1396
			// Code Kana locks, is in wrong state and must be auto-toggled:
1397
			// Toggle it by pressing the lock key and scheduling a
1398
			// release event.
1399
			// Only do this when the CODE/KANA key is currently released,
1400
			// because only then does the MSX see a new key-press edge and
1401
			// actually toggle its lock. While the user is holding the host
1402
			// CODE/KANA key (typically AltGr, which on many host layouts is
1403
			// needed to type '@', '#', ...) the key is already down, so the
1404
			// MSX would toggle nothing while we did flip 'locksOn' here,
1405
			// leaving us permanently out of sync with the MSX. In that case
1406
			// simply type the character with CODE/KANA held, like a real MSX.
1407
			locksOn ^= KeyInfo::CODE_MASK;
1408
			pressKeyMatrixEvent(time, modifierPos[KeyInfo::Modifier::CODE]);
1409
			insertCodeKanaRelease = true;
1410
		} else {
1411
			// Press the character key and related modifiers
1412
			// Ignore the CODE key in case that Code Kana locks
1413
			// (e.g. do not press it).
1414
			// Ignore the GRAPH key in case that Graph locks
1415
			// Always ignore CAPSLOCK mask (assume that user will
1416
			// use real CAPS lock to switch/ between hiragana and
1417
			// katakana on japanese model)
1418
			pressKeyMatrixEvent(time, keyInfo.pos);
1419
1420
			uint8_t modMask = keyInfo.modMask & ~modifierIsLock;
1421
			if (('A' <= unicode && unicode <= 'Z') || ('a' <= unicode && unicode <= 'z')) {
1422
				// For a-z and A-Z, leave SHIFT unchanged, this to cater
1423
				// for difference in behaviour between host and emulated
1424
				// machine with respect to how the combination of CAPSLOCK
1425
				// and SHIFT is interpreted for these characters.
1426
				modMask &= ~KeyInfo::SHIFT_MASK;
1427
			} else {
1428
				// Release SHIFT if our character does not require it.
1429
				if (~modMask & KeyInfo::SHIFT_MASK) {
1430
					releaseKeyMatrixEvent(time, modifierPos[KeyInfo::Modifier::SHIFT]);
1431
				}
1432
			}
1433
			// Press required modifiers for our character.
1434
			// Note that these modifiers are only pressed, never released.
1435
			for (auto [i, mp] : enumerate(modifierPos)) {
1436
				if ((modMask >> i) & 1) {
1437
					pressKeyMatrixEvent(time, mp);
1438
				}
1439
			}
1440
		}
1441
	} else {
1442
		releaseKeyMatrixEvent(time, keyInfo.pos);
1443
1444
		// Restore non-lock modifier keys.
1445
		for (auto [i, mp] : enumerate(modifierPos)) {
1446
			if (!((modifierIsLock >> i) & 1)) {
1447
				// Do not simply unpress graph, ctrl, code and shift but
1448
				// restore them to the values currently pressed by the user.
1449
				if ((msxModifiers >> i) & 1) {
1450
					releaseKeyMatrixEvent(time, mp);
1451
				} else {
1452
					pressKeyMatrixEvent(time, mp);
1453
				}
1454
			}
1455
		}
1456
	}
1457
	keysChanged = true;
1458
	return insertCodeKanaRelease;
1459
}
1460
1461
/*
1462
 * Press an ASCII character. It is used by the 'Insert characters'
1463
 * function that is exposed to the console.
1464
 * The characters are inserted in a separate keyboard matrix, to prevent
1465
 * interference with the keypresses of the user on the MSX itself.
1466
 *
1467
 * @returns:
1468
 *   zero  : handling this character is done
1469
 * non-zero: typing this character needs additional actions
1470
 *    bits 0-4: release these modifiers and call again
1471
 *    bit   7 : simply call again (used by SHIFT+GRAPH heuristic)
1472
 */
1473
uint8_t Keyboard::pressAscii(unsigned unicode, bool down)
1474
{
1475
	uint8_t releaseMask = 0;
1476
	UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.get(unicode);
1477
	if (!keyInfo.isValid()) {
1478
		return releaseMask;
1479
	}
1480
	uint8_t modMask = keyInfo.modMask & ~modifierIsLock;
1481
	if (down) {
1482
		// check for modifier toggles
1483
		uint8_t toggleLocks = needsLockToggle(keyInfo);
1484
		for (auto [i, mp] : enumerate(modifierPos)) {
1485
			if ((toggleLocks >> i) & 1) {
1486
				debug("Toggling lock %d\n", i);
1487
				locksOn ^= 1 << i;
1488
				releaseMask |= 1 << i;
1489
				typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1490
			}
1491
		}
1492
		if (releaseMask == 0) {
1493
			debug("Key pasted, unicode: 0x%04x, row: %02d, col: %d, modMask: %02x\n",
1494
			      unicode, keyInfo.pos.getRow(), keyInfo.pos.getColumn(), modMask);
1495
			// Workaround MSX-BIOS(?) bug/limitation:
1496
			//
1497
			// Under these conditions:
1498
			// - Typing a graphical MSX character 00-1F (such a char 'x' gets
1499
			//   printed as chr$(1) followed by chr$(x+64)).
1500
			// - Typing this character requires pressing SHIFT+GRAPH and one
1501
			//   'regular' key.
1502
			// - And all 3 keys are immediately pressed simultaneously.
1503
			// Then, from time to time, instead of the intended character 'x'
1504
			// (00-1F), the wrong character 'x+64' gets printed.
1505
			// When first SHIFT+GRAPH is pressed, and only one frame later the
1506
			// other keys is pressed (additionally), this problem seems to
1507
			// disappear.
1508
			//
1509
			// After implementing the above we found that a similar problem
1510
			// occurs when:
1511
			// - a GRAPH + <x> (without SHIFT) key combo is typed
1512
			// - immediately after a key combo with GRAPH + SHIFT + <x>.
1513
			// For example:
1514
			//   type "\u2666\u266a"
1515
			// from time to time 2nd character got wrongly typed as a
1516
			// 'M' (instead of a musical note symbol). But typing a sequence
1517
			// of \u266a chars without a preceding \u2666 just works.
1518
			//
1519
			// To fix both these problems (and possibly still undiscovered
1520
			// variations), I'm now extending the workaround to all characters
1521
			// that are typed via a key combination that includes GRAPH.
1522
			if (modMask & KeyInfo::GRAPH_MASK) {
1523
				auto isPressed = [&](auto& key) {
1524
					return (typeKeyMatrix[key.getRow()] & key.getMask()) == 0;
1525
				};
1526
				if (!isPressed(modifierPos[KeyInfo::Modifier::GRAPH])) {
1527
					// GRAPH not yet pressed ->
1528
					//  first press it before adding the non-modifier key
1529
					releaseMask = TRY_AGAIN;
1530
				}
1531
			}
1532
			// press modifiers
1533
			for (auto [i, mp] : enumerate(modifierPos)) {
1534
				if ((modMask >> i) & 1) {
1535
					typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1536
				}
1537
			}
1538
			if (releaseMask == 0) {
1539
				// press key
1540
				typeKeyMatrix[keyInfo.pos.getRow()] &= uint8_t(~keyInfo.pos.getMask());
1541
			}
1542
		}
1543
	} else {
1544
		typeKeyMatrix[keyInfo.pos.getRow()] |= keyInfo.pos.getMask();
1545
		for (auto [i, mp] : enumerate(modifierPos)) {
1546
			if ((modMask >> i) & 1) {
1547
				typeKeyMatrix[mp.getRow()] |= mp.getMask();
1548
			}
1549
		}
1550
	}
1551
	keysChanged = true;
1552
	return releaseMask;
1553
}
1554
1555
/*
1556
 * Press a lock key. It is used by the 'Insert characters'
1557
 * function that is exposed to the console.
1558
 * The characters are inserted in a separate keyboard matrix, to prevent
1559
 * interference with the keypresses of the user on the MSX itself
1560
 */
1561
void Keyboard::pressLockKeys(uint8_t lockKeysMask, bool down)
1562
{
1563
	for (auto [i, mp] : enumerate(modifierPos)) {
1564
		if ((lockKeysMask >> i) & 1) {
1565
			if (down) {
1566
				// press lock key
1567
				typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1568
			} else {
1569
				// release lock key
1570
				typeKeyMatrix[mp.getRow()] |= mp.getMask();
1571
			}
1572
		}
1573
	}
1574
	keysChanged = true;
1575
}
1576
1577
/*
1578
 * Check if there are common keys in the MSX matrix for
1579
 * two different unicodes.
1580
 * It is used by the 'insert keys' function to determine if it has to wait for
1581
 * a short while after releasing a key (to enter a certain character) before
1582
 * pressing the next key (to enter the next character)
1583
 */
1584
bool Keyboard::commonKeys(unsigned unicode1, unsigned unicode2) const
1585
{
1586
	// get row / mask of key (note: ignore modifier mask)
1587
	auto keyPos1 = unicodeKeymap.get(unicode1).pos;
1588
	auto keyPos2 = unicodeKeymap.get(unicode2).pos;
1589
1590
	return keyPos1 == keyPos2 && keyPos1.isValid();
1591
}
1592
1593
void Keyboard::debug(const char* format, ...) const
1594
{
1595
	if (keyboardSettings.getTraceKeyPresses()) {
1596
		va_list args;
1597
		va_start(args, format);
1598
		vfprintf(stderr, format, args);
1599
		va_end(args);
1600
	}
1601
}
1602
1603
1604
// class KeyMatrixUpCmd
1605
1606
Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd(
1607
		CommandController& commandController_,
1608
		StateChangeDistributor& stateChangeDistributor_,
1609
		Scheduler& scheduler_)
1610
	: RecordedCommand(commandController_, stateChangeDistributor_,
1611
		scheduler_, "keymatrixup")
1612
{
1613
}
1614
1615
void Keyboard::KeyMatrixUpCmd::execute(
1616
	std::span<const TclObject> tokens, TclObject& /*result*/, EmuTime /*time*/)
1617
{
1618
	checkNumArgs(tokens, 3, Prefix{1}, "row mask");
1619
	auto& keyboard = OUTER(Keyboard, keyMatrixUpCmd);
1620
	keyboard.processCmd(getInterpreter(), tokens, true);
1621
}
1622
1623
std::string Keyboard::KeyMatrixUpCmd::help(std::span<const TclObject> /*tokens*/) const
1624
{
1625
	return "keymatrixup <row> <bitmask>  release a key in the keyboard matrix\n";
1626
}
1627
1628
1629
// class KeyMatrixDownCmd
1630
1631
Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController_,
1632
		StateChangeDistributor& stateChangeDistributor_,
1633
		Scheduler& scheduler_)
1634
	: RecordedCommand(commandController_, stateChangeDistributor_,
1635
		scheduler_, "keymatrixdown")
1636
{
1637
}
1638
1639
void Keyboard::KeyMatrixDownCmd::execute(std::span<const TclObject> tokens,
1640
                                         TclObject& /*result*/, EmuTime /*time*/)
1641
{
1642
	checkNumArgs(tokens, 3, Prefix{1}, "row mask");
1643
	auto& keyboard = OUTER(Keyboard, keyMatrixDownCmd);
1644
	keyboard.processCmd(getInterpreter(), tokens, false);
1645
}
1646
1647
std::string Keyboard::KeyMatrixDownCmd::help(std::span<const TclObject> /*tokens*/) const
1648
{
1649
	return "keymatrixdown <row> <bitmask>  press a key in the keyboard matrix\n";
1650
}
1651
1652
1653
// class MsxKeyEventQueue
1654
1655
Keyboard::MsxKeyEventQueue::MsxKeyEventQueue(
1656
		Scheduler& scheduler_, Interpreter& interp_)
1657
	: Schedulable(scheduler_)
1658
	, interp(interp_)
1659
{
1660
}
1661
1662
void Keyboard::MsxKeyEventQueue::process_asap(
1663
	EmuTime time, const Event& event)
1664
{
1665
	bool processImmediately = eventQueue.empty();
1666
	eventQueue.push_back(event);
1667
	if (processImmediately) {
1668
		executeUntil(time);
1669
	}
1670
}
1671
1672
void Keyboard::MsxKeyEventQueue::clear()
1673
{
1674
	eventQueue.clear();
1675
	removeSyncPoint();
1676
}
1677
1678
void Keyboard::MsxKeyEventQueue::executeUntil(EmuTime time)
1679
{
1680
	// Get oldest event from the queue and process it
1681
	const Event& event = eventQueue.front();
1682
	auto& keyboard = OUTER(Keyboard, msxKeyEventQueue);
1683
	bool insertCodeKanaRelease = keyboard.processQueuedEvent(event, time);
1684
1685
	if (insertCodeKanaRelease) {
1686
		// The processor pressed the CODE/KANA key
1687
		// Schedule a CODE/KANA release event, to be processed
1688
		// before any of the other events in the queue
1689
		eventQueue.emplace_front(KeyUpEvent::create(keyboard.keyboardSettings.getCodeKanaHostKey()));
1690
	} else {
1691
		// The event has been completely processed. Delete it from the queue
1692
		if (!eventQueue.empty()) {
1693
			eventQueue.pop_front();
1694
		} else {
1695
			// it's possible clear() has been called
1696
			// (indirectly from keyboard.processQueuedEvent())
1697
		}
1698
	}
1699
1700
	if (!eventQueue.empty()) {
1701
		// There are still events. Process them in 1/15s from now
1702
		setSyncPoint(time + EmuDuration::hz(15));
1703
	}
1704
}
1705
1706
1707
// class KeyInserter
1708
1709
Keyboard::KeyInserter::KeyInserter(
1710
		CommandController& commandController_,
1711
		StateChangeDistributor& stateChangeDistributor_,
1712
		Scheduler& scheduler_)
1713
	: RecordedCommand(commandController_, stateChangeDistributor_,
1714
		scheduler_, "type_via_keyboard")
1715
	, Schedulable(scheduler_)
1716
{
1717
}
1718
1719
void Keyboard::KeyInserter::execute(
1720
	std::span<const TclObject> tokens, TclObject& /*result*/, EmuTime /*time*/)
1721
{
1722
	checkNumArgs(tokens, AtLeast{2}, "?-release? ?-freq hz? ?-cancel? text");
1723
1724
	bool cancel = false;
1725
	releaseBeforePress = false;
1726
	typingFrequency = 15;
1727
	std::array info = {
1728
		flagArg("-cancel", cancel),
1729
		flagArg("-release", releaseBeforePress),
1730
		valueArg("-freq", typingFrequency),
1731
	};
1732
	auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(1), info);
1733
1734
	if (typingFrequency <= 0) {
1735
		throw CommandException("Wrong argument for -freq (should be a positive number)");
1736
	}
1737
	if (cancel) {
1738
		text_utf8.clear();
1739
		return;
1740
	}
1741
1742
	if (arguments.size() != 1) throw SyntaxError();
1743
1744
	type(arguments[0].getString());
1745
}
1746
1747
std::string Keyboard::KeyInserter::help(std::span<const TclObject> /*tokens*/) const
1748
{
1749
	return "Type a string in the emulated MSX.\n"
1750
	       "Use -release to make sure the keys are always released before typing new ones (necessary for some game input routines, but in general, this means typing is twice as slow).\n"
1751
	       "Use -freq to tweak how fast typing goes and how long the keys will be pressed (and released in case -release was used). Keys will be typed at the given frequency and will remain pressed/released for 1/freq seconds\n"
1752
	       "Use -cancel to cancel a (long) in-progress type command.";
1753
}
1754
1755
void Keyboard::KeyInserter::tabCompletion(std::vector<std::string>& tokens) const
1756
{
1757
	using namespace std::literals;
1758
	static constexpr std::array options = {"-release"sv, "-freq"sv};
1759
	completeString(tokens, options);
1760
}
1761
1762
void Keyboard::KeyInserter::type(std::string_view str)
1763
{
1764
	if (str.empty()) {
1765
		return;
1766
	}
1767
	const auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1768
	oldLocksOn = keyboard.locksOn;
1769
	if (text_utf8.empty()) {
1770
		reschedule(getCurrentTime());
1771
	}
1772
	text_utf8.append(str.data(), str.size());
1773
}
1774
1775
void Keyboard::KeyInserter::executeUntil(EmuTime time)
1776
{
1777
	auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1778
	if (lockKeysMask != 0) {
1779
		// release CAPS and/or Code/Kana Lock keys
1780
		keyboard.pressLockKeys(lockKeysMask, false);
1781
	}
1782
	if (releaseLast) {
1783
		keyboard.pressAscii(last, false); // release previous character
1784
	}
1785
	if (text_utf8.empty()) {
1786
		releaseLast = false;
1787
		keyboard.debug("Restoring locks: %02X -> %02X\n", keyboard.locksOn, oldLocksOn);
1788
		uint8_t diff = oldLocksOn ^ keyboard.locksOn;
1789
		lockKeysMask = diff;
1790
		if (diff != 0) {
1791
			// press CAPS, GRAPH and/or Code/Kana Lock keys
1792
			keyboard.locksOn ^= diff;
1793
			keyboard.pressLockKeys(diff, true);
1794
			reschedule(time);
1795
		}
1796
		return;
1797
	}
1798
1799
	try {
1800
		auto it = begin(text_utf8);
1801
		unsigned current = utf8::next(it, end(text_utf8));
1802
		if (releaseLast && (releaseBeforePress || keyboard.commonKeys(last, current))) {
1803
			// There are common keys between previous and current character
1804
			// Do not immediately press again but give MSX the time to notice
1805
			// that the keys have been released
1806
			releaseLast = false;
1807
		} else {
1808
			// All keys in current char differ from previous char. The new keys
1809
			// can immediately be pressed
1810
			lockKeysMask = keyboard.pressAscii(current, true);
1811
			if (lockKeysMask == 0) {
1812
				last = current;
1813
				releaseLast = true;
1814
				text_utf8.erase(begin(text_utf8), it);
1815
			} else if (lockKeysMask & TRY_AGAIN) {
1816
				lockKeysMask &= ~TRY_AGAIN;
1817
				releaseLast = false;
1818
			} else if (releaseBeforePress) {
1819
				releaseLast = true;
1820
			}
1821
		}
1822
		reschedule(time);
1823
	} catch (std::exception&) {
1824
		// utf8 encoding error
1825
		text_utf8.clear();
1826
	}
1827
}
1828
1829
void Keyboard::KeyInserter::reschedule(EmuTime time)
1830
{
1831
	setSyncPoint(time + EmuDuration::hz(typingFrequency));
1832
}
1833
1834
1835
// Commands for conversion between msxcode <-> unicode.
1836
1837
Keyboard::Msxcode2UnicodeCmd::Msxcode2UnicodeCmd(CommandController& commandController_)
1838
	: Command(commandController_, "msxcode2unicode")
1839
{
1840
}
1841
1842
void Keyboard::Msxcode2UnicodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1843
{
1844
	checkNumArgs(tokens, Between{2, 3}, "msx-string ?fallback?");
1845
1846
	auto& interp = getInterpreter();
1847
	const auto& keyboard = OUTER(Keyboard, msxcode2UnicodeCmd);
1848
	const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1849
1850
	auto msx = tokens[1].getBinary(getInterpreter());
1851
	auto fallback = [&] -> std::function<uint32_t(uint8_t)> {
1852
		if (tokens.size() < 3) {
1853
			// If no fallback is given use space as replacement character
1854
			return [](uint8_t) { return uint32_t(' '); };
1855
		} else if (auto i = tokens[2].getOptionalInt()) {
1856
			// If an integer is given use that as a unicode character number
1857
			return [i = *i](uint8_t) { return uint32_t(i); };
1858
		} else {
1859
			// Otherwise use the given string as the name of a Tcl proc,
1860
			// That proc is (possibly later) invoked with a msx-character as input,
1861
			// and it should return the replacement unicode character number.
1862
			return [&](uint8_t m) {
1863
				TclObject cmd{TclObject::MakeListTag{}, tokens[2], m};
1864
				return uint32_t(cmd.executeCommand(interp).getInt(interp));
1865
			};
1866
		}
1867
	}();
1868
1869
	result = msxChars.msxToUtf8(msx, fallback);
1870
}
1871
1872
std::string Keyboard::Msxcode2UnicodeCmd::help(std::span<const TclObject> /*tokens*/) const
1873
{
1874
	return "msxcode2unicode <msx-string> [<fallback>]\n"
1875
	       "returns a unicode string converted from an MSX-string, i.e. a string based on\n"
1876
	       "MSX character codes.\n"
1877
	       "The optional fallback used for each character that cannot be mapped for the\n"
1878
	       "current MSX model can be:\n"
1879
	       "- omitted: then space will be used as fallback character.\n"
1880
	       "- an integer number: then this number will be used as unicode point to be the\n"
1881
	       "  the fallback character.\n"
1882
	       "- a Tcl proc, which expects one input character and must return one unicode\n"
1883
	       "  point.";
1884
}
1885
1886
1887
Keyboard::Unicode2MsxcodeCmd::Unicode2MsxcodeCmd(CommandController& commandController_)
1888
	: Command(commandController_, "unicode2msxcode")
1889
{
1890
}
1891
1892
void Keyboard::Unicode2MsxcodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1893
{
1894
	checkNumArgs(tokens, Between{2, 3}, "unicode-string ?fallback?");
1895
1896
	auto& interp = getInterpreter();
1897
	const auto& keyboard = OUTER(Keyboard, unicode2MsxcodeCmd);
1898
	const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1899
1900
	const auto& unicode = tokens[1].getString();
1901
	auto fallback = [&] -> std::function<uint8_t(uint32_t)> {
1902
		if (tokens.size() < 3) {
1903
			// If no fallback is given use space as replacement character
1904
			return [](uint32_t) { return uint8_t(' '); };
1905
		} else if (auto i = tokens[2].getOptionalInt()) {
1906
			// If an integer is given use that as a MSX character number
1907
			return [i = *i](uint32_t) { return uint8_t(i); };
1908
		} else {
1909
			// Otherwise use the given string as the name of a Tcl proc,
1910
			// That proc is (possibly later) invoked with a unicode character as
1911
			// input, and it should return the replacement MSX character number.
1912
			return [&](uint32_t u) {
1913
				TclObject cmd{TclObject::MakeListTag{}, tokens[2], u};
1914
				return uint8_t(cmd.executeCommand(interp).getInt(interp));
1915
			};
1916
		}
1917
	}();
1918
1919
	result = msxChars.utf8ToMsx(unicode, fallback);
1920
}
1921
1922
std::string Keyboard::Unicode2MsxcodeCmd::help(std::span<const TclObject> /*tokens*/) const
1923
{
1924
	return "unicode2msxcode <unicode-string> [<fallback>]\n"
1925
	       "returns an MSX string, i.e. a string based on MSX character codes, converted\n"
1926
	       "from a unicode string.\n"
1927
	       "The optional fallback used for each character that cannot be mapped for the\n"
1928
	       "current MSX model can be:\n"
1929
	       "- omitted: then space will be used as fallback character.\n"
1930
	       "- an integer number: then this number will be used as MSX character number to\n"
1931
	       "  to be the fallback character.\n"
1932
	       "- a Tcl proc, which expects one input character and must return one MSX\n"
1933
	       "  character number.";
1934
}
1935
1936
1937
/*
1938
 * class CapsLockAligner
1939
 *
1940
 * It is used to align MSX CAPS lock status with the host CAPS lock status
1941
 * during the reset of the MSX or after the openMSX window regains focus.
1942
 *
1943
 * It listens to the 'BOOT' event and schedules the real alignment
1944
 * 2 seconds later. Reason is that it takes a while before the MSX
1945
 * reset routine starts monitoring the MSX keyboard.
1946
 *
1947
 * For focus regain, the alignment is done immediately.
1948
 */
1949
Keyboard::CapsLockAligner::CapsLockAligner(
1950
		EventDistributor& eventDistributor_,
1951
		Scheduler& scheduler_)
1952
	: Schedulable(scheduler_)
1953
	, eventDistributor(eventDistributor_)
1954
{
1955
	for (auto type : {EventType::BOOT, EventType::WINDOW}) {
1956
		eventDistributor.registerEventListener(type,  *this);
1957
	}
1958
}
1959
1960
Keyboard::CapsLockAligner::~CapsLockAligner()
1961
{
1962
	for (auto type : {EventType::WINDOW, EventType::BOOT}) {
1963
		eventDistributor.unregisterEventListener(type,  *this);
1964
	}
1965
}
1966
1967
bool Keyboard::CapsLockAligner::signalEvent(const Event& event)
1968
{
1969
	if constexpr (!SANE_CAPSLOCK_BEHAVIOR) {
1970
		// don't even try
1971
		return false;
1972
	}
1973
1974
	if (state == IDLE) {
1975
		EmuTime time = getCurrentTime();
1976
		std::visit(overloaded{
1977
			[&](const WindowEvent& e) {
1978
				if (e.isMainWindow()) {
1979
					const auto& evt = e.getSdlWindowEvent();
1980
					if (evt.event == one_of(SDL_WINDOWEVENT_FOCUS_GAINED, SDL_WINDOWEVENT_FOCUS_LOST)) {
1981
						alignCapsLock(time);
1982
					}
1983
				}
1984
			},
1985
			[&](const BootEvent&) {
1986
				state = MUST_ALIGN_CAPSLOCK;
1987
				setSyncPoint(time + EmuDuration::sec(2)); // 2s (MSX time)
1988
			},
1989
			[](const EventBase&) {
1990
				// correct but causes excessive clang compile-time
1991
				// UNREACHABLE;
1992
			}
1993
		}, event);
1994
	}
1995
	return false;
1996
}
1997
1998
void Keyboard::CapsLockAligner::executeUntil(EmuTime time)
1999
{
2000
	switch (state) {
2001
		case MUST_ALIGN_CAPSLOCK:
2002
			alignCapsLock(time);
2003
			break;
2004
		case MUST_DISTRIBUTE_KEY_RELEASE: {
2005
			auto& keyboard = OUTER(Keyboard, capsLockAligner);
2006
			auto event = KeyUpEvent::create(SDLK_CAPSLOCK);
2007
			keyboard.msxEventDistributor.distributeEvent(event, time);
2008
			state = IDLE;
2009
			break;
2010
		}
2011
		default:
2012
			UNREACHABLE;
2013
	}
2014
}
2015
2016
/*
2017
 * Align MSX caps lock state with host caps lock state
2018
 * WARNING: This function assumes that the MSX will see and
2019
 *          process the caps lock key press.
2020
 *          If MSX misses the key press for whatever reason (e.g.
2021
 *          interrupts are disabled), the caps lock state in this
2022
 *          module will mismatch with the real MSX caps lock state
2023
 * TODO: Find a solution for the above problem. For example by monitoring
2024
 *       the MSX caps-lock LED state.
2025
 */
2026
void Keyboard::CapsLockAligner::alignCapsLock(EmuTime time)
2027
{
2028
	bool hostCapsLockOn = ((SDL_GetModState() & KMOD_CAPS) != 0);
2029
	auto& keyboard = OUTER(Keyboard, capsLockAligner);
2030
	if (bool(keyboard.locksOn & KeyInfo::CAPS_MASK) != hostCapsLockOn) {
2031
		keyboard.debug("Resyncing host and MSX CAPS lock\n");
2032
		// note: send out another event iso directly calling
2033
		// processCapslockEvent() because we want this to be recorded
2034
		auto event = KeyDownEvent::create(SDLK_CAPSLOCK);
2035
		keyboard.msxEventDistributor.distributeEvent(event, time);
2036
		keyboard.debug("Sending fake CAPS release\n");
2037
		state = MUST_DISTRIBUTE_KEY_RELEASE;
2038
		setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (MSX time)
2039
	} else {
2040
		state = IDLE;
2041
	}
2042
}
2043
2044
#if defined(_WIN32)
2045
// class ImeManager
2046
2047
/*  FOR WINDOWS:
2048
 *  To work around a Japanese keyboard Kanji mode bug. (Multi-character
2049
 *	input makes a keydown event without keyrelease message.)
2050
 */
2051
Keyboard::ImeManager::ImeManager(
2052
		EventDistributor& eventDistributor_)
2053
	: eventDistributor(eventDistributor_)
2054
{
2055
	eventDistributor.registerEventListener(EventType::WINDOW, *this);
2056
	hImc = 0;
2057
}
2058
2059
Keyboard::ImeManager::~ImeManager()
2060
{
2061
	eventDistributor.unregisterEventListener(EventType::WINDOW, *this);
2062
}
2063
2064
/*
2065
 *  disable IME at msx window.
2066
 *  -> SDL_WINDOWEVENT_FOCUS_GAINED : IME OFF
2067
 */
2068
bool Keyboard::ImeManager::signalEvent(const Event& event)
2069
{
2070
	auto getWindow = [](const WindowEvent& e) -> HWND {
2071
		SDL_SysWMinfo info;
2072
		SDL_VERSION(&info.version);
2073
		auto window = SDL_GetWindowFromID(e.getMainWindowId());
2074
		if (SDL_GetWindowWMInfo(window, &info)) {
2075
			return info.info.win.window;
2076
		}
2077
		return nullptr;
2078
	};
2079
2080
	std::visit(overloaded{
2081
		[&](const WindowEvent& e) {
2082
			if (e.isMainWindow()) {
2083
				const auto& evt = e.getSdlWindowEvent();
2084
				if (evt.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
2085
					auto& keyboard = OUTER(Keyboard, imeManager);
2086
					if (keyboard.keyboardSettings.getDisableIME()) {
2087
						// disable IME when focus is gained
2088
						auto hwnd = getWindow(e);
2089
						if (hwnd) {
2090
							hImc = ::ImmAssociateContext(hwnd, 0);
2091
						}
2092
					}
2093
				}
2094
				else if (evt.event == SDL_WINDOWEVENT_FOCUS_LOST) {
2095
					auto hwnd = getWindow(e);
2096
					if (hwnd && hImc) {
2097
						hImc = ::ImmAssociateContext(hwnd, hImc);
2098
					}
2099
				}
2100
			}
2101
		},
2102
		[](const EventBase&) { UNREACHABLE; }
2103
	}, event);
2104
2105
	return false;
2106
}
2107
#endif
2108
2109
// class KeybDebuggable
2110
2111
Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard_)
2112
	: SimpleDebuggable(motherBoard_, "keymatrix", "MSX Keyboard Matrix",
2113
	                   KeyMatrixPosition::NUM_ROWS)
2114
{
2115
}
2116
2117
uint8_t Keyboard::KeybDebuggable::read(unsigned address)
2118
{
2119
	const auto& keyboard = OUTER(Keyboard, keybDebuggable);
2120
	return keyboard.getKeys()[address];
2121
}
2122
2123
void Keyboard::KeybDebuggable::readBlock(unsigned start, std::span<uint8_t> output)
2124
{
2125
	const auto& keyboard = OUTER(Keyboard, keybDebuggable);
2126
	copy_to_range(keyboard.getKeys().subspan(start, output.size()), output);
2127
}
2128
2129
void Keyboard::KeybDebuggable::write(unsigned /*address*/, uint8_t /*value*/)
2130
{
2131
	// ignore
2132
}
2133
2134
2135
template<typename Archive>
2136
void Keyboard::KeyInserter::serialize(Archive& ar, unsigned /*version*/)
2137
{
2138
	ar.template serializeBase<Schedulable>(*this);
2139
	ar.serialize("text", text_utf8,
2140
	             "last", last,
2141
	             "lockKeysMask", lockKeysMask,
2142
	             "releaseLast", releaseLast);
2143
2144
	bool oldCodeKanaLockOn, oldGraphLockOn, oldCapsLockOn;
2145
	if constexpr (!Archive::IS_LOADER) {
2146
		oldCodeKanaLockOn = oldLocksOn & KeyInfo::CODE_MASK;
2147
		oldGraphLockOn = oldLocksOn & KeyInfo::GRAPH_MASK;
2148
		oldCapsLockOn = oldLocksOn & KeyInfo::CAPS_MASK;
2149
	}
2150
	ar.serialize("oldCodeKanaLockOn", oldCodeKanaLockOn,
2151
	             "oldGraphLockOn",    oldGraphLockOn,
2152
	             "oldCapsLockOn",     oldCapsLockOn);
2153
	if constexpr (Archive::IS_LOADER) {
2154
		oldLocksOn = (oldCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
2155
		           | (oldGraphLockOn ? KeyInfo::GRAPH_MASK : 0)
2156
		           | (oldCapsLockOn ? KeyInfo::CAPS_MASK : 0);
2157
	}
2158
}
2159
2160
// version 1: Initial version: {userKeyMatrix, dynKeymap, msxModifiers,
2161
//            msxKeyEventQueue} was intentionally not serialized. The reason
2162
//            was that after a loadstate, you want the MSX keyboard to reflect
2163
//            the state of the host keyboard. So any pressed MSX keys from the
2164
//            time the savestate was created are cleared.
2165
// version 2: For reverse-replay it is important that snapshots contain the
2166
//            full state of the MSX keyboard, so now we do serialize it.
2167
// version 3: split cmdKeyMatrix into cmdKeyMatrix + typeKeyMatrix
2168
// version 4: changed 'dynKeymap' to 'lastUnicodeForKeycode'
2169
// version 5: changed 'lastUnicodeForKeycode' to 'lastUnicodeForScancode'
2170
// TODO Is the assumption in version 1 correct (clear keyb state on load)?
2171
//      If it is still useful for 'regular' loadstate, then we could implement
2172
//      it by explicitly clearing the keyb state from the actual loadstate
2173
//      command. (But let's only do this when experience shows it's really
2174
//      better).
2175
template<typename Archive>
2176
void Keyboard::serialize(Archive& ar, unsigned version)
2177
{
2178
	ar.serialize("keyTypeCmd", keyTypeCmd,
2179
	             "cmdKeyMatrix", cmdKeyMatrix);
2180
	if (ar.versionAtLeast(version, 3)) {
2181
		ar.serialize("typeKeyMatrix", typeKeyMatrix);
2182
	} else {
2183
		typeKeyMatrix = cmdKeyMatrix;
2184
	}
2185
2186
	bool msxCapsLockOn, msxCodeKanaLockOn, msxGraphLockOn;
2187
	if constexpr (!Archive::IS_LOADER) {
2188
		msxCapsLockOn = locksOn & KeyInfo::CAPS_MASK;
2189
		msxCodeKanaLockOn = locksOn & KeyInfo::CODE_MASK;
2190
		msxGraphLockOn = locksOn & KeyInfo::GRAPH_MASK;
2191
	}
2192
	ar.serialize("msxCapsLockOn",     msxCapsLockOn,
2193
	             "msxCodeKanaLockOn", msxCodeKanaLockOn,
2194
	             "msxGraphLockOn",    msxGraphLockOn);
2195
	if constexpr (Archive::IS_LOADER) {
2196
		locksOn = (msxCapsLockOn ? KeyInfo::CAPS_MASK : 0)
2197
		        | (msxCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
2198
		        | (msxGraphLockOn ? KeyInfo::GRAPH_MASK : 0);
2199
	}
2200
2201
	if (ar.versionAtLeast(version, 2)) {
2202
		ar.serialize("userKeyMatrix",    userKeyMatrix,
2203
		             "msxmodifiers",     msxModifiers,
2204
		             "msxKeyEventQueue", msxKeyEventQueue);
2205
	}
2206
	if (ar.versionAtLeast(version, 5)) {
2207
		ar.serialize("lastUnicodeForScancode", lastUnicodeForScancode);
2208
	}
2209
	else if (ar.versionAtLeast(version, 4)) {
2210
		// convert keycode to scancode
2211
		std::vector<std::pair<SDL_Keycode, uint32_t> > lastUnicodeForKeycode;
2212
		ar.serialize("lastUnicodeForKeycode", lastUnicodeForKeycode);
2213
		lastUnicodeForScancode.clear();
2214
		for (const auto& [keyCode, unicode] : lastUnicodeForKeycode) {
2215
			auto scanCode = SDL_GetScancodeFromKey(keyCode);
2216
			lastUnicodeForScancode.emplace_back(scanCode, unicode);
2217
		}
2218
	} else {
2219
		// We can't (easily) reconstruct 'lastUnicodeForKeycode' from
2220
		// 'dynKeymap'. Usually this won't cause problems. E.g.
2221
		// typically you aren't typing at the same time that you create
2222
		// a savestate. For replays it might matter, though usually
2223
		// replays are about games, and typing in games is rare (for
2224
		// cursors and space this isn't needed).
2225
		//
2226
		// So, at least for now, it's fine to not reconstruct this data.
2227
	}
2228
	// don't serialize hostKeyMatrix
2229
2230
	if constexpr (Archive::IS_LOADER) {
2231
		// force recalculation of keyMatrix
2232
		keysChanged = true;
2233
	}
2234
}
2235
INSTANTIATE_SERIALIZE_METHODS(Keyboard);
2236
2237
template<typename Archive>
2238
void Keyboard::MsxKeyEventQueue::serialize(Archive& ar, unsigned /*version*/)
2239
{
2240
	ar.template serializeBase<Schedulable>(*this);
2241
2242
	// serialization of deque<Event> is not directly
2243
	// supported by the serialization framework (main problem is the
2244
	// constness, collections of shared_ptr to polymorphic objects are
2245
	// not a problem). Worked around this by serializing the events in
2246
	// ascii format. (In all practical cases this queue will anyway be
2247
	// empty or contain very few elements).
2248
	//ar.serialize("eventQueue", eventQueue);
2249
	std::vector<std::string> eventStrs;
2250
	if constexpr (!Archive::IS_LOADER) {
2251
		eventStrs = to_vector(std::views::transform(
2252
			eventQueue, [](const auto& e) { return toString(e); }));
2253
	}
2254
	ar.serialize("eventQueue", eventStrs);
2255
	if constexpr (Archive::IS_LOADER) {
2256
		assert(eventQueue.empty());
2257
		for (const auto& s : eventStrs) {
2258
			eventQueue.push_back(
2259
				InputEventFactory::createInputEvent(s, interp));
2260
		}
2261
	}
2262
}
2263
INSTANTIATE_SERIALIZE_METHODS(Keyboard::MsxKeyEventQueue);
2264
2265
} // namespace openmsx