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