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.registerKeyboard(*this);
737}
738
740{
741 auto& motherBoard = keybDebuggable.getMotherBoard();
742 motherBoard.unregisterKeyboard(*this);
743
744 stateChangeDistributor.unregisterListener(*this);
745 msxEventDistributor.unregisterEventListener(*this);
746}
747
749{
750 return unicodeKeymap.getMsxChars();
751}
752
753static constexpr void doKeyGhosting(std::span<uint8_t, KeyMatrixPosition::NUM_ROWS> matrix,
754 bool protectRow6)
755{
756 // This routine enables key-ghosting as seen on a real MSX
757 //
758 // If on a real MSX in the keyboard matrix the
759 // real buttons are pressed as in the left matrix
760 // then the matrix to the
761 // 10111111 right will be read by 10110101
762 // 11110101 because of the simple 10110101
763 // 10111101 electrical connections 10110101
764 // that are established by
765 // the closed switches
766 // However, some MSX models have protection against
767 // key-ghosting for SHIFT, GRAPH and CODE keys
768 // On those models, SHIFT, GRAPH and CODE are
769 // connected to row 6 via a diode. It prevents that
770 // SHIFT, GRAPH and CODE get ghosted to another
771 // row.
772 bool changedSomething = false;
773 do {
774 changedSomething = false;
775 // TODO: On Sega keyboards, ghosting should probably be done separately
776 // for rows 0..6 and 7..14, since they're connected to different
777 // PPI ports.
778 for (auto i : xrange(KeyMatrixPosition::NUM_ROWS - 1)) {
779 auto row1 = matrix[i];
780 for (auto j : xrange(i + 1, KeyMatrixPosition::NUM_ROWS)) {
781 auto row2 = matrix[j];
782 if ((row1 != row2) && ((row1 | row2) != 0xff)) {
783 auto rowIold = matrix[i];
784 auto rowJold = matrix[j];
785 // TODO: The shift/graph/code key ghosting protection
786 // implementation is only correct for MSX.
787 if (protectRow6 && i == 6) {
788 matrix[i] = row1 & row2;
789 matrix[j] = (row1 | 0x15) & row2;
790 row1 &= row2;
791 } else if (protectRow6 && j == 6) {
792 matrix[i] = row1 & (row2 | 0x15);
793 matrix[j] = row1 & row2;
794 row1 &= (row2 | 0x15);
795 } else {
796 // not same and some common zero's
797 // --> inherit other zero's
798 uint8_t newRow = row1 & row2;
799 matrix[i] = newRow;
800 matrix[j] = newRow;
801 row1 = newRow;
802 }
803 if (rowIold != matrix[i] ||
804 rowJold != matrix[j]) {
805 changedSomething = true;
806 }
807 }
808 }
809 }
810 } while (changedSomething);
811}
812
813std::span<const uint8_t, KeyMatrixPosition::NUM_ROWS> Keyboard::getKeys() const
814{
815 if (keysChanged) {
816 keysChanged = false;
817 std::span matrix = keyTypeCmd.isActive() ? typeKeyMatrix : userKeyMatrix;
818 for (auto row : xrange(KeyMatrixPosition::NUM_ROWS)) {
819 keyMatrix[row] = cmdKeyMatrix[row] & matrix[row];
820 }
821 if (keyGhosting) {
822 doKeyGhosting(keyMatrix, keyGhostingSGCprotected);
823 }
824 }
825 return keyMatrix;
826}
827
829{
830 // This mechanism exists to solve the following problem:
831 // - play a game where the spacebar is constantly pressed (e.g.
832 // Road Fighter)
833 // - go back in time (press the reverse hotkey) while keeping the
834 // spacebar pressed
835 // - interrupt replay by pressing the cursor keys, still while
836 // keeping spacebar pressed
837 // At the moment replay is interrupted, we need to resynchronize the
838 // msx keyboard with the host keyboard. In the past we assumed the host
839 // keyboard had no keys pressed. But this is wrong in the above
840 // scenario. Now we remember the state of the host keyboard and
841 // transfer that to the new keyboard(s) that get created for reverse.
842 // When replay is stopped we restore this host keyboard state, see
843 // stopReplay().
844
845 for (auto row : xrange(KeyMatrixPosition::NUM_ROWS)) {
846 hostKeyMatrix[row] = source.hostKeyMatrix[row];
847 }
848}
849
850void Keyboard::setFocus(bool newFocus, EmuTime::param time)
851{
852 if (newFocus == focus) return;
853 focus = newFocus;
854
855 syncHostKeyMatrix(time); // release all keys on lost focus
856}
857
858/* Received an MSX event
859 * Following events get processed:
860 * EventType::KEY_DOWN
861 * EventType::KEY_UP
862 */
863void Keyboard::signalMSXEvent(const Event& event,
864 EmuTime::param time) noexcept
865{
867 const auto& keyEvent = get_event<KeyEvent>(event);
868 if (keyEvent.getRepeat()) return;
869 // Ignore possible console on/off events:
870 // we do not re-scan the keyboard since this may lead to
871 // an unwanted pressing of <return> in MSX after typing
872 // "set console off" in the console.
873 msxKeyEventQueue.process_asap(time, event);
874 }
875}
876
877void Keyboard::signalStateChange(const StateChange& event)
878{
879 const auto* kms = dynamic_cast<const KeyMatrixState*>(&event);
880 if (!kms) return;
881
882 userKeyMatrix[kms->getRow()] &= uint8_t(~kms->getPress());
883 userKeyMatrix[kms->getRow()] |= kms->getRelease();
884 keysChanged = true; // do ghosting at next getKeys()
885}
886
887void Keyboard::stopReplay(EmuTime::param time) noexcept
888{
889 syncHostKeyMatrix(time);
890}
891
892void Keyboard::syncHostKeyMatrix(EmuTime::param time)
893{
894 for (auto [row, hkm] : enumerate(hostKeyMatrix)) {
895 changeKeyMatrixEvent(time, uint8_t(row), hkm);
896 }
897 msxModifiers = 0xff;
898 msxKeyEventQueue.clear();
899 lastUnicodeForKeycode.clear();
900}
901
902uint8_t Keyboard::needsLockToggle(const UnicodeKeymap::KeyInfo& keyInfo) const
903{
904 return modifierIsLock
905 & (locksOn ^ keyInfo.modMask)
906 & unicodeKeymap.getRelevantMods(keyInfo);
907}
908
909void Keyboard::pressKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
910{
911 if (!pos.isValid()) {
912 // No such key.
913 return;
914 }
915 auto row = pos.getRow();
916 auto press = pos.getMask();
917 if (((hostKeyMatrix[row] & press) == 0) &&
918 ((userKeyMatrix[row] & press) == 0)) {
919 // Won't have any effect, ignore.
920 return;
921 }
922 changeKeyMatrixEvent(time, row, hostKeyMatrix[row] & ~press);
923}
924
925void Keyboard::releaseKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
926{
927 if (!pos.isValid()) {
928 // No such key.
929 return;
930 }
931 auto row = pos.getRow();
932 auto release = pos.getMask();
933 if (((hostKeyMatrix[row] & release) == release) &&
934 ((userKeyMatrix[row] & release) == release)) {
935 // Won't have any effect, ignore.
936 // Test scenario: during replay, exit the openmsx console with
937 // the 'toggle console' command. The 'enter,release' event will
938 // end up here. But it shouldn't stop replay.
939 return;
940 }
941 changeKeyMatrixEvent(time, row, hostKeyMatrix[row] | release);
942}
943
944void Keyboard::changeKeyMatrixEvent(EmuTime::param time, uint8_t row, uint8_t newValue)
945{
946 if (!focus) newValue = 0xff;
947
948 // This method already updates hostKeyMatrix[],
949 // userKeyMatrix[] will soon be updated via KeyMatrixState events.
950 hostKeyMatrix[row] = newValue;
951
952 uint8_t diff = userKeyMatrix[row] ^ newValue;
953 if (diff == 0) return;
954 uint8_t press = userKeyMatrix[row] & diff;
955 uint8_t release = newValue & diff;
956 stateChangeDistributor.distributeNew<KeyMatrixState>(
957 time, row, press, release);
958}
959
960/*
961 * @return True iff a release event for the CODE/KANA key must be scheduled.
962 */
963bool Keyboard::processQueuedEvent(const Event& event, EmuTime::param time)
964{
965 auto mode = keyboardSettings.getMappingMode();
966
967 const auto& keyEvent = get_event<KeyEvent>(event);
968 bool down = getType(event) == EventType::KEY_DOWN;
969 auto key = keyEvent.getKey();
970
971 if (down) {
972 // TODO: refactor debug(...) method to expect a std::string and then adapt
973 // all invocations of it to provide a properly formatted string, using the C++
974 // features for it.
975 // Once that is done, debug(...) can pass the c_str() version of that string
976 // to ad_printf(...) so that I don't have to make an explicit ad_printf(...)
977 // invocation for each debug(...) invocation
978 ad_printf("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n",
979 keyEvent.getUnicode(),
980 keyEvent.getKeyCode(),
981 key.toString().c_str());
982 debug("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n",
983 keyEvent.getUnicode(),
984 keyEvent.getKeyCode(),
985 key.toString().c_str());
986 } else {
987 ad_printf("Key released, keyCode: 0x%05x, keyName: %s\n",
988 keyEvent.getKeyCode(),
989 key.toString().c_str());
990 debug("Key released, keyCode: 0x%05x, keyName: %s\n",
991 keyEvent.getKeyCode(),
992 key.toString().c_str());
993 }
994
995 // Process dead keys.
997 for (auto n : xrange(3)) {
998 if (key.sym.sym == keyboardSettings.getDeadKeyHostKey(n)) {
999 UnicodeKeymap::KeyInfo deadKey = unicodeKeymap.getDeadKey(n);
1000 if (deadKey.isValid()) {
1001 updateKeyMatrix(time, down, deadKey.pos);
1002 return false;
1003 }
1004 }
1005 }
1006 }
1007
1008 if (key.sym.sym == SDLK_CAPSLOCK) {
1009 processCapslockEvent(time, down);
1010 return false;
1011 } else if (key.sym.sym == keyboardSettings.getCodeKanaHostKey()) {
1012 processCodeKanaChange(time, down);
1013 return false;
1014 } else if (key.sym.sym == SDLK_LALT) {
1015 processGraphChange(time, down);
1016 return false;
1017 } else if (key.sym.sym == SDLK_KP_ENTER) {
1018 processKeypadEnterKey(time, down);
1019 return false;
1020 } else {
1021 return processKeyEvent(time, down, keyEvent);
1022 }
1023}
1024
1025/*
1026 * Process a change (up or down event) of the CODE/KANA key
1027 * It presses or releases the key in the MSX keyboard matrix
1028 * and changes the kana-lock state in case of a press
1029 */
1030void Keyboard::processCodeKanaChange(EmuTime::param time, bool down)
1031{
1032 if (down) {
1033 locksOn ^= KeyInfo::CODE_MASK;
1034 }
1035 updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::CODE]);
1036}
1037
1038/*
1039 * Process a change (up or down event) of the GRAPH key
1040 * It presses or releases the key in the MSX keyboard matrix
1041 * and changes the graph-lock state in case of a press
1042 */
1043void Keyboard::processGraphChange(EmuTime::param time, bool down)
1044{
1045 if (down) {
1046 locksOn ^= KeyInfo::GRAPH_MASK;
1047 }
1048 updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::GRAPH]);
1049}
1050
1051/*
1052 * Process a change (up or down event) of the CAPSLOCK key
1053 * It presses or releases the key in the MSX keyboard matrix
1054 * and changes the capslock state in case of a press
1055 */
1056void Keyboard::processCapslockEvent(EmuTime::param time, bool down)
1057{
1058 if (SANE_CAPSLOCK_BEHAVIOR) {
1059 debug("Changing CAPS lock state according to SDL request\n");
1060 if (down) {
1061 locksOn ^= KeyInfo::CAPS_MASK;
1062 }
1063 updateKeyMatrix(time, down, modifierPos[KeyInfo::Modifier::CAPS]);
1064 } else {
1065 debug("Pressing CAPS lock and scheduling a release\n");
1066 locksOn ^= KeyInfo::CAPS_MASK;
1067 updateKeyMatrix(time, true, modifierPos[KeyInfo::Modifier::CAPS]);
1068 setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (in MSX time)
1069 }
1070}
1071
1072void Keyboard::executeUntil(EmuTime::param time)
1073{
1074 debug("Releasing CAPS lock\n");
1075 updateKeyMatrix(time, false, modifierPos[KeyInfo::Modifier::CAPS]);
1076}
1077
1078void Keyboard::processKeypadEnterKey(EmuTime::param time, bool down)
1079{
1080 if (!hasKeypad && !keyboardSettings.getAlwaysEnableKeypad()) {
1081 // User entered on host keypad but this MSX model does not have one
1082 // Ignore the keypress/release
1083 return;
1084 }
1085 processSdlKey(time,
1087 ? SDLK_KP_ENTER : SDLK_RETURN,
1088 down));
1089}
1090
1091/*
1092 * Process an SDL key press/release event. It concerns a
1093 * special key (e.g. SHIFT, UP, DOWN, F1, F2, ...) that can not
1094 * be unambiguously derived from a unicode character;
1095 * Map the SDL key to an equivalent MSX key press/release event
1096 */
1097void Keyboard::processSdlKey(EmuTime::param time, SDLKey key)
1098{
1099 auto process = [&](KeyMatrixPosition pos) {
1100 assert(pos.isValid());
1101 if (pos.getRow() == 11 && blockRow11) {
1102 // do not process row 11 if we have no Yes/No keys
1103 return;
1104 }
1105 updateKeyMatrix(time, key.down, pos);
1106 };
1107
1109 if (const auto* mapping = binary_find(scanCodeTab, key.sym.scancode, {}, &ScanCodeMsxMapping::hostScanCode)) {
1110 process(mapping->msx);
1111 }
1112 } else {
1113 if (const auto* mapping = binary_find(keyCodeTab, key.sym.sym, {}, &KeyCodeMsxMapping::hostKeyCode)) {
1114 process(mapping->msx);
1115 }
1116 }
1117}
1118
1119/*
1120 * Update the MSX keyboard matrix
1121 */
1122void Keyboard::updateKeyMatrix(EmuTime::param time, bool down, KeyMatrixPosition pos)
1123{
1124 if (!pos.isValid()) {
1125 // No such key.
1126 return;
1127 }
1128 if (down) {
1129 pressKeyMatrixEvent(time, pos);
1130 // Keep track of the MSX modifiers.
1131 // The MSX modifiers sometimes get overruled by the unicode character
1132 // processing, in which case the unicode processing must be able to
1133 // restore them to the real key-combinations pressed by the user.
1134 for (auto [i, mp] : enumerate(modifierPos)) {
1135 if (pos == mp) {
1136 msxModifiers &= uint8_t(~(1 << i));
1137 }
1138 }
1139 } else {
1140 releaseKeyMatrixEvent(time, pos);
1141 for (auto [i, mp] : enumerate(modifierPos)) {
1142 if (pos == mp) {
1143 msxModifiers |= 1 << i;
1144 }
1145 }
1146 }
1147}
1148
1149/*
1150 * Process an SDL key event;
1151 * Check if it is a special key, in which case it can be directly
1152 * mapped to the MSX matrix.
1153 * Otherwise, retrieve the unicode character value for the event
1154 * and map the unicode character to the key-combination that must
1155 * be pressed to generate the equivalent character on the MSX
1156 * @return True iff a release event for the CODE/KANA key must be scheduled.
1157 */
1158bool Keyboard::processKeyEvent(EmuTime::param time, bool down, const KeyEvent& keyEvent)
1159{
1160 auto mode = keyboardSettings.getMappingMode();
1161
1162 auto keyCode = keyEvent.getKeyCode();
1163 auto key = keyEvent.getKey();
1164
1165 bool isOnKeypad =
1166 (SDLK_KP_1 <= keyCode && keyCode <= SDLK_KP_0) || // note order is 1-9,0
1167 (keyCode == one_of(SDLK_KP_PERIOD, SDLK_KP_DIVIDE, SDLK_KP_MULTIPLY,
1168 SDLK_KP_MINUS, SDLK_KP_PLUS));
1169
1170 if (isOnKeypad && !hasKeypad &&
1171 !keyboardSettings.getAlwaysEnableKeypad()) {
1172 // User entered on host keypad but this MSX model does not have one
1173 // Ignore the keypress/release
1174 return false;
1175 }
1176#if defined(__APPLE__)
1177 bool positional = mode == KeyboardSettings::MappingMode::POSITIONAL;
1178 if ((key.sym.mod & KMOD_GUI) &&
1179 (( positional && (keyEvent.getScanCode() == SDL_SCANCODE_I)) ||
1180 (!positional && (keyCode == SDLK_i)))) {
1181 // Apple keyboards don't have an Insert key, use Cmd+I as an alternative.
1182 keyCode = SDLK_INSERT;
1183 key.sym.sym = SDLK_INSERT;
1184 key.sym.scancode = SDL_SCANCODE_INSERT;
1185 key.sym.mod &= ~KMOD_GUI;
1186 key.sym.unused = 0; // unicode
1187 }
1188#endif
1189
1190 if (down) {
1191 UnicodeKeymap::KeyInfo keyInfo;
1192 unsigned unicode;
1193 if (isOnKeypad ||
1196 // User entered a key on numeric keypad or the driver is in
1197 // KEY/POSITIONAL mapping mode.
1198 // First option (keypad) maps to same unicode as some other key
1199 // combinations (e.g. digit on main keyboard or TAB/DEL).
1200 // Use unicode to handle the more common combination and use direct
1201 // matrix to matrix mapping for the exceptional cases (keypad).
1202 unicode = 0;
1203 } else {
1204 unicode = keyEvent.getUnicode();
1205 if ((unicode < 0x20) || ((0x7F <= unicode) && (unicode < 0xA0))) {
1206 // Control character in C0 or C1 range.
1207 // Use SDL's interpretation instead.
1208 unicode = 0;
1209 } else if (utf8::is_pua(unicode)) {
1210 // Code point in Private Use Area: undefined by Unicode,
1211 // so we rely on SDL's interpretation instead.
1212 // For example the Mac's cursor keys are in this range.
1213 unicode = 0;
1214 } else {
1215 keyInfo = unicodeKeymap.get(unicode);
1216 if (!keyInfo.isValid()) {
1217 // Unicode does not exist in our mapping; try to process
1218 // the key using its keycode instead.
1219 unicode = 0;
1220 }
1221 }
1222 }
1223
1224 // Remember which unicode character is currently derived
1225 // from this SDL key. It must be stored here (during key-press)
1226 // because during key-release SDL never returns the unicode
1227 // value (it always returns the value 0). But we must know
1228 // the unicode value in order to be able to perform the correct
1229 // key-combination-release in the MSX
1230 auto it = ranges::lower_bound(lastUnicodeForKeycode, keyCode, {}, &std::pair<SDL_Keycode, uint32_t>::first);
1231 if ((it != lastUnicodeForKeycode.end()) && (it->first == keyCode)) {
1232 // after a while we can overwrite existing elements, and
1233 // then we stop growing/reallocating this vector
1234 it->second = unicode;
1235 } else {
1236 // insert new element (in the right location)
1237 lastUnicodeForKeycode.emplace(it, keyCode, unicode);
1238 }
1239
1240 if (unicode == 0) {
1241 // It was an ambiguous key (numeric key-pad, CTRL+character)
1242 // or a special key according to SDL (like HOME, INSERT, etc)
1243 // or a first keystroke of a composed key
1244 // (e.g. altr-gr + = on azerty keyboard) or driver is in
1245 // direct SDL mapping mode:
1246 // Perform direct SDL matrix to MSX 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 return false;
1253 } else {
1254 // It is a unicode character; map it to the right key-combination
1255 return pressUnicodeByUser(time, keyInfo, unicode, true);
1256 }
1257 } else {
1258 // key was released
1259 auto it = ranges::lower_bound(lastUnicodeForKeycode, keyCode, {}, &std::pair<SDL_Keycode, uint32_t>::first);
1260 unsigned unicode = ((it != lastUnicodeForKeycode.end()) && (it->first == keyCode))
1261 ? it->second // get the unicode that was derived from this key
1262 : 0;
1263 if (unicode == 0) {
1264 // It was a special key, perform matrix to matrix mapping
1265 // But only when it is not a first keystroke of a
1266 // composed key
1267 if (!(key.sym.mod & KMOD_MODE)) {
1268 processSdlKey(time, key);
1269 }
1270 } else {
1271 // It was a unicode character; map it to the right key-combination
1272 pressUnicodeByUser(time, unicodeKeymap.get(unicode), unicode, false);
1273 }
1274 return false;
1275 }
1276}
1277
1278void Keyboard::processCmd(Interpreter& interp, std::span<const TclObject> tokens, bool up)
1279{
1280 unsigned row = tokens[1].getInt(interp);
1281 unsigned mask = tokens[2].getInt(interp);
1282 if (row >= KeyMatrixPosition::NUM_ROWS) {
1283 throw CommandException("Invalid row");
1284 }
1285 if (mask >= 256) {
1286 throw CommandException("Invalid mask");
1287 }
1288 if (up) {
1289 cmdKeyMatrix[row] |= narrow_cast<uint8_t>(mask);
1290 } else {
1291 cmdKeyMatrix[row] &= narrow_cast<uint8_t>(~mask);
1292 }
1293 keysChanged = true;
1294}
1295
1296/*
1297 * This routine processes unicode characters. It maps a unicode character
1298 * to the correct key-combination on the MSX.
1299 *
1300 * There are a few caveats with respect to the MSX and Host modifier keys
1301 * that you must be aware about if you want to understand why the routine
1302 * works as it works.
1303 *
1304 * Row 6 of the MSX keyboard matrix contains the MSX modifier keys:
1305 * CTRL, CODE, GRAPH and SHIFT
1306 *
1307 * The SHIFT key is also a modifier key on the host machine. However, the
1308 * SHIFT key behaviour can differ between HOST and MSX for all 'special'
1309 * characters (anything but A-Z).
1310 * For example, on AZERTY host keyboard, user presses SHIFT+& to make the '1'
1311 * On MSX QWERTY keyboard, the same key-combination leads to '!'.
1312 * So this routine must not only PRESS the SHIFT key when required according
1313 * to the unicode mapping table but it must also RELEASE the SHIFT key for all
1314 * these special keys when the user PRESSES the key/character.
1315 *
1316 * On the other hand, for A-Z, this routine must not touch the SHIFT key at all.
1317 * Otherwise it might give strange behaviour when CAPS lock is on (which also
1318 * acts as a key-modifier for A-Z). The routine can rely on the fact that
1319 * SHIFT+A-Z behaviour is the same on all host and MSX keyboards. It is
1320 * approximately the only part of keyboards that is de-facto standardized :-)
1321 *
1322 * For the other modifiers (CTRL, CODE and GRAPH), the routine must be able to
1323 * PRESS them when required but there is no need to RELEASE them during
1324 * character press. On the contrary; the host keys that map to CODE and GRAPH
1325 * do not work as modifiers on the host itself, so if the routine would release
1326 * them, it would give wrong result.
1327 * For example, 'ALT-A' on Host will lead to unicode character 'a', just like
1328 * only pressing the 'A' key. The MSX however must know about the difference.
1329 *
1330 * As a reminder: here is the build-up of row 6 of the MSX key matrix
1331 * 7 6 5 4 3 2 1 0
1332 * row 6 | F3 | F2 | F1 | code| caps|graph| ctrl|shift|
1333 */
1334bool Keyboard::pressUnicodeByUser(
1335 EmuTime::param time, UnicodeKeymap::KeyInfo keyInfo, unsigned unicode,
1336 bool down)
1337{
1338 bool insertCodeKanaRelease = false;
1339 if (down) {
1340 if ((needsLockToggle(keyInfo) & KeyInfo::CODE_MASK) &&
1341 keyboardSettings.getAutoToggleCodeKanaLock()) {
1342 // Code Kana locks, is in wrong state and must be auto-toggled:
1343 // Toggle it by pressing the lock key and scheduling a
1344 // release event
1345 locksOn ^= KeyInfo::CODE_MASK;
1346 pressKeyMatrixEvent(time, modifierPos[KeyInfo::Modifier::CODE]);
1347 insertCodeKanaRelease = true;
1348 } else {
1349 // Press the character key and related modifiers
1350 // Ignore the CODE key in case that Code Kana locks
1351 // (e.g. do not press it).
1352 // Ignore the GRAPH key in case that Graph locks
1353 // Always ignore CAPSLOCK mask (assume that user will
1354 // use real CAPS lock to switch/ between hiragana and
1355 // katakana on japanese model)
1356 pressKeyMatrixEvent(time, keyInfo.pos);
1357
1358 uint8_t modMask = keyInfo.modMask & ~modifierIsLock;
1359 if (('A' <= unicode && unicode <= 'Z') || ('a' <= unicode && unicode <= 'z')) {
1360 // For a-z and A-Z, leave SHIFT unchanged, this to cater
1361 // for difference in behaviour between host and emulated
1362 // machine with respect to how the combination of CAPSLOCK
1363 // and SHIFT is interpreted for these characters.
1364 modMask &= ~KeyInfo::SHIFT_MASK;
1365 } else {
1366 // Release SHIFT if our character does not require it.
1367 if (~modMask & KeyInfo::SHIFT_MASK) {
1368 releaseKeyMatrixEvent(time, modifierPos[KeyInfo::Modifier::SHIFT]);
1369 }
1370 }
1371 // Press required modifiers for our character.
1372 // Note that these modifiers are only pressed, never released.
1373 for (auto [i, mp] : enumerate(modifierPos)) {
1374 if ((modMask >> i) & 1) {
1375 pressKeyMatrixEvent(time, mp);
1376 }
1377 }
1378 }
1379 } else {
1380 releaseKeyMatrixEvent(time, keyInfo.pos);
1381
1382 // Restore non-lock modifier keys.
1383 for (auto [i, mp] : enumerate(modifierPos)) {
1384 if (!((modifierIsLock >> i) & 1)) {
1385 // Do not simply unpress graph, ctrl, code and shift but
1386 // restore them to the values currently pressed by the user.
1387 if ((msxModifiers >> i) & 1) {
1388 releaseKeyMatrixEvent(time, mp);
1389 } else {
1390 pressKeyMatrixEvent(time, mp);
1391 }
1392 }
1393 }
1394 }
1395 keysChanged = true;
1396 return insertCodeKanaRelease;
1397}
1398
1399/*
1400 * Press an ASCII character. It is used by the 'Insert characters'
1401 * function that is exposed to the console.
1402 * The characters are inserted in a separate keyboard matrix, to prevent
1403 * interference with the keypresses of the user on the MSX itself.
1404 *
1405 * @returns:
1406 * zero : handling this character is done
1407 * non-zero: typing this character needs additional actions
1408 * bits 0-4: release these modifiers and call again
1409 * bit 7 : simply call again (used by SHIFT+GRAPH heuristic)
1410 */
1411uint8_t Keyboard::pressAscii(unsigned unicode, bool down)
1412{
1413 uint8_t releaseMask = 0;
1414 UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.get(unicode);
1415 if (!keyInfo.isValid()) {
1416 return releaseMask;
1417 }
1418 uint8_t modMask = keyInfo.modMask & ~modifierIsLock;
1419 if (down) {
1420 // check for modifier toggles
1421 uint8_t toggleLocks = needsLockToggle(keyInfo);
1422 for (auto [i, mp] : enumerate(modifierPos)) {
1423 if ((toggleLocks >> i) & 1) {
1424 debug("Toggling lock %d\n", i);
1425 locksOn ^= 1 << i;
1426 releaseMask |= 1 << i;
1427 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1428 }
1429 }
1430 if (releaseMask == 0) {
1431 debug("Key pasted, unicode: 0x%04x, row: %02d, col: %d, modMask: %02x\n",
1432 unicode, keyInfo.pos.getRow(), keyInfo.pos.getColumn(), modMask);
1433 // Workaround MSX-BIOS(?) bug/limitation:
1434 //
1435 // Under these conditions:
1436 // - Typing a graphical MSX character 00-1F (such a char 'x' gets
1437 // printed as chr$(1) followed by chr$(x+64)).
1438 // - Typing this character requires pressing SHIFT+GRAPH and one
1439 // 'regular' key.
1440 // - And all 3 keys are immediately pressed simultaneously.
1441 // Then, from time to time, instead of the intended character 'x'
1442 // (00-1F), the wrong character 'x+64' gets printed.
1443 // When first SHIFT+GRAPH is pressed, and only one frame later the
1444 // other keys is pressed (additionally), this problem seems to
1445 // disappear.
1446 //
1447 // After implementing the above we found that a similar problem
1448 // occurs when:
1449 // - a GRAPH + <x> (without SHIFT) key combo is typed
1450 // - immediately after a key combo with GRAPH + SHIFT + <x>.
1451 // For example:
1452 // type "\u2666\u266a"
1453 // from time to time 2nd character got wrongly typed as a
1454 // 'M' (instead of a musical note symbol). But typing a sequence
1455 // of \u266a chars without a preceding \u2666 just works.
1456 //
1457 // To fix both these problems (and possibly still undiscovered
1458 // variations), I'm now extending the workaround to all characters
1459 // that are typed via a key combination that includes GRAPH.
1460 if (modMask & KeyInfo::GRAPH_MASK) {
1461 auto isPressed = [&](auto& key) {
1462 return (typeKeyMatrix[key.getRow()] & key.getMask()) == 0;
1463 };
1464 if (!isPressed(modifierPos[KeyInfo::Modifier::GRAPH])) {
1465 // GRAPH not yet pressed ->
1466 // first press it before adding the non-modifier key
1467 releaseMask = TRY_AGAIN;
1468 }
1469 }
1470 // press modifiers
1471 for (auto [i, mp] : enumerate(modifierPos)) {
1472 if ((modMask >> i) & 1) {
1473 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1474 }
1475 }
1476 if (releaseMask == 0) {
1477 // press key
1478 typeKeyMatrix[keyInfo.pos.getRow()] &= uint8_t(~keyInfo.pos.getMask());
1479 }
1480 }
1481 } else {
1482 typeKeyMatrix[keyInfo.pos.getRow()] |= keyInfo.pos.getMask();
1483 for (auto [i, mp] : enumerate(modifierPos)) {
1484 if ((modMask >> i) & 1) {
1485 typeKeyMatrix[mp.getRow()] |= mp.getMask();
1486 }
1487 }
1488 }
1489 keysChanged = true;
1490 return releaseMask;
1491}
1492
1493/*
1494 * Press a lock key. It is used by the 'Insert characters'
1495 * function that is exposed to the console.
1496 * The characters are inserted in a separate keyboard matrix, to prevent
1497 * interference with the keypresses of the user on the MSX itself
1498 */
1499void Keyboard::pressLockKeys(uint8_t lockKeysMask, bool down)
1500{
1501 for (auto [i, mp] : enumerate(modifierPos)) {
1502 if ((lockKeysMask >> i) & 1) {
1503 if (down) {
1504 // press lock key
1505 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1506 } else {
1507 // release lock key
1508 typeKeyMatrix[mp.getRow()] |= mp.getMask();
1509 }
1510 }
1511 }
1512 keysChanged = true;
1513}
1514
1515/*
1516 * Check if there are common keys in the MSX matrix for
1517 * two different unicodes.
1518 * It is used by the 'insert keys' function to determine if it has to wait for
1519 * a short while after releasing a key (to enter a certain character) before
1520 * pressing the next key (to enter the next character)
1521 */
1522bool Keyboard::commonKeys(unsigned unicode1, unsigned unicode2) const
1523{
1524 // get row / mask of key (note: ignore modifier mask)
1525 auto keyPos1 = unicodeKeymap.get(unicode1).pos;
1526 auto keyPos2 = unicodeKeymap.get(unicode2).pos;
1527
1528 return keyPos1 == keyPos2 && keyPos1.isValid();
1529}
1530
1531void Keyboard::debug(const char* format, ...) const
1532{
1533 if (keyboardSettings.getTraceKeyPresses()) {
1534 va_list args;
1535 va_start(args, format);
1536 vfprintf(stderr, format, args);
1537 va_end(args);
1538 }
1539}
1540
1541
1542// class KeyMatrixUpCmd
1543
1544Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd(
1545 CommandController& commandController_,
1546 StateChangeDistributor& stateChangeDistributor_,
1547 Scheduler& scheduler_)
1548 : RecordedCommand(commandController_, stateChangeDistributor_,
1549 scheduler_, "keymatrixup")
1550{
1551}
1552
1553void Keyboard::KeyMatrixUpCmd::execute(
1554 std::span<const TclObject> tokens, TclObject& /*result*/, EmuTime::param /*time*/)
1555{
1556 checkNumArgs(tokens, 3, Prefix{1}, "row mask");
1557 auto& keyboard = OUTER(Keyboard, keyMatrixUpCmd);
1558 return keyboard.processCmd(getInterpreter(), tokens, true);
1559}
1560
1561std::string Keyboard::KeyMatrixUpCmd::help(std::span<const TclObject> /*tokens*/) const
1562{
1563 return "keymatrixup <row> <bitmask> release a key in the keyboard matrix\n";
1564}
1565
1566
1567// class KeyMatrixDownCmd
1568
1569Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController_,
1570 StateChangeDistributor& stateChangeDistributor_,
1571 Scheduler& scheduler_)
1572 : RecordedCommand(commandController_, stateChangeDistributor_,
1573 scheduler_, "keymatrixdown")
1574{
1575}
1576
1577void Keyboard::KeyMatrixDownCmd::execute(std::span<const TclObject> tokens,
1578 TclObject& /*result*/, EmuTime::param /*time*/)
1579{
1580 checkNumArgs(tokens, 3, Prefix{1}, "row mask");
1581 auto& keyboard = OUTER(Keyboard, keyMatrixDownCmd);
1582 return keyboard.processCmd(getInterpreter(), tokens, false);
1583}
1584
1585std::string Keyboard::KeyMatrixDownCmd::help(std::span<const TclObject> /*tokens*/) const
1586{
1587 return "keymatrixdown <row> <bitmask> press a key in the keyboard matrix\n";
1588}
1589
1590
1591// class MsxKeyEventQueue
1592
1593Keyboard::MsxKeyEventQueue::MsxKeyEventQueue(
1594 Scheduler& scheduler_, Interpreter& interp_)
1595 : Schedulable(scheduler_)
1596 , interp(interp_)
1597{
1598}
1599
1600void Keyboard::MsxKeyEventQueue::process_asap(
1601 EmuTime::param time, const Event& event)
1602{
1603 bool processImmediately = eventQueue.empty();
1604 eventQueue.push_back(event);
1605 if (processImmediately) {
1606 executeUntil(time);
1607 }
1608}
1609
1610void Keyboard::MsxKeyEventQueue::clear()
1611{
1612 eventQueue.clear();
1613 removeSyncPoint();
1614}
1615
1616void Keyboard::MsxKeyEventQueue::executeUntil(EmuTime::param time)
1617{
1618 // Get oldest event from the queue and process it
1619 const Event& event = eventQueue.front();
1620 auto& keyboard = OUTER(Keyboard, msxKeyEventQueue);
1621 bool insertCodeKanaRelease = keyboard.processQueuedEvent(event, time);
1622
1623 if (insertCodeKanaRelease) {
1624 // The processor pressed the CODE/KANA key
1625 // Schedule a CODE/KANA release event, to be processed
1626 // before any of the other events in the queue
1627 eventQueue.emplace_front(KeyUpEvent::create(keyboard.keyboardSettings.getCodeKanaHostKey()));
1628 } else {
1629 // The event has been completely processed. Delete it from the queue
1630 if (!eventQueue.empty()) {
1631 eventQueue.pop_front();
1632 } else {
1633 // it's possible clear() has been called
1634 // (indirectly from keyboard.processQueuedEvent())
1635 }
1636 }
1637
1638 if (!eventQueue.empty()) {
1639 // There are still events. Process them in 1/15s from now
1640 setSyncPoint(time + EmuDuration::hz(15));
1641 }
1642}
1643
1644
1645// class KeyInserter
1646
1647Keyboard::KeyInserter::KeyInserter(
1648 CommandController& commandController_,
1649 StateChangeDistributor& stateChangeDistributor_,
1650 Scheduler& scheduler_)
1651 : RecordedCommand(commandController_, stateChangeDistributor_,
1652 scheduler_, "type_via_keyboard")
1653 , Schedulable(scheduler_)
1654{
1655}
1656
1657void Keyboard::KeyInserter::execute(
1658 std::span<const TclObject> tokens, TclObject& /*result*/, EmuTime::param /*time*/)
1659{
1660 checkNumArgs(tokens, AtLeast{2}, "?-release? ?-freq hz? ?-cancel? text");
1661
1662 bool cancel = false;
1663 releaseBeforePress = false;
1664 typingFrequency = 15;
1665 std::array info = {
1666 flagArg("-cancel", cancel),
1667 flagArg("-release", releaseBeforePress),
1668 valueArg("-freq", typingFrequency),
1669 };
1670 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(1), info);
1671
1672 if (typingFrequency <= 0) {
1673 throw CommandException("Wrong argument for -freq (should be a positive number)");
1674 }
1675 if (cancel) {
1676 text_utf8.clear();
1677 return;
1678 }
1679
1680 if (arguments.size() != 1) throw SyntaxError();
1681
1682 type(arguments[0].getString());
1683}
1684
1685std::string Keyboard::KeyInserter::help(std::span<const TclObject> /*tokens*/) const
1686{
1687 return "Type a string in the emulated MSX.\n"
1688 "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"
1689 "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"
1690 "Use -cancel to cancel a (long) in-progress type command.";
1691}
1692
1693void Keyboard::KeyInserter::tabCompletion(std::vector<std::string>& tokens) const
1694{
1695 using namespace std::literals;
1696 static constexpr std::array options = {"-release"sv, "-freq"sv};
1697 completeString(tokens, options);
1698}
1699
1700void Keyboard::KeyInserter::type(std::string_view str)
1701{
1702 if (str.empty()) {
1703 return;
1704 }
1705 const auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1706 oldLocksOn = keyboard.locksOn;
1707 if (text_utf8.empty()) {
1708 reschedule(getCurrentTime());
1709 }
1710 text_utf8.append(str.data(), str.size());
1711}
1712
1713void Keyboard::KeyInserter::executeUntil(EmuTime::param time)
1714{
1715 auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1716 if (lockKeysMask != 0) {
1717 // release CAPS and/or Code/Kana Lock keys
1718 keyboard.pressLockKeys(lockKeysMask, false);
1719 }
1720 if (releaseLast) {
1721 keyboard.pressAscii(last, false); // release previous character
1722 }
1723 if (text_utf8.empty()) {
1724 releaseLast = false;
1725 keyboard.debug("Restoring locks: %02X -> %02X\n", keyboard.locksOn, oldLocksOn);
1726 uint8_t diff = oldLocksOn ^ keyboard.locksOn;
1727 lockKeysMask = diff;
1728 if (diff != 0) {
1729 // press CAPS, GRAPH and/or Code/Kana Lock keys
1730 keyboard.locksOn ^= diff;
1731 keyboard.pressLockKeys(diff, true);
1732 reschedule(time);
1733 }
1734 return;
1735 }
1736
1737 try {
1738 auto it = begin(text_utf8);
1739 unsigned current = utf8::next(it, end(text_utf8));
1740 if (releaseLast && (releaseBeforePress || keyboard.commonKeys(last, current))) {
1741 // There are common keys between previous and current character
1742 // Do not immediately press again but give MSX the time to notice
1743 // that the keys have been released
1744 releaseLast = false;
1745 } else {
1746 // All keys in current char differ from previous char. The new keys
1747 // can immediately be pressed
1748 lockKeysMask = keyboard.pressAscii(current, true);
1749 if (lockKeysMask == 0) {
1750 last = current;
1751 releaseLast = true;
1752 text_utf8.erase(begin(text_utf8), it);
1753 } else if (lockKeysMask & TRY_AGAIN) {
1754 lockKeysMask &= ~TRY_AGAIN;
1755 releaseLast = false;
1756 } else if (releaseBeforePress) {
1757 releaseLast = true;
1758 }
1759 }
1760 reschedule(time);
1761 } catch (std::exception&) {
1762 // utf8 encoding error
1763 text_utf8.clear();
1764 }
1765}
1766
1767void Keyboard::KeyInserter::reschedule(EmuTime::param time)
1768{
1769 setSyncPoint(time + EmuDuration::hz(typingFrequency));
1770}
1771
1772
1773// Commands for conversion between msxcode <-> unicode.
1774
1775Keyboard::Msxcode2UnicodeCmd::Msxcode2UnicodeCmd(CommandController& commandController_)
1776 : Command(commandController_, "msxcode2unicode")
1777{
1778}
1779
1780void Keyboard::Msxcode2UnicodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1781{
1782 checkNumArgs(tokens, Between{2, 3}, "msx-string ?fallback?");
1783
1784 auto& interp = getInterpreter();
1785 const auto& keyboard = OUTER(Keyboard, msxcode2UnicodeCmd);
1786 const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1787
1788 auto msx = tokens[1].getBinary();
1789 auto fallback = [&]() -> std::function<uint32_t(uint8_t)> {
1790 if (tokens.size() < 3) {
1791 // If no fallback is given use space as replacement character
1792 return [](uint8_t) { return uint32_t(' '); };
1793 } else if (auto i = tokens[2].getOptionalInt()) {
1794 // If an integer is given use that as a unicode character number
1795 return [i = *i](uint8_t) { return uint32_t(i); };
1796 } else {
1797 // Otherwise use the given string as the name of a Tcl proc,
1798 // That proc is (possibly later) invoked with a msx-character as input,
1799 // and it should return the replacement unicode character number.
1800 return [&](uint8_t m) {
1801 TclObject cmd{TclObject::MakeListTag{}, tokens[2], m};
1802 return uint32_t(cmd.executeCommand(interp).getInt(interp));
1803 };
1804 }
1805 }();
1806
1807 result = msxChars.msxToUtf8(msx, fallback);
1808}
1809
1810std::string Keyboard::Msxcode2UnicodeCmd::help(std::span<const TclObject> /*tokens*/) const
1811{
1812 return "msxcode2unicode <msx-string> [<fallback>]\n"
1813 "returns a unicode string converted from an MSX-string, i.e. a string based on\n"
1814 "MSX character codes.\n"
1815 "The optional fallback used for each character that cannot be mapped for the\n"
1816 "current MSX model can be:\n"
1817 "- omitted: then space will be used as fallback character.\n"
1818 "- an integer number: then this number will be used as unicode point to be the\n"
1819 " the fallback character.\n"
1820 "- a Tcl proc, which expects one input character and must return one unicode\n"
1821 " point.";
1822}
1823
1824
1825Keyboard::Unicode2MsxcodeCmd::Unicode2MsxcodeCmd(CommandController& commandController_)
1826 : Command(commandController_, "unicode2msxcode")
1827{
1828}
1829
1830void Keyboard::Unicode2MsxcodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1831{
1832 checkNumArgs(tokens, Between{2, 3}, "unicode-string ?fallback?");
1833
1834 auto& interp = getInterpreter();
1835 const auto& keyboard = OUTER(Keyboard, unicode2MsxcodeCmd);
1836 const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1837
1838 const auto& unicode = tokens[1].getString();
1839 auto fallback = [&]() -> std::function<uint8_t(uint32_t)> {
1840 if (tokens.size() < 3) {
1841 // If no fallback is given use space as replacement character
1842 return [](uint32_t) { return uint8_t(' '); };
1843 } else if (auto i = tokens[2].getOptionalInt()) {
1844 // If an integer is given use that as a MSX character number
1845 return [i = *i](uint32_t) { return uint8_t(i); };
1846 } else {
1847 // Otherwise use the given string as the name of a Tcl proc,
1848 // That proc is (possibly later) invoked with a unicode character as
1849 // input, and it should return the replacement MSX character number.
1850 return [&](uint32_t u) {
1851 TclObject cmd{TclObject::MakeListTag{}, tokens[2], u};
1852 return uint8_t(cmd.executeCommand(interp).getInt(interp));
1853 };
1854 }
1855 }();
1856
1857 result = msxChars.utf8ToMsx(unicode, fallback);
1858}
1859
1860std::string Keyboard::Unicode2MsxcodeCmd::help(std::span<const TclObject> /*tokens*/) const
1861{
1862 return "unicode2msxcode <unicode-string> [<fallback>]\n"
1863 "returns an MSX string, i.e. a string based on MSX character codes, converted\n"
1864 "from a unicode string.\n"
1865 "The optional fallback used for each character that cannot be mapped for the\n"
1866 "current MSX model can be:\n"
1867 "- omitted: then space will be used as fallback character.\n"
1868 "- an integer number: then this number will be used as MSX character number to\n"
1869 " to be the fallback character.\n"
1870 "- a Tcl proc, which expects one input character and must return one MSX\n"
1871 " character number.";
1872}
1873
1874
1875/*
1876 * class CapsLockAligner
1877 *
1878 * It is used to align MSX CAPS lock status with the host CAPS lock status
1879 * during the reset of the MSX or after the openMSX window regains focus.
1880 *
1881 * It listens to the 'BOOT' event and schedules the real alignment
1882 * 2 seconds later. Reason is that it takes a while before the MSX
1883 * reset routine starts monitoring the MSX keyboard.
1884 *
1885 * For focus regain, the alignment is done immediately.
1886 */
1887Keyboard::CapsLockAligner::CapsLockAligner(
1888 EventDistributor& eventDistributor_,
1889 Scheduler& scheduler_)
1890 : Schedulable(scheduler_)
1891 , eventDistributor(eventDistributor_)
1892{
1893 for (auto type : {EventType::BOOT, EventType::WINDOW}) {
1894 eventDistributor.registerEventListener(type, *this);
1895 }
1896}
1897
1898Keyboard::CapsLockAligner::~CapsLockAligner()
1899{
1900 for (auto type : {EventType::WINDOW, EventType::BOOT}) {
1901 eventDistributor.unregisterEventListener(type, *this);
1902 }
1903}
1904
1905bool Keyboard::CapsLockAligner::signalEvent(const Event& event)
1906{
1907 if constexpr (!SANE_CAPSLOCK_BEHAVIOR) {
1908 // don't even try
1909 return false;
1910 }
1911
1912 if (state == IDLE) {
1913 EmuTime::param time = getCurrentTime();
1914 std::visit(overloaded{
1915 [&](const WindowEvent& e) {
1916 if (e.isMainWindow()) {
1917 const auto& evt = e.getSdlWindowEvent();
1918 if (evt.event == one_of(SDL_WINDOWEVENT_FOCUS_GAINED, SDL_WINDOWEVENT_FOCUS_LOST)) {
1919 alignCapsLock(time);
1920 }
1921 }
1922 },
1923 [&](const BootEvent&) {
1924 state = MUST_ALIGN_CAPSLOCK;
1925 setSyncPoint(time + EmuDuration::sec(2)); // 2s (MSX time)
1926 },
1927 [](const EventBase&) { UNREACHABLE; }
1928 }, event);
1929 }
1930 return false;
1931}
1932
1933void Keyboard::CapsLockAligner::executeUntil(EmuTime::param time)
1934{
1935 switch (state) {
1936 case MUST_ALIGN_CAPSLOCK:
1937 alignCapsLock(time);
1938 break;
1939 case MUST_DISTRIBUTE_KEY_RELEASE: {
1940 auto& keyboard = OUTER(Keyboard, capsLockAligner);
1941 auto event = KeyUpEvent::create(SDLK_CAPSLOCK);
1942 keyboard.msxEventDistributor.distributeEvent(event, time);
1943 state = IDLE;
1944 break;
1945 }
1946 default:
1948 }
1949}
1950
1951/*
1952 * Align MSX caps lock state with host caps lock state
1953 * WARNING: This function assumes that the MSX will see and
1954 * process the caps lock key press.
1955 * If MSX misses the key press for whatever reason (e.g.
1956 * interrupts are disabled), the caps lock state in this
1957 * module will mismatch with the real MSX caps lock state
1958 * TODO: Find a solution for the above problem. For example by monitoring
1959 * the MSX caps-lock LED state.
1960 */
1961void Keyboard::CapsLockAligner::alignCapsLock(EmuTime::param time)
1962{
1963 bool hostCapsLockOn = ((SDL_GetModState() & KMOD_CAPS) != 0);
1964 auto& keyboard = OUTER(Keyboard, capsLockAligner);
1965 if (bool(keyboard.locksOn & KeyInfo::CAPS_MASK) != hostCapsLockOn) {
1966 keyboard.debug("Resyncing host and MSX CAPS lock\n");
1967 // note: send out another event iso directly calling
1968 // processCapslockEvent() because we want this to be recorded
1969 auto event = KeyDownEvent::create(SDLK_CAPSLOCK);
1970 keyboard.msxEventDistributor.distributeEvent(event, time);
1971 keyboard.debug("Sending fake CAPS release\n");
1972 state = MUST_DISTRIBUTE_KEY_RELEASE;
1973 setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (MSX time)
1974 } else {
1975 state = IDLE;
1976 }
1977}
1978
1979
1980// class KeybDebuggable
1981
1982Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard_)
1983 : SimpleDebuggable(motherBoard_, "keymatrix", "MSX Keyboard Matrix",
1984 KeyMatrixPosition::NUM_ROWS)
1985{
1986}
1987
1988uint8_t Keyboard::KeybDebuggable::read(unsigned address)
1989{
1990 const auto& keyboard = OUTER(Keyboard, keybDebuggable);
1991 return keyboard.getKeys()[address];
1992}
1993
1994void Keyboard::KeybDebuggable::write(unsigned /*address*/, uint8_t /*value*/)
1995{
1996 // ignore
1997}
1998
1999
2000template<typename Archive>
2001void Keyboard::KeyInserter::serialize(Archive& ar, unsigned /*version*/)
2002{
2003 ar.template serializeBase<Schedulable>(*this);
2004 ar.serialize("text", text_utf8,
2005 "last", last,
2006 "lockKeysMask", lockKeysMask,
2007 "releaseLast", releaseLast);
2008
2009 bool oldCodeKanaLockOn, oldGraphLockOn, oldCapsLockOn;
2010 if constexpr (!Archive::IS_LOADER) {
2011 oldCodeKanaLockOn = oldLocksOn & KeyInfo::CODE_MASK;
2012 oldGraphLockOn = oldLocksOn & KeyInfo::GRAPH_MASK;
2013 oldCapsLockOn = oldLocksOn & KeyInfo::CAPS_MASK;
2014 }
2015 ar.serialize("oldCodeKanaLockOn", oldCodeKanaLockOn,
2016 "oldGraphLockOn", oldGraphLockOn,
2017 "oldCapsLockOn", oldCapsLockOn);
2018 if constexpr (Archive::IS_LOADER) {
2019 oldLocksOn = (oldCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
2020 | (oldGraphLockOn ? KeyInfo::GRAPH_MASK : 0)
2021 | (oldCapsLockOn ? KeyInfo::CAPS_MASK : 0);
2022 }
2023}
2024
2025// version 1: Initial version: {userKeyMatrix, dynKeymap, msxModifiers,
2026// msxKeyEventQueue} was intentionally not serialized. The reason
2027// was that after a loadstate, you want the MSX keyboard to reflect
2028// the state of the host keyboard. So any pressed MSX keys from the
2029// time the savestate was created are cleared.
2030// version 2: For reverse-replay it is important that snapshots contain the
2031// full state of the MSX keyboard, so now we do serialize it.
2032// version 3: split cmdKeyMatrix into cmdKeyMatrix + typeKeyMatrix
2033// version 4: changed 'dynKeymap' to 'lastUnicodeForKeycode'
2034// TODO Is the assumption in version 1 correct (clear keyb state on load)?
2035// If it is still useful for 'regular' loadstate, then we could implement
2036// it by explicitly clearing the keyb state from the actual loadstate
2037// command. (But let's only do this when experience shows it's really
2038// better).
2039template<typename Archive>
2040void Keyboard::serialize(Archive& ar, unsigned version)
2041{
2042 ar.serialize("keyTypeCmd", keyTypeCmd,
2043 "cmdKeyMatrix", cmdKeyMatrix);
2044 if (ar.versionAtLeast(version, 3)) {
2045 ar.serialize("typeKeyMatrix", typeKeyMatrix);
2046 } else {
2047 typeKeyMatrix = cmdKeyMatrix;
2048 }
2049
2050 bool msxCapsLockOn, msxCodeKanaLockOn, msxGraphLockOn;
2051 if constexpr (!Archive::IS_LOADER) {
2052 msxCapsLockOn = locksOn & KeyInfo::CAPS_MASK;
2053 msxCodeKanaLockOn = locksOn & KeyInfo::CODE_MASK;
2054 msxGraphLockOn = locksOn & KeyInfo::GRAPH_MASK;
2055 }
2056 ar.serialize("msxCapsLockOn", msxCapsLockOn,
2057 "msxCodeKanaLockOn", msxCodeKanaLockOn,
2058 "msxGraphLockOn", msxGraphLockOn);
2059 if constexpr (Archive::IS_LOADER) {
2060 locksOn = (msxCapsLockOn ? KeyInfo::CAPS_MASK : 0)
2061 | (msxCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
2062 | (msxGraphLockOn ? KeyInfo::GRAPH_MASK : 0);
2063 }
2064
2065 if (ar.versionAtLeast(version, 2)) {
2066 ar.serialize("userKeyMatrix", userKeyMatrix,
2067 "msxmodifiers", msxModifiers,
2068 "msxKeyEventQueue", msxKeyEventQueue);
2069 }
2070 if (ar.versionAtLeast(version, 4)) {
2071 ar.serialize("lastUnicodeForKeycode", lastUnicodeForKeycode);
2072 } else {
2073 // We can't (easily) reconstruct 'lastUnicodeForKeycode' from
2074 // 'dynKeymap'. Usually this won't cause problems. E.g.
2075 // typically you aren't typing at the same time that you create
2076 // a savestate. For replays it might matter, though usually
2077 // replays are about games, and typing in games is rare (for
2078 // cursors and space this isn't needed).
2079 //
2080 // So, at least for now, it's fine to not reconstruct this data.
2081 }
2082 // don't serialize hostKeyMatrix
2083
2084 if constexpr (Archive::IS_LOADER) {
2085 // force recalculation of keyMatrix
2086 keysChanged = true;
2087 }
2088}
2090
2091template<typename Archive>
2092void Keyboard::MsxKeyEventQueue::serialize(Archive& ar, unsigned /*version*/)
2093{
2094 ar.template serializeBase<Schedulable>(*this);
2095
2096 // serialization of deque<Event> is not directly
2097 // supported by the serialization framework (main problem is the
2098 // constness, collections of shared_ptr to polymorphic objects are
2099 // not a problem). Worked around this by serializing the events in
2100 // ascii format. (In all practical cases this queue will anyway be
2101 // empty or contain very few elements).
2102 //ar.serialize("eventQueue", eventQueue);
2103 std::vector<std::string> eventStrs;
2104 if constexpr (!Archive::IS_LOADER) {
2105 eventStrs = to_vector(view::transform(
2106 eventQueue, [](const auto& e) { return toString(e); }));
2107 }
2108 ar.serialize("eventQueue", eventStrs);
2109 if constexpr (Archive::IS_LOADER) {
2110 assert(eventQueue.empty());
2111 for (const auto& s : eventStrs) {
2112 eventQueue.push_back(
2114 }
2115 }
2116}
2117INSTANTIATE_SERIALIZE_METHODS(Keyboard::MsxKeyEventQueue);
2118
2119} // 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:828
void serialize(Archive &ar, unsigned version)
Definition Keyboard.cc:2040
void setFocus(bool newFocus, EmuTime::param time)
Definition Keyboard.cc:850
std::span< const uint8_t, KeyMatrixPosition::NUM_ROWS > getKeys() const
Returns a pointer to the current KeyBoard matrix.
Definition Keyboard.cc:813
const MsxChar2Unicode & getMsxChar2Unicode() const
Definition Keyboard.cc:748
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
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:278
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)