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