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