53static constexpr bool SANE_CAPSLOCK_BEHAVIOR =
false;
58static constexpr bool SANE_CAPSLOCK_BEHAVIOR =
true;
62static constexpr uint8_t TRY_AGAIN = 0x80;
70 KeyMatrixState(EmuTime::param time_, uint8_t row_, uint8_t press_, uint8_t release_)
72 , row(row_), press(press_), release(release_)
75 assert((press != 0) || (release != 0));
79 assert((press & release) == 0);
81 [[nodiscard]] uint8_t
getRow()
const {
return row; }
82 [[nodiscard]] uint8_t
getPress()
const {
return press; }
83 [[nodiscard]] uint8_t
getRelease()
const {
return release; }
85 template<
typename Archive>
void serialize(Archive& ar,
unsigned )
87 ar.template serializeBase<StateChange>(*
this);
88 ar.serialize(
"row", row,
93 uint8_t row, press, release;
98static constexpr std::array<std::string_view, 4> defaultKeymapForMatrix = {
105static constexpr std::array modifierPosForMatrix = {
107 KeyMatrixPosition(6, 0),
108 KeyMatrixPosition(6, 1),
109 KeyMatrixPosition(6, 2),
110 KeyMatrixPosition(6, 3),
111 KeyMatrixPosition(6, 4),
114 KeyMatrixPosition(6, 0),
115 KeyMatrixPosition(6, 1),
116 KeyMatrixPosition(6, 2),
117 KeyMatrixPosition(8, 3),
118 KeyMatrixPosition(6, 3),
120 std::array<KeyMatrixPosition, UnicodeKeymap::KeyInfo::NUM_MODIFIERS>{
123 KeyMatrixPosition(13, 3),
124 KeyMatrixPosition(13, 2),
125 KeyMatrixPosition(13, 1),
127 KeyMatrixPosition( 0, 4),
139template<
typename Proj>
140static constexpr size_t count(std::span<const MsxKeyScanMapping> mapping, Proj proj)
142 using Array = std::remove_cvref_t<
decltype(std::invoke(proj, mapping[0]))>;
143 using Code =
typename Array::value_type;
146 for (
const auto& m : mapping) {
147 for (
const auto& c : std::invoke(proj, m)) {
148 if (c != Code{}) ++result;
154template<
typename GetMapping>
155static constexpr auto extractKeyCodeMapping(GetMapping getMapping)
157 constexpr auto mapping = getMapping();
159 std::array<KeyCodeMsxMapping, N> result;
161 for (
const auto& m : mapping) {
162 for (
const auto& k : m.hostKeyCodes) {
163 if (k != SDLK_UNKNOWN) {
164 result[i++] = {k, m.msx};
172template<
typename GetMapping>
173static constexpr auto extractScanCodeMapping(GetMapping getMapping)
175 constexpr auto mapping = getMapping();
177 std::array<ScanCodeMsxMapping, N> result;
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};
191static constexpr auto getMSXMapping()
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}},
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}},
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}},
239 M{P{0x26}, K{SDLK_a},
S{SDL_SCANCODE_A}},
240 M{P{0x27}, K{SDLK_b},
S{SDL_SCANCODE_B}},
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}},
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}},
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}},
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}},
272 M{P{0x63}, K{SDLK_CAPSLOCK},
S{SDL_SCANCODE_CAPSLOCK}},
273 M{P{0x64}, K{SDLK_RALT},
S{SDL_SCANCODE_RALT}},
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}},
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}},
283 M{P{0x75}, K{SDLK_BACKSPACE},
S{SDL_SCANCODE_BACKSPACE}},
284 M{P{0x76}, K{SDLK_F7},
S{SDL_SCANCODE_F7}},
285 M{P{0x77}, K{SDLK_RETURN},
S{SDL_SCANCODE_RETURN}},
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}},
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}},
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}},
314 M{P{0xB1}, K{SDLK_RGUI},
S{SDL_SCANCODE_RGUI}},
315 M{P{0xB3}, K{SDLK_LGUI},
S{SDL_SCANCODE_LGUI}},
320static constexpr auto getSVIMapping()
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}},
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}},
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}},
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}},
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}},
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}},
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}},
403 M{P{0x66}, K{SDLK_RETURN},
S{SDL_SCANCODE_RETURN}},
404 M{P{0x67}, K{SDLK_LEFT},
S{SDL_SCANCODE_LEFT}},
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}},
412 M{P{0x76}, K{SDLK_INSERT},
S{SDL_SCANCODE_INSERT}},
413 M{P{0x77}, K{SDLK_DOWN},
S{SDL_SCANCODE_DOWN}},
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}},
420 M{P{0x85}, K{SDLK_PRINTSCREEN},
S{SDL_SCANCODE_PRINTSCREEN}},
422 M{P{0x87}, K{SDLK_RIGHT},
S{SDL_SCANCODE_RIGHT}},
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}},
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}},
445static constexpr auto getCvJoyMapping()
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}},
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}},
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}},
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}},
499 M{P{0x40}, K{SDLK_u},
S{SDL_SCANCODE_U}},
500 M{P{0x41}, K{SDLK_v},
S{SDL_SCANCODE_V}},
501 M{P{0x42}, K{SDLK_b},
S{SDL_SCANCODE_B}},
502 M{P{0x43}, K{SDLK_n},
S{SDL_SCANCODE_N}},
503 M{P{0x44}, K{SDLK_f},
S{SDL_SCANCODE_F}},
504 M{P{0x45}, K{SDLK_g},
S{SDL_SCANCODE_G}},
505 M{P{0x46}, K{SDLK_h},
S{SDL_SCANCODE_H}},
506 M{P{0x47}, K{SDLK_r},
S{SDL_SCANCODE_R}},
508 M{P{0x50}, K{SDLK_t},
S{SDL_SCANCODE_T}},
509 M{P{0x51}, K{SDLK_y},
S{SDL_SCANCODE_Y}},
510 M{P{0x52}, K{SDLK_j},
S{SDL_SCANCODE_J}},
511 M{P{0x53}, K{SDLK_m},
S{SDL_SCANCODE_M}},
516static constexpr auto getSegaMapping()
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}},
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}},
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}},
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}},
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}},
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}},
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}},
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}},
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}},
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}},
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}},
617 M{P{0x65}, K{SDLK_RIGHT},
S{SDL_SCANCODE_RIGHT}},
618 M{P{0x66}, K{SDLK_UP},
S{SDL_SCANCODE_UP}},
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}},
631 M{P{0xC0}, K{SDLK_BACKSLASH},
S{SDL_SCANCODE_BACKSLASH}},
632 M{P{0xC3}, K{SDLK_TAB},
S{SDL_SCANCODE_TAB}},
634 M{P{0xD0}, K{SDLK_ESCAPE, SDLK_F8},
635 S{SDL_SCANCODE_ESCAPE, SDL_SCANCODE_F8}},
636 M{P{0xD1}, K{SDLK_LALT},
S{SDL_SCANCODE_LALT}},
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}},
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(); });
675static constexpr std::array<std::span<const KeyCodeMsxMapping>, 4> defaultKeyCodeMappings = {
681static constexpr std::array<std::span<const ScanCodeMsxMapping>, 4> defaultScanCodeMappings = {
684 cvJoyScanCodeMapping,
697 , commandController(commandController_)
698 , msxEventDistributor(msxEventDistributor_)
699 , stateChangeDistributor(stateChangeDistributor_)
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))
749static constexpr void doKeyGhosting(std::span<uint8_t, KeyMatrixPosition::NUM_ROWS> matrix,
768 bool changedSomething =
false;
770 changedSomething =
false;
775 auto row1 = matrix[i];
777 auto row2 = matrix[j];
778 if ((row1 != row2) && ((row1 | row2) != 0xff)) {
779 auto rowIold = matrix[i];
780 auto rowJold = matrix[j];
783 if (protectRow6 && i == 6) {
784 matrix[i] = row1 & row2;
785 matrix[j] = (row1 | 0x15) & row2;
787 }
else if (protectRow6 && j == 6) {
788 matrix[i] = row1 & (row2 | 0x15);
789 matrix[j] = row1 & row2;
790 row1 &= (row2 | 0x15);
794 uint8_t newRow = row1 & row2;
799 if (rowIold != matrix[i] ||
800 rowJold != matrix[j]) {
801 changedSomething =
true;
806 }
while (changedSomething);
813 std::span matrix = keyTypeCmd.isActive() ? typeKeyMatrix : userKeyMatrix;
815 keyMatrix[row] = cmdKeyMatrix[row] & matrix[row];
818 doKeyGhosting(keyMatrix, keyGhostingSGCprotected);
842 hostKeyMatrix[row] = source.hostKeyMatrix[row];
851void Keyboard::signalMSXEvent(
const Event& event,
852 EmuTime::param time)
noexcept
855 const auto& keyEvent = get_event<KeyEvent>(event);
856 if (keyEvent.getRepeat())
return;
861 msxKeyEventQueue.process_asap(time, event);
865void Keyboard::signalStateChange(
const StateChange& event)
867 const auto* kms =
dynamic_cast<const KeyMatrixState*
>(&event);
870 userKeyMatrix[kms->getRow()] &= uint8_t(~kms->getPress());
871 userKeyMatrix[kms->getRow()] |= kms->getRelease();
875void Keyboard::stopReplay(EmuTime::param time)
noexcept
877 for (
auto [row, hkm] :
enumerate(hostKeyMatrix)) {
878 changeKeyMatrixEvent(time, uint8_t(row), hkm);
881 msxKeyEventQueue.clear();
882 lastUnicodeForKeycode.clear();
885uint8_t Keyboard::needsLockToggle(
const UnicodeKeymap::KeyInfo& keyInfo)
const
887 return modifierIsLock
888 & (locksOn ^ keyInfo.modMask)
892void Keyboard::pressKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
894 if (!pos.isValid()) {
898 auto row = pos.getRow();
899 auto press = pos.getMask();
900 if (((hostKeyMatrix[row] & press) == 0) &&
901 ((userKeyMatrix[row] & press) == 0)) {
905 changeKeyMatrixEvent(time, row, hostKeyMatrix[row] & ~press);
908void Keyboard::releaseKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
910 if (!pos.isValid()) {
914 auto row = pos.getRow();
915 auto release = pos.getMask();
916 if (((hostKeyMatrix[row] & release) == release) &&
917 ((userKeyMatrix[row] & release) == release)) {
924 changeKeyMatrixEvent(time, row, hostKeyMatrix[row] | release);
927void Keyboard::changeKeyMatrixEvent(EmuTime::param time, uint8_t row, uint8_t newValue)
931 hostKeyMatrix[row] = newValue;
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;
938 time, row, press, release);
944bool Keyboard::processQueuedEvent(
const Event& event, EmuTime::param time)
948 const auto& keyEvent = get_event<KeyEvent>(event);
950 auto key = keyEvent.getKey();
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());
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());
978 for (
auto n :
xrange(3)) {
980 UnicodeKeymap::KeyInfo deadKey = unicodeKeymap.
getDeadKey(n);
981 if (deadKey.isValid()) {
982 updateKeyMatrix(time, down, deadKey.pos);
989 if (key.sym.sym == SDLK_CAPSLOCK) {
990 processCapslockEvent(time, down);
993 processCodeKanaChange(time, down);
995 }
else if (key.sym.sym == SDLK_LALT) {
996 processGraphChange(time, down);
998 }
else if (key.sym.sym == SDLK_KP_ENTER) {
999 processKeypadEnterKey(time, down);
1002 return processKeyEvent(time, down, keyEvent);
1011void Keyboard::processCodeKanaChange(EmuTime::param time,
bool down)
1024void Keyboard::processGraphChange(EmuTime::param time,
bool down)
1037void Keyboard::processCapslockEvent(EmuTime::param time,
bool down)
1039 if (SANE_CAPSLOCK_BEHAVIOR) {
1040 debug(
"Changing CAPS lock state according to SDL request\n");
1046 debug(
"Pressing CAPS lock and scheduling a release\n");
1053void Keyboard::executeUntil(EmuTime::param time)
1055 debug(
"Releasing CAPS lock\n");
1059void Keyboard::processKeypadEnterKey(EmuTime::param time,
bool down)
1068 ? SDLK_KP_ENTER : SDLK_RETURN,
1078void Keyboard::processSdlKey(EmuTime::param time, SDLKey key)
1080 auto process = [&](KeyMatrixPosition pos) {
1081 assert(pos.isValid());
1082 if (pos.getRow() == 11 && blockRow11) {
1086 updateKeyMatrix(time, key.down, pos);
1091 process(mapping->msx);
1095 process(mapping->msx);
1103void Keyboard::updateKeyMatrix(EmuTime::param time,
bool down, KeyMatrixPosition pos)
1105 if (!pos.isValid()) {
1110 pressKeyMatrixEvent(time, pos);
1115 for (
auto [i, mp] :
enumerate(modifierPos)) {
1117 msxModifiers &= uint8_t(~(1 << i));
1121 releaseKeyMatrixEvent(time, pos);
1122 for (
auto [i, mp] :
enumerate(modifierPos)) {
1124 msxModifiers |= 1 << i;
1139bool Keyboard::processKeyEvent(EmuTime::param time,
bool down,
const KeyEvent& keyEvent)
1143 auto keyCode = keyEvent.getKeyCode();
1144 auto key = keyEvent.getKey();
1147 (SDLK_KP_1 <= keyCode && keyCode <= SDLK_KP_0) ||
1148 (keyCode ==
one_of(SDLK_KP_PERIOD, SDLK_KP_DIVIDE, SDLK_KP_MULTIPLY,
1149 SDLK_KP_MINUS, SDLK_KP_PLUS));
1151 if (isOnKeypad && !hasKeypad &&
1157#if defined(__APPLE__)
1159 if ((key.sym.mod & KMOD_GUI) &&
1160 (( positional && (keyEvent.getScanCode() == SDL_SCANCODE_I)) ||
1161 (!positional && (keyCode == SDLK_i)))) {
1163 keyCode = SDLK_INSERT;
1164 key.sym.sym = SDLK_INSERT;
1165 key.sym.scancode = SDL_SCANCODE_INSERT;
1166 key.sym.mod &= ~KMOD_GUI;
1172 UnicodeKeymap::KeyInfo keyInfo;
1185 unicode = keyEvent.getUnicode();
1186 if ((unicode < 0x20) || ((0x7F <= unicode) && (unicode < 0xA0))) {
1196 keyInfo = unicodeKeymap.
get(unicode);
1197 if (!keyInfo.isValid()) {
1211 auto it =
ranges::lower_bound(lastUnicodeForKeycode, keyCode, {}, &std::pair<SDL_Keycode, uint32_t>::first);
1212 if ((it != lastUnicodeForKeycode.end()) && (it->first == keyCode)) {
1215 it->second = unicode;
1218 lastUnicodeForKeycode.emplace(it, keyCode, unicode);
1230 if (!(key.sym.mod & KMOD_MODE)) {
1231 processSdlKey(time, key);
1236 return pressUnicodeByUser(time, keyInfo, unicode,
true);
1240 auto it =
ranges::lower_bound(lastUnicodeForKeycode, keyCode, {}, &std::pair<SDL_Keycode, uint32_t>::first);
1241 unsigned unicode = ((it != lastUnicodeForKeycode.end()) && (it->first == keyCode))
1248 if (!(key.sym.mod & KMOD_MODE)) {
1249 processSdlKey(time, key);
1253 pressUnicodeByUser(time, unicodeKeymap.
get(unicode), unicode,
false);
1259void Keyboard::processCmd(Interpreter& interp, std::span<const TclObject> tokens,
bool up)
1261 unsigned row = tokens[1].getInt(interp);
1262 unsigned mask = tokens[2].getInt(interp);
1264 throw CommandException(
"Invalid row");
1267 throw CommandException(
"Invalid mask");
1270 cmdKeyMatrix[row] |= narrow_cast<uint8_t>(mask);
1272 cmdKeyMatrix[row] &= narrow_cast<uint8_t>(~mask);
1315bool Keyboard::pressUnicodeByUser(
1316 EmuTime::param time, UnicodeKeymap::KeyInfo keyInfo,
unsigned unicode,
1319 bool insertCodeKanaRelease =
false;
1328 insertCodeKanaRelease =
true;
1337 pressKeyMatrixEvent(time, keyInfo.pos);
1339 uint8_t modMask = keyInfo.modMask & ~modifierIsLock;
1340 if ((
'A' <= unicode && unicode <=
'Z') || (
'a' <= unicode && unicode <=
'z')) {
1345 modMask &= ~KeyInfo::SHIFT_MASK;
1354 for (
auto [i, mp] :
enumerate(modifierPos)) {
1355 if ((modMask >> i) & 1) {
1356 pressKeyMatrixEvent(time, mp);
1361 releaseKeyMatrixEvent(time, keyInfo.pos);
1364 for (
auto [i, mp] :
enumerate(modifierPos)) {
1365 if (!((modifierIsLock >> i) & 1)) {
1368 if ((msxModifiers >> i) & 1) {
1369 releaseKeyMatrixEvent(time, mp);
1371 pressKeyMatrixEvent(time, mp);
1377 return insertCodeKanaRelease;
1392uint8_t Keyboard::pressAscii(
unsigned unicode,
bool down)
1394 uint8_t releaseMask = 0;
1395 UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.
get(unicode);
1396 if (!keyInfo.isValid()) {
1399 uint8_t modMask = keyInfo.
modMask & ~modifierIsLock;
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);
1407 releaseMask |= 1 << i;
1408 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
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);
1442 auto isPressed = [&](
auto& key) {
1443 return (typeKeyMatrix[key.getRow()] & key.getMask()) == 0;
1448 releaseMask = TRY_AGAIN;
1452 for (
auto [i, mp] :
enumerate(modifierPos)) {
1453 if ((modMask >> i) & 1) {
1454 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1457 if (releaseMask == 0) {
1459 typeKeyMatrix[keyInfo.pos.getRow()] &= uint8_t(~keyInfo.pos.getMask());
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();
1480void Keyboard::pressLockKeys(uint8_t lockKeysMask,
bool down)
1482 for (
auto [i, mp] :
enumerate(modifierPos)) {
1483 if ((lockKeysMask >> i) & 1) {
1486 typeKeyMatrix[mp.getRow()] &= uint8_t(~mp.getMask());
1489 typeKeyMatrix[mp.getRow()] |= mp.getMask();
1503bool Keyboard::commonKeys(
unsigned unicode1,
unsigned unicode2)
const
1506 auto keyPos1 = unicodeKeymap.
get(unicode1).
pos;
1507 auto keyPos2 = unicodeKeymap.
get(unicode2).
pos;
1509 return keyPos1 == keyPos2 && keyPos1.
isValid();
1512void Keyboard::debug(
const char* format, ...)
const
1516 va_start(args, format);
1517 vfprintf(stderr, format, args);
1525Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd(
1526 CommandController& commandController_,
1527 StateChangeDistributor& stateChangeDistributor_,
1528 Scheduler& scheduler_)
1529 : RecordedCommand(commandController_, stateChangeDistributor_,
1530 scheduler_,
"keymatrixup")
1534void Keyboard::KeyMatrixUpCmd::execute(
1535 std::span<const TclObject> tokens, TclObject& , EmuTime::param )
1537 checkNumArgs(tokens, 3, Prefix{1},
"row mask");
1538 auto& keyboard =
OUTER(Keyboard, keyMatrixUpCmd);
1539 return keyboard.processCmd(getInterpreter(), tokens,
true);
1542std::string Keyboard::KeyMatrixUpCmd::help(std::span<const TclObject> )
const
1544 return "keymatrixup <row> <bitmask> release a key in the keyboard matrix\n";
1550Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController_,
1551 StateChangeDistributor& stateChangeDistributor_,
1552 Scheduler& scheduler_)
1553 : RecordedCommand(commandController_, stateChangeDistributor_,
1554 scheduler_,
"keymatrixdown")
1558void Keyboard::KeyMatrixDownCmd::execute(std::span<const TclObject> tokens,
1559 TclObject& , EmuTime::param )
1561 checkNumArgs(tokens, 3, Prefix{1},
"row mask");
1562 auto& keyboard =
OUTER(Keyboard, keyMatrixDownCmd);
1563 return keyboard.processCmd(getInterpreter(), tokens,
false);
1566std::string Keyboard::KeyMatrixDownCmd::help(std::span<const TclObject> )
const
1568 return "keymatrixdown <row> <bitmask> press a key in the keyboard matrix\n";
1574Keyboard::MsxKeyEventQueue::MsxKeyEventQueue(
1575 Scheduler& scheduler_, Interpreter& interp_)
1581void Keyboard::MsxKeyEventQueue::process_asap(
1582 EmuTime::param time,
const Event& event)
1584 bool processImmediately = eventQueue.empty();
1585 eventQueue.push_back(event);
1586 if (processImmediately) {
1591void Keyboard::MsxKeyEventQueue::clear()
1597void Keyboard::MsxKeyEventQueue::executeUntil(EmuTime::param time)
1600 const Event&
event = eventQueue.front();
1601 auto& keyboard =
OUTER(Keyboard, msxKeyEventQueue);
1602 bool insertCodeKanaRelease = keyboard.processQueuedEvent(event, time);
1604 if (insertCodeKanaRelease) {
1608 eventQueue.emplace_front(
KeyUpEvent::create(keyboard.keyboardSettings.getCodeKanaHostKey()));
1611 if (!eventQueue.empty()) {
1612 eventQueue.pop_front();
1619 if (!eventQueue.empty()) {
1628Keyboard::KeyInserter::KeyInserter(
1629 CommandController& commandController_,
1630 StateChangeDistributor& stateChangeDistributor_,
1631 Scheduler& scheduler_)
1632 : RecordedCommand(commandController_, stateChangeDistributor_,
1633 scheduler_,
"type_via_keyboard")
1638void Keyboard::KeyInserter::execute(
1639 std::span<const TclObject> tokens, TclObject& , EmuTime::param )
1641 checkNumArgs(tokens, AtLeast{2},
"?-release? ?-freq hz? ?-cancel? text");
1643 bool cancel =
false;
1644 releaseBeforePress =
false;
1645 typingFrequency = 15;
1648 flagArg(
"-release", releaseBeforePress),
1649 valueArg(
"-freq", typingFrequency),
1651 auto arguments =
parseTclArgs(getInterpreter(), tokens.subspan(1), info);
1653 if (typingFrequency <= 0) {
1654 throw CommandException(
"Wrong argument for -freq (should be a positive number)");
1661 if (arguments.size() != 1)
throw SyntaxError();
1663 type(arguments[0].getString());
1666std::string Keyboard::KeyInserter::help(std::span<const TclObject> )
const
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.";
1674void Keyboard::KeyInserter::tabCompletion(std::vector<std::string>& tokens)
const
1676 using namespace std::literals;
1677 static constexpr std::array options = {
"-release"sv,
"-freq"sv};
1678 completeString(tokens, options);
1681void Keyboard::KeyInserter::type(std::string_view str)
1686 auto& keyboard =
OUTER(Keyboard, keyTypeCmd);
1687 oldLocksOn = keyboard.locksOn;
1688 if (text_utf8.empty()) {
1689 reschedule(getCurrentTime());
1691 text_utf8.append(str.data(), str.size());
1694void Keyboard::KeyInserter::executeUntil(EmuTime::param time)
1696 auto& keyboard =
OUTER(Keyboard, keyTypeCmd);
1697 if (lockKeysMask != 0) {
1699 keyboard.pressLockKeys(lockKeysMask,
false);
1702 keyboard.pressAscii(last,
false);
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;
1711 keyboard.locksOn ^= diff;
1712 keyboard.pressLockKeys(diff,
true);
1719 auto it =
begin(text_utf8);
1721 if (releaseLast && (releaseBeforePress || keyboard.commonKeys(last, current))) {
1725 releaseLast =
false;
1729 lockKeysMask = keyboard.pressAscii(current,
true);
1730 if (lockKeysMask == 0) {
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) {
1742 }
catch (std::exception&) {
1748void Keyboard::KeyInserter::reschedule(EmuTime::param time)
1756Keyboard::Msxcode2UnicodeCmd::Msxcode2UnicodeCmd(CommandController& commandController_)
1757 : Command(commandController_,
"msxcode2unicode")
1761void Keyboard::Msxcode2UnicodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1763 checkNumArgs(tokens, Between{2, 3},
"msx-string ?fallback?");
1765 auto& interp = getInterpreter();
1766 const auto& keyboard =
OUTER(Keyboard, msxcode2UnicodeCmd);
1767 const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1769 auto msx = tokens[1].getBinary();
1770 auto fallback = [&]() -> std::function<uint32_t(uint8_t)> {
1771 if (tokens.size() < 3) {
1773 return [](uint8_t) {
return uint32_t(
' '); };
1774 }
else if (
auto i = tokens[2].getOptionalInt()) {
1776 return [i = *i](uint8_t) {
return uint32_t(i); };
1781 return [&](uint8_t m) {
1782 TclObject cmd{TclObject::MakeListTag{}, tokens[2], m};
1783 return uint32_t(cmd.executeCommand(interp).getInt(interp));
1788 result = msxChars.msxToUtf8(msx, fallback);
1791std::string Keyboard::Msxcode2UnicodeCmd::help(std::span<const TclObject> )
const
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"
1806Keyboard::Unicode2MsxcodeCmd::Unicode2MsxcodeCmd(CommandController& commandController_)
1807 : Command(commandController_,
"unicode2msxcode")
1811void Keyboard::Unicode2MsxcodeCmd::execute(std::span<const TclObject> tokens, TclObject& result)
1813 checkNumArgs(tokens, Between{2, 3},
"unicode-string ?fallback?");
1815 auto& interp = getInterpreter();
1816 auto& keyboard =
OUTER(Keyboard, unicode2MsxcodeCmd);
1817 const auto& msxChars = keyboard.unicodeKeymap.getMsxChars();
1819 const auto& unicode = tokens[1].getString();
1820 auto fallback = [&]() -> std::function<uint8_t(uint32_t)> {
1821 if (tokens.size() < 3) {
1823 return [](uint32_t) {
return uint8_t(
' '); };
1824 }
else if (
auto i = tokens[2].getOptionalInt()) {
1826 return [i = *i](uint32_t) {
return uint8_t(i); };
1831 return [&](uint32_t u) {
1832 TclObject cmd{TclObject::MakeListTag{}, tokens[2], u};
1833 return uint8_t(cmd.executeCommand(interp).getInt(interp));
1838 result = msxChars.utf8ToMsx(unicode, fallback);
1841std::string Keyboard::Unicode2MsxcodeCmd::help(std::span<const TclObject> )
const
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.";
1868Keyboard::CapsLockAligner::CapsLockAligner(
1869 EventDistributor& eventDistributor_,
1870 Scheduler& scheduler_)
1872 , eventDistributor(eventDistributor_)
1875 eventDistributor.registerEventListener(type, *
this);
1879Keyboard::CapsLockAligner::~CapsLockAligner()
1882 eventDistributor.unregisterEventListener(type, *
this);
1886int Keyboard::CapsLockAligner::signalEvent(
const Event& event)
1888 if constexpr (!SANE_CAPSLOCK_BEHAVIOR) {
1893 if (state == IDLE) {
1894 EmuTime::param time = getCurrentTime();
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);
1904 [&](
const BootEvent&) {
1905 state = MUST_ALIGN_CAPSLOCK;
1914void Keyboard::CapsLockAligner::executeUntil(EmuTime::param time)
1917 case MUST_ALIGN_CAPSLOCK:
1918 alignCapsLock(time);
1920 case MUST_DISTRIBUTE_KEY_RELEASE: {
1921 auto& keyboard =
OUTER(Keyboard, capsLockAligner);
1923 keyboard.msxEventDistributor.distributeEvent(event, time);
1942void Keyboard::CapsLockAligner::alignCapsLock(EmuTime::param time)
1944 bool hostCapsLockOn = ((SDL_GetModState() & KMOD_CAPS) != 0);
1945 auto& keyboard =
OUTER(Keyboard, capsLockAligner);
1947 keyboard.debug(
"Resyncing host and MSX CAPS lock\n");
1951 keyboard.msxEventDistributor.distributeEvent(event, time);
1952 keyboard.debug(
"Sending fake CAPS release\n");
1953 state = MUST_DISTRIBUTE_KEY_RELEASE;
1963Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard_)
1964 : SimpleDebuggable(motherBoard_,
"keymatrix",
"MSX Keyboard Matrix",
1965 KeyMatrixPosition::NUM_ROWS)
1969uint8_t Keyboard::KeybDebuggable::read(
unsigned address)
1971 auto& keyboard =
OUTER(Keyboard, keybDebuggable);
1972 return keyboard.getKeys()[address];
1975void Keyboard::KeybDebuggable::write(
unsigned , uint8_t )
1981template<
typename Archive>
1982void Keyboard::KeyInserter::serialize(Archive& ar,
unsigned )
1984 ar.template serializeBase<Schedulable>(*
this);
1985 ar.serialize(
"text", text_utf8,
1987 "lockKeysMask", lockKeysMask,
1988 "releaseLast", releaseLast);
1990 bool oldCodeKanaLockOn, oldGraphLockOn, oldCapsLockOn;
1991 if constexpr (!Archive::IS_LOADER) {
1996 ar.serialize(
"oldCodeKanaLockOn", oldCodeKanaLockOn,
1997 "oldGraphLockOn", oldGraphLockOn,
1998 "oldCapsLockOn", oldCapsLockOn);
1999 if constexpr (Archive::IS_LOADER) {
2002 | (oldCapsLockOn ?
KeyInfo::CAPS_MASK : 0);
2020template<
typename Archive>
2023 ar.serialize(
"keyTypeCmd", keyTypeCmd,
2024 "cmdKeyMatrix", cmdKeyMatrix);
2025 if (ar.versionAtLeast(version, 3)) {
2026 ar.serialize(
"typeKeyMatrix", typeKeyMatrix);
2028 typeKeyMatrix = cmdKeyMatrix;
2031 bool msxCapsLockOn, msxCodeKanaLockOn, msxGraphLockOn;
2032 if constexpr (!Archive::IS_LOADER) {
2037 ar.serialize(
"msxCapsLockOn", msxCapsLockOn,
2038 "msxCodeKanaLockOn", msxCodeKanaLockOn,
2039 "msxGraphLockOn", msxGraphLockOn);
2040 if constexpr (Archive::IS_LOADER) {
2046 if (ar.versionAtLeast(version, 2)) {
2047 ar.serialize(
"userKeyMatrix", userKeyMatrix,
2048 "msxmodifiers", msxModifiers,
2049 "msxKeyEventQueue", msxKeyEventQueue);
2051 if (ar.versionAtLeast(version, 4)) {
2052 ar.serialize(
"lastUnicodeForKeycode", lastUnicodeForKeycode);
2065 if constexpr (Archive::IS_LOADER) {
2072template<
typename Archive>
2073void Keyboard::MsxKeyEventQueue::serialize(Archive& ar,
unsigned )
2075 ar.template serializeBase<Schedulable>(*
this);
2084 std::vector<std::string> eventStrs;
2085 if constexpr (!Archive::IS_LOADER) {
2087 eventQueue, [](
const auto& e) {
return toString(e); }));
2089 ar.serialize(
"eventQueue", eventStrs);
2090 if constexpr (Archive::IS_LOADER) {
2091 assert(eventQueue.empty());
2092 for (
auto& s : eventStrs) {
2093 eventQueue.push_back(
static constexpr EmuDuration sec(unsigned x)
static constexpr EmuDuration hz(unsigned x)
static KeyDownEvent create(SDL_Keycode code, SDL_Keymod mod=KMOD_NONE)
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_)
void serialize(Archive &ar, unsigned)
uint8_t getRelease() const
static KeyUpEvent create(SDL_Keycode code, SDL_Keymod mod=KMOD_NONE)
bool getAlwaysEnableKeypad() const
bool getTraceKeyPresses() const
MappingMode getMappingMode() const
KpEnterMode getKpEnterMode() const
bool getAutoToggleCodeKanaLock() 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.
void transferHostKeyMatrix(const Keyboard &source)
void serialize(Archive &ar, unsigned version)
std::span< const uint8_t, KeyMatrixPosition::NUM_ROWS > getKeys() const
Returns a pointer to the current KeyBoard matrix.
const MsxChar2Unicode & getMsxChar2Unicode() const
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....
This file implemented 3 utility functions:
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)
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
UnicodeKeymap::KeyInfo KeyInfo
ArgsInfo flagArg(std::string_view name, bool &flag)
constexpr void fill(ForwardRange &&range, const T &value)
constexpr void sort(RandomAccessRange &&range)
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
constexpr bool is_pua(uint32_t cp)
uint32_t next(octet_iterator &it, octet_iterator end)
constexpr auto transform(Range &&range, UnaryOp op)
#define OUTER(type, member)
auto * binary_find(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
constexpr auto to_underlying(E e) noexcept
std::array< SDL_Scancode, 3 > hostScanCodes
std::array< SDL_Keycode, 3 > hostKeyCodes
static SDLKey create(SDL_Keycode code, bool down, uint16_t mod=0)
SDL_Scancode hostScanCode
static constexpr uint8_t CAPS_MASK
static constexpr uint8_t SHIFT_MASK
static constexpr uint8_t GRAPH_MASK
static constexpr uint8_t CODE_MASK
constexpr auto xrange(T e)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)