openMSX
Keyboard.cc
Go to the documentation of this file.
1 #include "Keyboard.hh"
2 #include "Keys.hh"
3 #include "DeviceConfig.hh"
4 #include "EventDistributor.hh"
5 #include "InputEventFactory.hh"
6 #include "MSXEventDistributor.hh"
8 #include "MSXMotherBoard.hh"
9 #include "ReverseManager.hh"
10 #include "CommandController.hh"
11 #include "CommandException.hh"
12 #include "InputEvents.hh"
13 #include "StateChange.hh"
14 #include "TclArgParser.hh"
15 #include "utf8_checked.hh"
16 #include "checked_cast.hh"
17 #include "unreachable.hh"
18 #include "serialize.hh"
19 #include "serialize_stl.hh"
20 #include "serialize_meta.hh"
21 #include "openmsx.hh"
22 #include "outer.hh"
23 #include "stl.hh"
24 #include "view.hh"
25 #include <SDL.h>
26 #include <cstdio>
27 #include <cstring>
28 #include <cassert>
29 #include <cstdarg>
30 
31 using std::string;
32 using std::vector;
33 using std::shared_ptr;
34 using std::make_shared;
35 
36 namespace openmsx {
37 
38 static const int TRY_AGAIN = 0x80; // see pressAscii()
39 
41 
42 class KeyMatrixState final : public StateChange
43 {
44 public:
45  KeyMatrixState() = default; // for serialize
46  KeyMatrixState(EmuTime::param time_, byte row_, byte press_, byte release_)
47  : StateChange(time_)
48  , row(row_), press(press_), release(release_)
49  {
50  // disallow useless events
51  assert((press != 0) || (release != 0));
52  // avoid confusion about what happens when some bits are both
53  // set and reset (in other words: don't rely on order of and-
54  // and or-operations)
55  assert((press & release) == 0);
56  }
57  byte getRow() const { return row; }
58  byte getPress() const { return press; }
59  byte getRelease() const { return release; }
60 
61  template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
62  {
63  ar.template serializeBase<StateChange>(*this);
64  ar.serialize("row", row,
65  "press", press,
66  "release", release);
67  }
68 private:
69  byte row, press, release;
70 };
72 
73 
74 static const char* defaultKeymapForMatrix[] = {
75  "int", // MATRIX_MSX
76  "svi", // MATRIX_SVI
77  "cvjoy", // MATRIX_CVJOY
78 };
79 
80 static const std::array<KeyMatrixPosition, UnicodeKeymap::KeyInfo::NUM_MODIFIERS>
81  modifierPosForMatrix[] = {
82  { // MATRIX_MSX
83  KeyMatrixPosition(6, 0), // SHIFT
84  KeyMatrixPosition(6, 1), // CTRL
85  KeyMatrixPosition(6, 2), // GRAPH
86  KeyMatrixPosition(6, 3), // CAPS
87  KeyMatrixPosition(6, 4), // CODE
88  },
89  { // MATRIX_SVI
90  KeyMatrixPosition(6, 0), // SHIFT
91  KeyMatrixPosition(6, 1), // CTRL
92  KeyMatrixPosition(6, 2), // LGRAPH
93  KeyMatrixPosition(8, 3), // CAPS
94  KeyMatrixPosition(6, 3), // RGRAPH
95  },
96  { // MATRIX_CVJOY
97  },
98 };
99 
101  Scheduler& scheduler_,
102  CommandController& commandController_,
103  EventDistributor& eventDistributor,
104  MSXEventDistributor& msxEventDistributor_,
105  StateChangeDistributor& stateChangeDistributor_,
106  MatrixType matrix,
107  const DeviceConfig& config)
108  : commandController(commandController_)
109  , msxEventDistributor(msxEventDistributor_)
110  , stateChangeDistributor(stateChangeDistributor_)
111  , keyTab(keyTabs[matrix])
112  , modifierPos(modifierPosForMatrix[matrix])
113  , keyMatrixUpCmd (commandController, stateChangeDistributor, scheduler_)
114  , keyMatrixDownCmd(commandController, stateChangeDistributor, scheduler_)
115  , keyTypeCmd (commandController, stateChangeDistributor, scheduler_)
116  , capsLockAligner(eventDistributor, scheduler_)
117  , keyboardSettings(commandController)
118  , msxKeyEventQueue(scheduler_, commandController.getInterpreter())
119  , keybDebuggable(motherBoard)
120  , unicodeKeymap(config.getChildData(
121  "keyboard_type", defaultKeymapForMatrix[matrix]))
122  , hasKeypad(config.getChildDataAsBool("has_keypad", true))
123  , blockRow11(matrix == MATRIX_MSX
124  && !config.getChildDataAsBool("has_yesno_keys", false))
125  , keyGhosting(config.getChildDataAsBool("key_ghosting", true))
126  , keyGhostingSGCprotected(config.getChildDataAsBool(
127  "key_ghosting_sgc_protected", true))
128  , modifierIsLock(KeyInfo::CAPS_MASK
129  | (config.getChildDataAsBool("code_kana_locks", false) ? KeyInfo::CODE_MASK : 0)
130  | (config.getChildDataAsBool("graph_locks", false) ? KeyInfo::GRAPH_MASK : 0))
131  , locksOn(0)
132 {
133  keysChanged = false;
134  msxmodifiers = 0xff;
135  ranges::fill(keyMatrix, 255);
136  ranges::fill(cmdKeyMatrix, 255);
137  ranges::fill(typeKeyMatrix, 255);
138  ranges::fill(userKeyMatrix, 255);
139  ranges::fill(hostKeyMatrix, 255);
140  ranges::fill(dynKeymap, 0);
141 
142  msxEventDistributor.registerEventListener(*this);
143  stateChangeDistributor.registerListener(*this);
144  // We do not listen for CONSOLE_OFF_EVENTS because rescanning the
145  // keyboard can have unwanted side effects
146 
147  motherBoard.getReverseManager().registerKeyboard(*this);
148 }
149 
151 {
152  stateChangeDistributor.unregisterListener(*this);
153  msxEventDistributor.unregisterEventListener(*this);
154 }
155 
156 template<unsigned NUM_ROWS>
157 static void doKeyGhosting(byte (&matrix)[NUM_ROWS], bool protectRow6)
158 {
159  // This routine enables keyghosting as seen on a real MSX
160  //
161  // If on a real MSX in the keyboardmatrix the
162  // real buttons are pressed as in the left matrix
163  // then the matrix to the
164  // 10111111 right will be read by 10110101
165  // 11110101 because of the simple 10110101
166  // 10111101 electrical connections 10110101
167  // that are established by
168  // the closed switches
169  // However, some MSX models have protection against
170  // key-ghosting for SHIFT, GRAPH and CODE keys
171  // On those models, SHIFT, GRAPH and CODE are
172  // connected to row 6 via a diode. It prevents that
173  // SHIFT, GRAPH and CODE get ghosted to another
174  // row.
175  bool changedSomething;
176  do {
177  changedSomething = false;
178  for (unsigned i = 0; i < NUM_ROWS - 1; i++) {
179  auto row1 = matrix[i];
180  for (unsigned j = i + 1; j < NUM_ROWS; j++) {
181  auto row2 = matrix[j];
182  if ((row1 != row2) && ((row1 | row2) != 0xff)) {
183  auto rowIold = matrix[i];
184  auto rowJold = matrix[j];
185  // TODO: The shift/graph/code key ghosting protection
186  // implementation is only correct for MSX.
187  if (protectRow6 && i == 6) {
188  matrix[i] = row1 & row2;
189  matrix[j] = (row1 | 0x15) & row2;
190  row1 &= row2;
191  } else if (protectRow6 && j == 6) {
192  matrix[i] = row1 & (row2 | 0x15);
193  matrix[j] = row1 & row2;
194  row1 &= (row2 | 0x15);
195  } else {
196  // not same and some common zero's
197  // --> inherit other zero's
198  auto newRow = row1 & row2;
199  matrix[i] = newRow;
200  matrix[j] = newRow;
201  row1 = newRow;
202  }
203  if (rowIold != matrix[i] ||
204  rowJold != matrix[j]) {
205  changedSomething = true;
206  }
207  }
208  }
209  }
210  } while (changedSomething);
211 }
212 
213 const byte* Keyboard::getKeys() const
214 {
215  if (keysChanged) {
216  keysChanged = false;
217  const auto* matrix = keyTypeCmd.isActive() ? typeKeyMatrix : userKeyMatrix;
218  for (unsigned row = 0; row < KeyMatrixPosition::NUM_ROWS; ++row) {
219  keyMatrix[row] = cmdKeyMatrix[row] & matrix[row];
220  }
221  if (keyGhosting) {
222  doKeyGhosting(keyMatrix, keyGhostingSGCprotected);
223  }
224  }
225  return keyMatrix;
226 }
227 
229 {
230  // This mechanism exists to solve the following problem:
231  // - play a game where the spacebar is constantly pressed (e.g.
232  // Road Fighter)
233  // - go back in time (press the reverse hotkey) while keeping the
234  // spacebar pressed
235  // - interrupt replay by pressing the cursor keys, still while
236  // keeping spacebar pressed
237  // At the moment replay is interrupted, we need to resynchronize the
238  // msx keyboard with the host keyboard. In the past we assumed the host
239  // keyboard had no keys pressed. But this is wrong in the above
240  // scenario. Now we remember the state of the host keyboard and
241  // transfer that to the new keyboard(s) that get created for reverese.
242  // When replay is stopped we restore this host keyboard state, see
243  // stopReplay().
244 
245  for (unsigned row = 0; row < KeyMatrixPosition::NUM_ROWS; ++row) {
246  hostKeyMatrix[row] = source.hostKeyMatrix[row];
247  }
248 }
249 
250 /* Received an MSX event
251  * Following events get processed:
252  * OPENMSX_KEY_DOWN_EVENT
253  * OPENMSX_KEY_UP_EVENT
254  */
255 void Keyboard::signalMSXEvent(const shared_ptr<const Event>& event,
256  EmuTime::param time)
257 {
258  EventType type = event->getType();
259  if ((type == OPENMSX_KEY_DOWN_EVENT) ||
260  (type == OPENMSX_KEY_UP_EVENT)) {
261  // Ignore possible console on/off events:
262  // we do not rescan the keyboard since this may lead to
263  // an unwanted pressing of <return> in MSX after typing
264  // "set console off" in the console.
265  msxKeyEventQueue.process_asap(time, event);
266  }
267 }
268 
269 void Keyboard::signalStateChange(const shared_ptr<StateChange>& event)
270 {
271  auto kms = dynamic_cast<KeyMatrixState*>(event.get());
272  if (!kms) return;
273 
274  userKeyMatrix[kms->getRow()] &= ~kms->getPress();
275  userKeyMatrix[kms->getRow()] |= kms->getRelease();
276  keysChanged = true; // do ghosting at next getKeys()
277 }
278 
279 void Keyboard::stopReplay(EmuTime::param time)
280 {
281  for (unsigned row = 0; row < KeyMatrixPosition::NUM_ROWS; ++row) {
282  changeKeyMatrixEvent(time, row, hostKeyMatrix[row]);
283  }
284  msxmodifiers = 0xff;
285  msxKeyEventQueue.clear();
286  memset(dynKeymap, 0, sizeof(dynKeymap));
287 }
288 
289 byte Keyboard::needsLockToggle(const UnicodeKeymap::KeyInfo& keyInfo) const
290 {
291  return modifierIsLock
292  & (locksOn ^ keyInfo.modmask)
293  & unicodeKeymap.getRelevantMods(keyInfo);
294 }
295 
296 void Keyboard::pressKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
297 {
298  if (!pos.isValid()) {
299  // No such key.
300  return;
301  }
302  auto row = pos.getRow();
303  auto press = pos.getMask();
304  if (((hostKeyMatrix[row] & press) == 0) &&
305  ((userKeyMatrix[row] & press) == 0)) {
306  // Won't have any effect, ignore.
307  return;
308  }
309  changeKeyMatrixEvent(time, row, hostKeyMatrix[row] & ~press);
310 }
311 
312 void Keyboard::releaseKeyMatrixEvent(EmuTime::param time, KeyMatrixPosition pos)
313 {
314  if (!pos.isValid()) {
315  // No such key.
316  return;
317  }
318  auto row = pos.getRow();
319  auto release = pos.getMask();
320  if (((hostKeyMatrix[row] & release) == release) &&
321  ((userKeyMatrix[row] & release) == release)) {
322  // Won't have any effect, ignore.
323  // Test scenario: during replay, exit the openmsx console with
324  // the 'toggle console' command. The 'enter,release' event will
325  // end up here. But it shouldn't stop replay.
326  return;
327  }
328  changeKeyMatrixEvent(time, row, hostKeyMatrix[row] | release);
329 }
330 
331 void Keyboard::changeKeyMatrixEvent(EmuTime::param time, byte row, byte newValue)
332 {
333  // This method already updates hostKeyMatrix[],
334  // userKeyMatrix[] will soon be updated via KeyMatrixState events.
335  hostKeyMatrix[row] = newValue;
336 
337  byte diff = userKeyMatrix[row] ^ newValue;
338  if (diff == 0) return;
339  byte press = userKeyMatrix[row] & diff;
340  byte release = newValue & diff;
341  stateChangeDistributor.distributeNew(make_shared<KeyMatrixState>(
342  time, row, press, release));
343 }
344 
345 /*
346  * @return True iff a release event for the CODE/KANA key must be scheduled.
347  */
348 bool Keyboard::processQueuedEvent(const Event& event, EmuTime::param time)
349 {
350  auto& keyEvent = checked_cast<const KeyEvent&>(event);
351  bool down = event.getType() == OPENMSX_KEY_DOWN_EVENT;
352  auto key = static_cast<Keys::KeyCode>(
353  int(keyEvent.getKeyCode()) & int(Keys::K_MASK));
354  if (down) {
355  // TODO: refactor debug(...) method to expect a std::string and then adapt
356  // all invocations of it to provide a properly formatted string, using the C++
357  // features for it.
358  // Once that is done, debug(...) can pass the c_str() version of that string
359  // to ad_printf(...) so that I don't have to make an explicit ad_printf(...)
360  // invocation for each debug(...) invocation
361  ad_printf("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n",
362  keyEvent.getUnicode(),
363  keyEvent.getKeyCode(),
364  Keys::getName(keyEvent.getKeyCode()).c_str());
365  debug("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n",
366  keyEvent.getUnicode(),
367  keyEvent.getKeyCode(),
368  Keys::getName(keyEvent.getKeyCode()).c_str());
369  } else {
370  ad_printf("Key released, keyCode: 0x%05x, keyName: %s\n",
371  keyEvent.getKeyCode(),
372  Keys::getName(keyEvent.getKeyCode()).c_str());
373  debug("Key released, keyCode: 0x%05x, keyName: %s\n",
374  keyEvent.getKeyCode(),
375  Keys::getName(keyEvent.getKeyCode()).c_str());
376  }
377 
378  // Process deadkeys.
379  if (keyboardSettings.getMappingMode() == KeyboardSettings::CHARACTER_MAPPING) {
380  for (unsigned n = 0; n < 3; n++) {
381  if (key == keyboardSettings.getDeadkeyHostKey(n)) {
382  UnicodeKeymap::KeyInfo deadkey = unicodeKeymap.getDeadkey(n);
383  if (deadkey.isValid()) {
384  updateKeyMatrix(time, down, deadkey.pos);
385  return false;
386  }
387  }
388  }
389  }
390 
391  if (key == Keys::K_CAPSLOCK) {
392  processCapslockEvent(time, down);
393  return false;
394  } else if (key == keyboardSettings.getCodeKanaHostKey()) {
395  processCodeKanaChange(time, down);
396  return false;
397  } else if (key == Keys::K_LALT) {
398  processGraphChange(time, down);
399  return false;
400  } else if (key == Keys::K_KP_ENTER) {
401  processKeypadEnterKey(time, down);
402  return false;
403  } else {
404  return processKeyEvent(time, down, keyEvent);
405  }
406 }
407 
408 /*
409  * Process a change (up or down event) of the CODE/KANA key
410  * It presses or releases the key in the MSX keyboard matrix
411  * and changes the kanalock state in case of a press
412  */
413 void Keyboard::processCodeKanaChange(EmuTime::param time, bool down)
414 {
415  if (down) {
416  locksOn ^= KeyInfo::CODE_MASK;
417  }
418  updateKeyMatrix(time, down, modifierPos[KeyInfo::CODE]);
419 }
420 
421 /*
422  * Process a change (up or down event) of the GRAPH key
423  * It presses or releases the key in the MSX keyboard matrix
424  * and changes the graphlock state in case of a press
425  */
426 void Keyboard::processGraphChange(EmuTime::param time, bool down)
427 {
428  if (down) {
429  locksOn ^= KeyInfo::GRAPH_MASK;
430  }
431  updateKeyMatrix(time, down, modifierPos[KeyInfo::GRAPH]);
432 }
433 
434 /*
435  * Process a change (up or down event) of the CAPSLOCK key
436  * It presses or releases the key in the MSX keyboard matrix
437  * and changes the capslock state in case of a press
438  */
439 void Keyboard::processCapslockEvent(EmuTime::param time, bool down)
440 {
441  debug("Changing CAPS lock state according to SDL request\n");
442  if (down) {
443  locksOn ^= KeyInfo::CAPS_MASK;
444  }
445  updateKeyMatrix(time, down, modifierPos[KeyInfo::CAPS]);
446 }
447 
448 void Keyboard::processKeypadEnterKey(EmuTime::param time, bool down)
449 {
450  if (!hasKeypad && !keyboardSettings.getAlwaysEnableKeypad()) {
451  // User entered on host keypad but this MSX model does not have one
452  // Ignore the keypress/release
453  return;
454  }
455  processSdlKey(time, down,
456  keyboardSettings.getKpEnterMode() == KeyboardSettings::MSX_KP_COMMA
458 }
459 
460 /*
461  * Process an SDL key press/release event. It concerns a
462  * special key (e.g. SHIFT, UP, DOWN, F1, F2, ...) that can not
463  * be unambiguously derived from a unicode character;
464  * Map the SDL key to an equivalent MSX key press/release event
465  */
466 void Keyboard::processSdlKey(EmuTime::param time, bool down, Keys::KeyCode key)
467 {
468  if (key < MAX_KEYSYM) {
469  auto pos = keyTab[key];
470  if (pos.isValid()) {
471  if (pos.getRow() == 11 && blockRow11) {
472  // do not process row 11 if we have no Yes/No keys
473  return;
474  }
475  updateKeyMatrix(time, down, pos);
476  }
477  }
478 }
479 
480 /*
481  * Update the MSX keyboard matrix
482  */
483 void Keyboard::updateKeyMatrix(EmuTime::param time, bool down, KeyMatrixPosition pos)
484 {
485  if (!pos.isValid()) {
486  // No such key.
487  return;
488  }
489  if (down) {
490  pressKeyMatrixEvent(time, pos);
491  // Keep track of the MSX modifiers.
492  // The MSX modifiers sometimes get overruled by the unicode character
493  // processing, in which case the unicode processing must be able to
494  // restore them to the real key-combinations pressed by the user.
495  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
496  if (pos == modifierPos[i]) {
497  msxmodifiers &= ~(1 << i);
498  }
499  }
500  } else {
501  releaseKeyMatrixEvent(time, pos);
502  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
503  if (pos == modifierPos[i]) {
504  msxmodifiers |= 1 << i;
505  }
506  }
507  }
508 }
509 
510 /*
511  * Process an SDL key event;
512  * Check if it is a special key, in which case it can be directly
513  * mapped to the MSX matrix.
514  * Otherwise, retrieve the unicode character value for the event
515  * and map the unicode character to the key-combination that must
516  * be pressed to generate the equivalent character on the MSX
517  * @return True iff a release event for the CODE/KANA key must be scheduled.
518  */
519 bool Keyboard::processKeyEvent(EmuTime::param time, bool down, const KeyEvent& keyEvent)
520 {
521  Keys::KeyCode keyCode = keyEvent.getKeyCode();
522  auto key = static_cast<Keys::KeyCode>(
523  int(keyCode) & int(Keys::K_MASK));
524  unsigned unicode;
525 
526  bool isOnKeypad = (
527  (key >= Keys::K_KP0 && key <= Keys::K_KP9) ||
528  (key == Keys::K_KP_PERIOD) ||
529  (key == Keys::K_KP_DIVIDE) ||
530  (key == Keys::K_KP_MULTIPLY) ||
531  (key == Keys::K_KP_MINUS) ||
532  (key == Keys::K_KP_PLUS));
533 
534  if (isOnKeypad && !hasKeypad &&
535  !keyboardSettings.getAlwaysEnableKeypad()) {
536  // User entered on host keypad but this MSX model does not have one
537  // Ignore the keypress/release
538  return false;
539  }
540 
541  if (down) {
542  UnicodeKeymap::KeyInfo keyInfo;
543  if (isOnKeypad ||
544  keyboardSettings.getMappingMode() == KeyboardSettings::KEY_MAPPING) {
545  // User entered a key on numeric keypad or the driver is in KEY
546  // mapping mode.
547  // First option (keypad) maps to same unicode as some other key
548  // combinations (e.g. digit on main keyboard or TAB/DEL).
549  // Use unicode to handle the more common combination and use direct
550  // matrix to matrix mapping for the exceptional cases (keypad).
551  unicode = 0;
552 #if defined(__APPLE__)
553  } else if ((keyCode & (Keys::K_MASK | Keys::KM_META))
554  == (Keys::K_I | Keys::KM_META)) {
555  // Apple keyboards don't have an Insert key, use Cmd+I as an alternative.
556  keyCode = key = Keys::K_INSERT;
557  unicode = 0;
558 #endif
559  } else {
560  unicode = keyEvent.getUnicode();
561  if ((unicode < 0x20) || ((0x7F <= unicode) && (unicode < 0xA0))) {
562  // Control character in C0 or C1 range.
563  // Use SDL's interpretation instead.
564  unicode = 0;
565  } else if (utf8::is_pua(unicode)) {
566  // Code point in Private Use Area: undefined by Unicode,
567  // so we rely on SDL's interpretation instead.
568  // For example the Mac's cursor keys are in this range.
569  unicode = 0;
570  } else {
571  keyInfo = unicodeKeymap.get(unicode);
572  if (!keyInfo.isValid()) {
573  // Unicode does not exist in our mapping; try to process
574  // the key using its keycode instead.
575  unicode = 0;
576  }
577  }
578  }
579  if (key < MAX_KEYSYM) {
580  // Remember which unicode character is currently derived
581  // from this SDL key. It must be stored here (during key-press)
582  // because during key-release SDL never returns the unicode
583  // value (it always returns the value 0). But we must know
584  // the unicode value in order to be able to perform the correct
585  // key-combination-release in the MSX
586  dynKeymap[key] = unicode;
587  } else {
588  // Unexpectedly high key-code. Can't store the unicode
589  // character for this key. Instead directly treat the key
590  // via matrix to matrix mapping
591  unicode = 0;
592  }
593  if (unicode == 0) {
594  // It was an ambiguous key (numeric key-pad, CTRL+character)
595  // or a special key according to SDL (like HOME, INSERT, etc)
596  // or a first keystroke of a composed key
597  // (e.g. altr-gr + = on azerty keyboard) or driver is in
598  // direct SDL mapping mode:
599  // Perform direct SDL matrix to MSX matrix mapping
600  // But only when it is not a first keystroke of a
601  // composed key
602  if ((keyCode & Keys::KM_MODE) == 0) {
603  processSdlKey(time, down, key);
604  }
605  return false;
606  } else {
607  // It is a unicode character; map it to the right key-combination
608  return pressUnicodeByUser(time, keyInfo, unicode, true);
609  }
610  } else {
611  // key was released
612 #if defined(__APPLE__)
613  if ((keyCode & (Keys::K_MASK | Keys::KM_META))
614  == (Keys::K_I | Keys::KM_META)) {
615  keyCode = key = Keys::K_INSERT;
616  }
617 #endif
618  if (key < MAX_KEYSYM) {
619  unicode = dynKeymap[key]; // Get the unicode that was derived from this key
620  } else {
621  unicode = 0;
622  }
623  if (unicode == 0) {
624  // It was a special key, perform matrix to matrix mapping
625  // But only when it is not a first keystroke of a
626  // composed key
627  if ((keyCode & Keys::KM_MODE) == 0) {
628  processSdlKey(time, down, key);
629  }
630  } else {
631  // It was a unicode character; map it to the right key-combination
632  pressUnicodeByUser(time, unicodeKeymap.get(unicode), unicode, false);
633  }
634  return false;
635  }
636 }
637 
638 void Keyboard::processCmd(Interpreter& interp, span<const TclObject> tokens, bool up)
639 {
640  unsigned row = tokens[1].getInt(interp);
641  unsigned mask = tokens[2].getInt(interp);
642  if (row >= KeyMatrixPosition::NUM_ROWS) {
643  throw CommandException("Invalid row");
644  }
645  if (mask >= 256) {
646  throw CommandException("Invalid mask");
647  }
648  if (up) {
649  cmdKeyMatrix[row] |= mask;
650  } else {
651  cmdKeyMatrix[row] &= ~mask;
652  }
653  keysChanged = true;
654 }
655 
656 /*
657  * This routine processes unicode characters. It maps a unicode character
658  * to the correct key-combination on the MSX.
659  *
660  * There are a few caveats with respect to the MSX and Host modifier keys
661  * that you must be aware about if you want to understand why the routine
662  * works as it works.
663  *
664  * Row 6 of the MSX keyboard matrix contains the MSX modifier keys:
665  * CTRL, CODE, GRAPH and SHIFT
666  *
667  * The SHIFT key is also a modifier key on the host machine. However, the
668  * SHIFT key behaviour can differ between HOST and MSX for all 'special'
669  * characters (anything but A-Z).
670  * For example, on AZERTY host keyboard, user presses SHIFT+& to make the '1'
671  * On MSX QWERTY keyboard, the same key-combination leads to '!'.
672  * So this routine must not only PRESS the SHIFT key when required according
673  * to the unicode mapping table but it must also RELEASE the SHIFT key for all
674  * these special keys when the user PRESSES the key/character.
675  *
676  * On the other hand, for A-Z, this routine must not touch the SHIFT key at all.
677  * Otherwise it might give strange behaviour when CAPS lock is on (which also
678  * acts as a key-modifier for A-Z). The routine can rely on the fact that
679  * SHIFT+A-Z behaviour is the same on all host and MSX keyboards. It is
680  * approximately the only part of keyboards that is de-facto standardized :-)
681  *
682  * For the other modifiers (CTRL, CODE and GRAPH), the routine must be able to
683  * PRESS them when required but there is no need to RELEASE them during
684  * character press. On the contrary; the host keys that map to CODE and GRAPH
685  * do not work as modifiers on the host itself, so if the routine would release
686  * them, it would give wrong result.
687  * For example, 'ALT-A' on Host will lead to unicode character 'a', just like
688  * only pressing the 'A' key. The MSX however must know about the difference.
689  *
690  * As a reminder: here is the build-up of row 6 of the MSX key matrix
691  * 7 6 5 4 3 2 1 0
692  * row 6 | F3 | F2 | F1 | code| caps|graph| ctrl|shift|
693  */
694 bool Keyboard::pressUnicodeByUser(
695  EmuTime::param time, UnicodeKeymap::KeyInfo keyInfo, unsigned unicode,
696  bool down)
697 {
698  bool insertCodeKanaRelease = false;
699  if (down) {
700  if ((needsLockToggle(keyInfo) & KeyInfo::CODE_MASK) &&
701  keyboardSettings.getAutoToggleCodeKanaLock()) {
702  // Code Kana locks, is in wrong state and must be auto-toggled:
703  // Toggle it by pressing the lock key and scheduling a
704  // release event
705  locksOn ^= KeyInfo::CODE_MASK;
706  pressKeyMatrixEvent(time, modifierPos[KeyInfo::CODE]);
707  insertCodeKanaRelease = true;
708  } else {
709  // Press the character key and related modifiers
710  // Ignore the CODE key in case that Code Kana locks
711  // (e.g. do not press it).
712  // Ignore the GRAPH key in case that Graph locks
713  // Always ignore CAPSLOCK mask (assume that user will
714  // use real CAPS lock to switch/ between hiragana and
715  // katanana on japanese model)
716  pressKeyMatrixEvent(time, keyInfo.pos);
717 
718  byte modmask = keyInfo.modmask & ~modifierIsLock;
719  if (('A' <= unicode && unicode <= 'Z') || ('a' <= unicode && unicode <= 'z')) {
720  // For a-z and A-Z, leave SHIFT unchanged, this to cater
721  // for difference in behaviour between host and emulated
722  // machine with respect to how the combination of CAPSLOCK
723  // and SHIFT is interpreted for these characters.
724  modmask &= ~KeyInfo::SHIFT_MASK;
725  } else {
726  // Release SHIFT if our character does not require it.
727  if (~modmask & KeyInfo::SHIFT_MASK) {
728  releaseKeyMatrixEvent(time, modifierPos[KeyInfo::SHIFT]);
729  }
730  }
731  // Press required modifiers for our character.
732  // Note that these modifiers are only pressed, never released.
733  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
734  if ((modmask >> i) & 1) {
735  pressKeyMatrixEvent(time, modifierPos[i]);
736  }
737  }
738  }
739  } else {
740  releaseKeyMatrixEvent(time, keyInfo.pos);
741 
742  // Restore non-lock modifier keys.
743  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
744  if (!((modifierIsLock >> i) & 1)) {
745  // Do not simply unpress graph, ctrl, code and shift but
746  // restore them to the values currently pressed by the user.
747  if ((msxmodifiers >> i) & 1) {
748  releaseKeyMatrixEvent(time, modifierPos[i]);
749  } else {
750  pressKeyMatrixEvent(time, modifierPos[i]);
751  }
752  }
753  }
754  }
755  keysChanged = true;
756  return insertCodeKanaRelease;
757 }
758 
759 /*
760  * Press an ASCII character. It is used by the 'Insert characters'
761  * function that is exposed to the console.
762  * The characters are inserted in a separate keyboard matrix, to prevent
763  * interference with the keypresses of the user on the MSX itself.
764  *
765  * @returns:
766  * zero : handling this character is done
767  * non-zero: typing this character needs additional actions
768  * bits 0-4: release these modifiers and call again
769  * bit 7 : simply call again (used by SHIFT+GRAPH heuristic)
770  */
771 int Keyboard::pressAscii(unsigned unicode, bool down)
772 {
773  int releaseMask = 0;
774  UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.get(unicode);
775  if (!keyInfo.isValid()) {
776  return releaseMask;
777  }
778  byte modmask = keyInfo.modmask & ~modifierIsLock;
779  if (down) {
780  // check for modifier toggles
781  byte toggleLocks = needsLockToggle(keyInfo);
782  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
783  if ((toggleLocks >> i) & 1) {
784  debug("Toggling lock %d\n", i);
785  locksOn ^= 1 << i;
786  releaseMask |= 1 << i;
787  auto lockPos = modifierPos[i];
788  typeKeyMatrix[lockPos.getRow()] &= ~lockPos.getMask();
789  }
790  }
791  if (releaseMask == 0) {
792  debug("Key pasted, unicode: 0x%04x, row: %02d, col: %d, modmask: %02x\n",
793  unicode, keyInfo.pos.getRow(), keyInfo.pos.getColumn(), modmask);
794  // Workaround MSX-BIOS(?) bug/limitation:
795  //
796  // Under these conditions:
797  // - Typing a graphical MSX character 00-1F (such a char 'x' gets
798  // printed as chr$(1) followed by chr$(x+64)).
799  // - Typing this character requires pressing SHIFT+GRAPH and one
800  // 'regular' key.
801  // - And all 3 keys are immediately pressed simultaneously.
802  // Then, from time to time, instead of the intended character 'x'
803  // (00-1F), the wrong character 'x+64' gets printed.
804  // When first SHIFT+GRAPH is pressed, and only one frame later the
805  // other keys is pressed (additionally), this problem seems to
806  // disappear.
807  //
808  // After implementing the above we found that a similar problem
809  // occurs when:
810  // - a GRAPH + <x> (without SHIFT) key combo is typed
811  // - immediately after a key combo with GRAPH + SHIFT + <x>.
812  // For example:
813  // type "\u2666\u266a"
814  // from time to time 2nd character got wrongly typed as a
815  // 'M' (instead of a musical note symbol). But typing a sequence
816  // of \u266a chars without a preceding \u2666 just works.
817  //
818  // To fix both these problems (and possibly still undiscovered
819  // variations), I'm now extending the workaround to all characters
820  // that are typed via a key combination that includes GRAPH.
821  if (modmask & KeyInfo::GRAPH_MASK) {
822  auto isPressed = [&](auto& key) {
823  return (typeKeyMatrix[key.getRow()] & key.getMask()) == 0;
824  };
825  if (!isPressed(modifierPos[KeyInfo::GRAPH])) {
826  // GRAPH not yet pressed ->
827  // first press it before adding the non-modifier key
828  releaseMask = TRY_AGAIN;
829  }
830  }
831  // press modifiers
832  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
833  if ((modmask >> i) & 1) {
834  auto modPos = modifierPos[i];
835  typeKeyMatrix[modPos.getRow()] &= ~modPos.getMask();
836  }
837  }
838  if (releaseMask == 0) {
839  // press key
840  typeKeyMatrix[keyInfo.pos.getRow()] &= ~keyInfo.pos.getMask();
841  }
842  }
843  } else {
844  typeKeyMatrix[keyInfo.pos.getRow()] |= keyInfo.pos.getMask();
845  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
846  if ((modmask >> i) & 1) {
847  auto modPos = modifierPos[i];
848  typeKeyMatrix[modPos.getRow()] |= modPos.getMask();
849  }
850  }
851  }
852  keysChanged = true;
853  return releaseMask;
854 }
855 
856 /*
857  * Press a lock key. It is used by the 'Insert characters'
858  * function that is exposed to the console.
859  * The characters are inserted in a separate keyboard matrix, to prevent
860  * interference with the keypresses of the user on the MSX itself
861  */
862 void Keyboard::pressLockKeys(byte lockKeysMask, bool down)
863 {
864  for (unsigned i = 0; i < KeyInfo::NUM_MODIFIERS; i++) {
865  if ((lockKeysMask >> i) & 1) {
866  auto lockPos = modifierPos[i];
867  if (down) {
868  // press lock key
869  typeKeyMatrix[lockPos.getRow()] &= ~lockPos.getMask();
870  } else {
871  // release lock key
872  typeKeyMatrix[lockPos.getRow()] |= lockPos.getMask();
873  }
874  }
875  }
876  keysChanged = true;
877 }
878 
879 /*
880  * Check if there are common keys in the MSX matrix for
881  * two different unicodes.
882  * It is used by the 'insert keys' function to determine if it has to wait for
883  * a short while after releasing a key (to enter a certain character) before
884  * pressing the next key (to enter the next character)
885  */
886 bool Keyboard::commonKeys(unsigned unicode1, unsigned unicode2)
887 {
888  // get row / mask of key (note: ignore modifier mask)
889  auto keyPos1 = unicodeKeymap.get(unicode1).pos;
890  auto keyPos2 = unicodeKeymap.get(unicode2).pos;
891 
892  return keyPos1 == keyPos2 && keyPos1.isValid();
893 }
894 
895 void Keyboard::debug(const char* format, ...)
896 {
897  if (keyboardSettings.getTraceKeyPresses()) {
898  va_list args;
899  va_start(args, format);
900  vfprintf(stderr, format, args);
901  va_end(args);
902  }
903 }
904 
905 
906 // class KeyMatrixUpCmd
907 
908 Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd(
909  CommandController& commandController_,
910  StateChangeDistributor& stateChangeDistributor_,
911  Scheduler& scheduler_)
912  : RecordedCommand(commandController_, stateChangeDistributor_,
913  scheduler_, "keymatrixup")
914 {
915 }
916 
917 void Keyboard::KeyMatrixUpCmd::execute(
918  span<const TclObject> tokens, TclObject& /*result*/, EmuTime::param /*time*/)
919 {
920  checkNumArgs(tokens, 3, Prefix{1}, "row mask");
921  auto& keyboard = OUTER(Keyboard, keyMatrixUpCmd);
922  return keyboard.processCmd(getInterpreter(), tokens, true);
923 }
924 
925 string Keyboard::KeyMatrixUpCmd::help(const vector<string>& /*tokens*/) const
926 {
927  static const string helpText =
928  "keymatrixup <row> <bitmask> release a key in the keyboardmatrix\n";
929  return helpText;
930 }
931 
932 
933 // class KeyMatrixDownCmd
934 
935 Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController_,
936  StateChangeDistributor& stateChangeDistributor_,
937  Scheduler& scheduler_)
938  : RecordedCommand(commandController_, stateChangeDistributor_,
939  scheduler_, "keymatrixdown")
940 {
941 }
942 
943 void Keyboard::KeyMatrixDownCmd::execute(span<const TclObject> tokens,
944  TclObject& /*result*/, EmuTime::param /*time*/)
945 {
946  checkNumArgs(tokens, 3, Prefix{1}, "row mask");
947  auto& keyboard = OUTER(Keyboard, keyMatrixDownCmd);
948  return keyboard.processCmd(getInterpreter(), tokens, false);
949 }
950 
951 string Keyboard::KeyMatrixDownCmd::help(const vector<string>& /*tokens*/) const
952 {
953  static const string helpText =
954  "keymatrixdown <row> <bitmask> press a key in the keyboardmatrix\n";
955  return helpText;
956 }
957 
958 
959 // class MsxKeyEventQueue
960 
961 Keyboard::MsxKeyEventQueue::MsxKeyEventQueue(
962  Scheduler& scheduler_, Interpreter& interp_)
963  : Schedulable(scheduler_)
964  , interp(interp_)
965 {
966 }
967 
968 void Keyboard::MsxKeyEventQueue::process_asap(
969  EmuTime::param time, const shared_ptr<const Event>& event)
970 {
971  bool processImmediately = eventQueue.empty();
972  eventQueue.push_back(event);
973  if (processImmediately) {
974  executeUntil(time);
975  }
976 }
977 
978 void Keyboard::MsxKeyEventQueue::clear()
979 {
980  eventQueue.clear();
981  removeSyncPoint();
982 }
983 
984 void Keyboard::MsxKeyEventQueue::executeUntil(EmuTime::param time)
985 {
986  // Get oldest event from the queue and process it
987  shared_ptr<const Event> event = eventQueue.front();
988  auto& keyboard = OUTER(Keyboard, msxKeyEventQueue);
989  bool insertCodeKanaRelease = keyboard.processQueuedEvent(*event, time);
990 
991  if (insertCodeKanaRelease) {
992  // The processor pressed the CODE/KANA key
993  // Schedule a CODE/KANA release event, to be processed
994  // before any of the other events in the queue
995  eventQueue.push_front(make_shared<KeyUpEvent>(
996  keyboard.keyboardSettings.getCodeKanaHostKey()));
997  } else {
998  // The event has been completely processed. Delete it from the queue
999  if (!eventQueue.empty()) {
1000  eventQueue.pop_front();
1001  } else {
1002  // it's possible clear() has been called
1003  // (indirectly from keyboard.processQueuedEvent())
1004  }
1005  }
1006 
1007  if (!eventQueue.empty()) {
1008  // There are still events. Process them in 1/15s from now
1009  setSyncPoint(time + EmuDuration::hz(15));
1010  }
1011 }
1012 
1013 
1014 // class KeyInserter
1015 
1016 Keyboard::KeyInserter::KeyInserter(
1017  CommandController& commandController_,
1018  StateChangeDistributor& stateChangeDistributor_,
1019  Scheduler& scheduler_)
1020  : RecordedCommand(commandController_, stateChangeDistributor_,
1021  scheduler_, "type_via_keyboard")
1022  , Schedulable(scheduler_)
1023  , lockKeysMask(0)
1024  , releaseLast(false)
1025 {
1026  // avoid UMR
1027  last = 0;
1028  oldLocksOn = 0;
1029  releaseBeforePress = false;
1030  typingFrequency = 15;
1031 }
1032 
1033 void Keyboard::KeyInserter::execute(
1034  span<const TclObject> tokens, TclObject& /*result*/, EmuTime::param /*time*/)
1035 {
1036  checkNumArgs(tokens, AtLeast{2}, "?-release? ?-freq hz? text");
1037 
1038  releaseBeforePress = false;
1039  typingFrequency = 15;
1040 
1041  // for full backwards compatibility: one option means type it...
1042  if (tokens.size() == 2) {
1043  type(tokens[1].getString());
1044  return;
1045  }
1046 
1047  ArgsInfo info[] = {
1048  flagArg("-release", releaseBeforePress),
1049  valueArg("-freq", typingFrequency),
1050  };
1051  auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(1), info);
1052 
1053  if (typingFrequency <= 0) {
1054  throw CommandException("Wrong argument for -freq (should be a positive number)");
1055  }
1056  if (arguments.size() != 1) throw SyntaxError();
1057 
1058  type(arguments[0].getString());
1059 }
1060 
1061 string Keyboard::KeyInserter::help(const vector<string>& /*tokens*/) const
1062 {
1063  static const string helpText = "Type a string in the emulated MSX.\n" \
1064  "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" \
1065  "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";
1066  return helpText;
1067 }
1068 
1069 void Keyboard::KeyInserter::tabCompletion(vector<string>& tokens) const
1070 {
1071  vector<const char*> options;
1072  if (!contains(tokens, "-release")) {
1073  options.push_back("-release");
1074  }
1075  if (!contains(tokens, "-freq")) {
1076  options.push_back("-freq");
1077  }
1078  completeString(tokens, options);
1079 }
1080 
1081 void Keyboard::KeyInserter::type(string_view str)
1082 {
1083  if (str.empty()) {
1084  return;
1085  }
1086  auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1087  oldLocksOn = keyboard.locksOn;
1088  if (text_utf8.empty()) {
1089  reschedule(getCurrentTime());
1090  }
1091  text_utf8.append(str.data(), str.size());
1092 }
1093 
1094 void Keyboard::KeyInserter::executeUntil(EmuTime::param time)
1095 {
1096  auto& keyboard = OUTER(Keyboard, keyTypeCmd);
1097  if (lockKeysMask != 0) {
1098  // release CAPS and/or Code/Kana Lock keys
1099  keyboard.pressLockKeys(lockKeysMask, false);
1100  }
1101  if (releaseLast) {
1102  keyboard.pressAscii(last, false); // release previous character
1103  }
1104  if (text_utf8.empty()) {
1105  releaseLast = false;
1106  keyboard.debug("Restoring locks: %02X -> %02X\n", keyboard.locksOn, oldLocksOn);
1107  auto diff = oldLocksOn ^ keyboard.locksOn;
1108  lockKeysMask = diff;
1109  if (diff != 0) {
1110  // press CAPS, GRAPH and/or Code/Kana Lock keys
1111  keyboard.locksOn ^= diff;
1112  keyboard.pressLockKeys(diff, true);
1113  reschedule(time);
1114  }
1115  return;
1116  }
1117 
1118  try {
1119  auto it = begin(text_utf8);
1120  unsigned current = utf8::next(it, end(text_utf8));
1121  if (releaseLast && (releaseBeforePress || keyboard.commonKeys(last, current))) {
1122  // There are common keys between previous and current character
1123  // Do not immediately press again but give MSX the time to notice
1124  // that the keys have been released
1125  releaseLast = false;
1126  } else {
1127  // All keys in current char differ from previous char. The new keys
1128  // can immediately be pressed
1129  lockKeysMask = keyboard.pressAscii(current, true);
1130  if (lockKeysMask == 0) {
1131  last = current;
1132  releaseLast = true;
1133  text_utf8.erase(begin(text_utf8), it);
1134  } else if (lockKeysMask & TRY_AGAIN) {
1135  lockKeysMask &= ~TRY_AGAIN;
1136  releaseLast = false;
1137  } else if (releaseBeforePress) {
1138  releaseLast = true;
1139  }
1140  }
1141  reschedule(time);
1142  } catch (std::exception&) {
1143  // utf8 encoding error
1144  text_utf8.clear();
1145  }
1146 }
1147 
1148 void Keyboard::KeyInserter::reschedule(EmuTime::param time)
1149 {
1150  setSyncPoint(time + EmuDuration::hz(typingFrequency));
1151 }
1152 
1153 /*
1154  * class CapsLockAligner
1155  *
1156  * It is used to align MSX CAPS lock status with the host CAPS lock status
1157  * during the reset of the MSX or after the openMSX window regains focus.
1158  *
1159  * It listens to the 'BOOT' event and schedules the real alignment
1160  * 2 seconds later. Reason is that it takes a while before the MSX
1161  * reset routine starts monitoring the MSX keyboard.
1162  *
1163  * For focus regain, the alignment is done immediately.
1164  */
1165 Keyboard::CapsLockAligner::CapsLockAligner(
1166  EventDistributor& eventDistributor_,
1167  Scheduler& scheduler_)
1168  : Schedulable(scheduler_)
1169  , eventDistributor(eventDistributor_)
1170 {
1171  state = IDLE;
1172  eventDistributor.registerEventListener(OPENMSX_BOOT_EVENT, *this);
1173  eventDistributor.registerEventListener(OPENMSX_FOCUS_EVENT, *this);
1174 }
1175 
1176 Keyboard::CapsLockAligner::~CapsLockAligner()
1177 {
1178  eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this);
1179  eventDistributor.unregisterEventListener(OPENMSX_BOOT_EVENT, *this);
1180 }
1181 
1182 int Keyboard::CapsLockAligner::signalEvent(const shared_ptr<const Event>& event)
1183 {
1184  if (state == IDLE) {
1185  EmuTime::param time = getCurrentTime();
1186  EventType type = event->getType();
1187  if (type == OPENMSX_FOCUS_EVENT) {
1188  alignCapsLock(time);
1189  } else if (type == OPENMSX_BOOT_EVENT) {
1190  state = MUST_ALIGN_CAPSLOCK;
1191  setSyncPoint(time + EmuDuration::sec(2)); // 2s (MSX time)
1192  } else {
1193  UNREACHABLE;
1194  }
1195  }
1196  return 0;
1197 }
1198 
1199 void Keyboard::CapsLockAligner::executeUntil(EmuTime::param time)
1200 {
1201  switch (state) {
1202  case MUST_ALIGN_CAPSLOCK:
1203  alignCapsLock(time);
1204  break;
1205  case MUST_DISTRIBUTE_KEY_RELEASE: {
1206  auto& keyboard = OUTER(Keyboard, capsLockAligner);
1207  auto event = make_shared<KeyUpEvent>(Keys::K_CAPSLOCK);
1208  keyboard.msxEventDistributor.distributeEvent(event, time);
1209  state = IDLE;
1210  break;
1211  }
1212  default:
1213  UNREACHABLE;
1214  }
1215 }
1216 
1217 /*
1218  * Align MSX caps lock state with host caps lock state
1219  * WARNING: This function assumes that the MSX will see and
1220  * process the caps lock key press.
1221  * If MSX misses the key press for whatever reason (e.g.
1222  * interrupts are disabled), the caps lock state in this
1223  * module will mismatch with the real MSX caps lock state
1224  * TODO: Find a solution for the above problem. For example by monitoring
1225  * the MSX caps-lock LED state.
1226  */
1227 void Keyboard::CapsLockAligner::alignCapsLock(EmuTime::param time)
1228 {
1229  bool hostCapsLockOn = ((SDL_GetModState() & KMOD_CAPS) != 0);
1230  auto& keyboard = OUTER(Keyboard, capsLockAligner);
1231  if (bool(keyboard.locksOn & KeyInfo::CAPS_MASK) != hostCapsLockOn) {
1232  keyboard.debug("Resyncing host and MSX CAPS lock\n");
1233  // note: send out another event iso directly calling
1234  // processCapslockEvent() because we want this to be recorded
1235  auto event = make_shared<KeyDownEvent>(Keys::K_CAPSLOCK);
1236  keyboard.msxEventDistributor.distributeEvent(event, time);
1237  keyboard.debug("Sending fake CAPS release\n");
1238  state = MUST_DISTRIBUTE_KEY_RELEASE;
1239  setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (MSX time)
1240  } else {
1241  state = IDLE;
1242  }
1243 }
1244 
1245 
1246 // class KeybDebuggable
1247 
1248 Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard_)
1249  : SimpleDebuggable(motherBoard_, "keymatrix", "MSX Keyboard Matrix",
1251 {
1252 }
1253 
1254 byte Keyboard::KeybDebuggable::read(unsigned address)
1255 {
1256  auto& keyboard = OUTER(Keyboard, keybDebuggable);
1257  return keyboard.getKeys()[address];
1258 }
1259 
1260 void Keyboard::KeybDebuggable::write(unsigned /*address*/, byte /*value*/)
1261 {
1262  // ignore
1263 }
1264 
1265 
1266 template<typename Archive>
1267 void Keyboard::KeyInserter::serialize(Archive& ar, unsigned /*version*/)
1268 {
1269  ar.template serializeBase<Schedulable>(*this);
1270  ar.serialize("text", text_utf8,
1271  "last", last,
1272  "lockKeysMask", lockKeysMask,
1273  "releaseLast", releaseLast);
1274 
1275  bool oldCodeKanaLockOn, oldGraphLockOn, oldCapsLockOn;
1276  if (!ar.isLoader()) {
1277  oldCodeKanaLockOn = oldLocksOn & KeyInfo::CODE_MASK;
1278  oldGraphLockOn = oldLocksOn & KeyInfo::GRAPH_MASK;
1279  oldCapsLockOn = oldLocksOn & KeyInfo::CAPS_MASK;
1280  }
1281  ar.serialize("oldCodeKanaLockOn", oldCodeKanaLockOn,
1282  "oldGraphLockOn", oldGraphLockOn,
1283  "oldCapsLockOn", oldCapsLockOn);
1284  if (ar.isLoader()) {
1285  oldLocksOn = (oldCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
1286  | (oldGraphLockOn ? KeyInfo::GRAPH_MASK : 0)
1287  | (oldCapsLockOn ? KeyInfo::CAPS_MASK : 0);
1288  }
1289 }
1290 
1291 // version 1: Initial version: {userKeyMatrix, dynKeymap, msxmodifiers,
1292 // msxKeyEventQueue} was intentionally not serialized. The reason
1293 // was that after a loadstate, you want the MSX keyboard to reflect
1294 // the state of the host keyboard. So any pressed MSX keys from the
1295 // time the savestate was created are cleared.
1296 // version 2: For reverse-replay it is important that snapshots contain the
1297 // full state of the MSX keyboard, so now we do serialize it.
1298 // version 3: split cmdKeyMatrix into cmdKeyMatrix + typeKeyMatrix
1299 // TODO Is the assumption in version 1 correct (clear keyb state on load)?
1300 // If it is still useful for 'regular' loadstate, then we could implement
1301 // it by explicitly clearing the keyb state from the actual loadstate
1302 // command. (But let's only do this when experience shows it's really
1303 // better).
1304 template<typename Archive>
1305 void Keyboard::serialize(Archive& ar, unsigned version)
1306 {
1307  ar.serialize("keyTypeCmd", keyTypeCmd,
1308  "cmdKeyMatrix", cmdKeyMatrix);
1309  if (ar.versionAtLeast(version, 3)) {
1310  ar.serialize("typeKeyMatrix", typeKeyMatrix);
1311  } else {
1312  ranges::copy(cmdKeyMatrix, typeKeyMatrix);
1313  }
1314 
1315  bool msxCapsLockOn, msxCodeKanaLockOn, msxGraphLockOn;
1316  if (!ar.isLoader()) {
1317  msxCapsLockOn = locksOn & KeyInfo::CAPS_MASK;
1318  msxCodeKanaLockOn = locksOn & KeyInfo::CODE_MASK;
1319  msxGraphLockOn = locksOn & KeyInfo::GRAPH_MASK;
1320  }
1321  ar.serialize("msxCapsLockOn", msxCapsLockOn,
1322  "msxCodeKanaLockOn", msxCodeKanaLockOn,
1323  "msxGraphLockOn", msxGraphLockOn);
1324  if (ar.isLoader()) {
1325  locksOn = (msxCapsLockOn ? KeyInfo::CAPS_MASK : 0)
1326  | (msxCodeKanaLockOn ? KeyInfo::CODE_MASK : 0)
1327  | (msxGraphLockOn ? KeyInfo::GRAPH_MASK : 0);
1328  }
1329 
1330  if (ar.versionAtLeast(version, 2)) {
1331  ar.serialize("userKeyMatrix", userKeyMatrix,
1332  "dynKeymap", dynKeymap,
1333  "msxmodifiers", msxmodifiers,
1334  "msxKeyEventQueue", msxKeyEventQueue);
1335  }
1336  // don't serialize hostKeyMatrix
1337 
1338  if (ar.isLoader()) {
1339  // force recalculation of keyMatrix
1340  keysChanged = true;
1341  }
1342 }
1344 
1345 template<typename Archive>
1346 void Keyboard::MsxKeyEventQueue::serialize(Archive& ar, unsigned /*version*/)
1347 {
1348  ar.template serializeBase<Schedulable>(*this);
1349 
1350  // serialization of deque<shared_ptr<const Event>> is not directly
1351  // supported by the serialization framework (main problem is the
1352  // constness, collections of shared_ptr to polymorhpic objects are
1353  // not a problem). Worked around this by serializing the events in
1354  // ascii format. (In all practical cases this queue will anyway be
1355  // empty or contain very few elements).
1356  //ar.serialize("eventQueue", eventQueue);
1357  vector<string> eventStrs;
1358  if (!ar.isLoader()) {
1359  eventStrs = to_vector(view::transform(
1360  eventQueue, [](auto& e) { return e->toString(); }));
1361  }
1362  ar.serialize("eventQueue", eventStrs);
1363  if (ar.isLoader()) {
1364  assert(eventQueue.empty());
1365  for (auto& s : eventStrs) {
1366  eventQueue.push_back(
1368  }
1369  }
1370 }
1371 INSTANTIATE_SERIALIZE_METHODS(Keyboard::MsxKeyEventQueue);
1372 
1373 
1376 // Mapping from SDL keys to emulated keys, ordered by MatrixType
1377 static const KeyMatrixPosition x = KeyMatrixPosition();
1378 const KeyMatrixPosition Keyboard::keyTabs[][MAX_KEYSYM] = {
1379  {
1380 // MSX Key-Matrix table
1381 //
1382 // row/bit 7 6 5 4 3 2 1 0
1383 // +-----+-----+-----+-----+-----+-----+-----+-----+
1384 // 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1385 // 1 | ; | ] | [ | \ | = | - | 9 | 8 |
1386 // 2 | B | A | Acc | / | . | , | ` | ' |
1387 // 3 | J | I | H | G | F | E | D | C |
1388 // 4 | R | Q | P | O | N | M | L | K |
1389 // 5 | Z | Y | X | W | V | U | T | S |
1390 // 6 | F3 | F2 | F1 | code| caps|graph| ctrl|shift|
1391 // 7 | ret |selec| bs | stop| tab | esc | F5 | F4 |
1392 // 8 |right| down| up | left| del | ins | hom |space|
1393 // 9 | 4 | 3 | 2 | 1 | 0 | / | + | * |
1394 // 10 | . | , | - | 9 | 8 | 7 | 6 | 5 |
1395 // 11 | | | | | 'NO'| |'YES'| |
1396 // +-----+-----+-----+-----+-----+-----+-----+-----+
1397 // 0 1 2 3 4 5 6 7 8 9 a b c d e f
1398  x , x , x , x , x , x , x , x ,0x75,0x73, x , x , x ,0x77, x , x , //000
1399  x , x , x , x , x , x , x , x , x , x , x ,0x72, x , x , x , x , //010
1400  0x80, x , x , x , x , x , x ,0x20, x , x , x , x ,0x22,0x12,0x23,0x24, //020
1401  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x10,0x11, x ,0x17, x ,0x13, x , x , //030
1402  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //040
1403  x ,0x84,0x85,0x87,0x86, x , x , x , x , x , x ,0x15,0x14,0x16, x , x , //050
1404  0x21,0x26,0x27,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x40,0x41,0x42,0x43,0x44, //060
1405  0x45,0x46,0x47,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, x , x , x , x ,0x83, //070
1406  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //080
1407  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //090
1408  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0A0
1409  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0B0
1410  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0C0
1411  x , x , x , x , x , x , x , x ,0x81, x , x , x , x , x , x , x , //0D0
1412  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0E0
1413  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0F0
1414  0x93,0x94,0x95,0x96,0x97,0xA0,0xA1,0xA2,0xA3,0xA4,0xA7,0x92,0x90,0xA5,0x91,0xA6, //100
1415  x ,0x85,0x86,0x87,0x84,0x82,0x81, x , x , x ,0x65,0x66,0x67,0x70,0x71, x , //110
1416  0x76,0x74, x , x , x , x , x , x , x , x , x , x , x , x , x ,0x60, //120
1417  0x60,0x25,0x61, x , x ,0xB3,0xB1,0xB3,0xB1,0xB1,0xB3, x , x , x , x , x , //130
1418  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //140
1419  },
1420  {
1421 // SVI Keyboard Matrix
1422 //
1423 // row/bit 7 6 5 4 3 2 1 0
1424 // +-----+-----+-----+-----+-----+-----+-----+-----+
1425 // 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1426 // 1 | / | . | = | , | ' | : | 9 | 8 |
1427 // 2 | G | F | E | D | C | B | A | - |
1428 // 3 | O | N | M | L | K | J | I | H |
1429 // 4 | W | V | U | T | S | R | Q | P |
1430 // 5 | UP | BS | ] | \ | [ | Z | Y | X |
1431 // 6 |LEFT |ENTER|STOP | ESC |RGRAP|LGRAP|CTRL |SHIFT|
1432 // 7 |DOWN | INS | CLS | F5 | F4 | F3 | F2 | F1 |
1433 // 8 |RIGHT| |PRINT| SEL |CAPS | DEL | TAB |SPACE|
1434 // 9 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Numerical keypad
1435 // 10 | , | . | / | * | - | + | 9 | 8 | SVI-328 only
1436 // +-----+-----+-----+-----+-----+-----+-----+-----+
1437 // 0 1 2 3 4 5 6 7 8 9 a b c d e f
1438  x , x , x , x , x , x , x , x ,0x56,0x81, x , x , x ,0x66, x , x , //000
1439  x , x , x , x , x , x , x , x , x , x , x ,0x64, x , x , x , x , //010
1440  0x80, x , x , x , x , x , x ,0x20, x , x , x , x ,0x14,0x20,0x16,0x17, //020
1441  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x10,0x11,0x12, x , x ,0x15, x , x , //030
1442  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //040
1443  x ,0x67,0x57,0x87,0x77, x , x , x , x , x , x ,0x53,0x54,0x55, x , x , //050
1444  x ,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, //060
1445  0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x50,0x51,0x52, x , x , x , x ,0x82, //070
1446  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //080
1447  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //090
1448  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0A0
1449  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0B0
1450  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0C0
1451  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0D0
1452  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0E0
1453  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0F0
1454  0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0xA0,0xA1,0xA6,0xA5,0xA4,0xA3,0xA2,0xA7, //100
1455  x ,0x57,0x77,0x87,0x67,0x76, x , x , x , x ,0x70,0x71,0x72,0x73,0x74, x , //110
1456  0x75,0x65, x , x , x , x , x , x , x , x , x , x , x , x , x ,0x60, //120
1457  0x60, x ,0x61, x , x , x , x , x , x , x , x , x , x , x , x , x , //130
1458  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //140
1459  },
1460  {
1461 // ColecoVision Joystick "Matrix"
1462 //
1463 // The hardware consists of 2 controllers that each have 2 triggers
1464 // and a 12-key keypad. They're not actually connected in a matrix,
1465 // but a ghosting-free matrix is the easiest way to model it in openMSX.
1466 //
1467 // row/bit 7 6 5 4 3 2 1 0
1468 // +-----+-----+-----+-----+-----+-----+-----+-----+
1469 // 0 |TRIGB|TRIGA| | |LEFT |DOWN |RIGHT| UP | controller 1
1470 // 1 |TRIGB|TRIGA| | |LEFT |DOWN |RIGHT| UP | controller 2
1471 // 2 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | controller 1
1472 // 3 | | | | | # | * | 9 | 8 | controller 1
1473 // 4 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | controller 2
1474 // 5 | | | | | # | * | 9 | 8 | controller 2
1475 // +-----+-----+-----+-----+-----+-----+-----+-----+
1476 // 0 1 2 3 4 5 6 7 8 9 a b c d e f
1477  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //000
1478  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //010
1479  0x06, x , x , x , x , x , x , x , x , x , x , x , x ,0x32, x , x , //020
1480  0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x30,0x31, x , x , x ,0x33, x , x , //030
1481  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //040
1482  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //050
1483  x ,0x13,0x42, x ,0x11, x ,0x44,0x45,0x46, x ,0x52, x , x ,0x53,0x43, x , //060
1484  x , x ,0x47,0x12,0x50,0x40,0x41,0x10, x ,0x51, x , x , x , x , x , x , //070
1485  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //080
1486  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //090
1487  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0A0
1488  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0B0
1489  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0C0
1490  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0D0
1491  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0E0
1492  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0F0
1493  0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x30,0x31, x ,0x33,0x32,0x32,0x33, x , //100
1494  x ,0x00,0x02,0x01,0x03, x , x , x , x , x , x , x , x , x , x , x , //110
1495  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x ,0x07, //120
1496  0x17,0x06,0x16,0x07,0x07, x , x , x , x , x , x , x , x , x , x , x , //130
1497  x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //140
1498  }
1499 };
1500 
1501 } // namespace openmsx
const char * data() const
Definition: string_view.hh:57
static EmuDuration sec(unsigned x)
Definition: EmuDuration.hh:37
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:312
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:149
static EmuDuration hz(unsigned x)
Definition: EmuDuration.hh:43
bool isValid() const
Returns true iff this position is valid.
const byte * getKeys() const
Returns a pointer to the current KeyBoard matrix.
Definition: Keyboard.cc:213
A position (row, column) in a keyboard matrix.
unsigned getMask() const
Returns a mask with the bit corresponding to this position&#39;s column set, all other bits clear...
uint32_t next(octet_iterator &it, octet_iterator end)
Definition: span.hh:34
void unregisterListener(StateChangeListener &listener)
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
bool is_pua(uint32_t cp)
Definition: utf8_core.hh:255
bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:106
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
Commands that directly influence the MSX state should send and events so that they can be recorded by...
ArgsInfo flagArg(string_view name, bool &flag)
Definition: TclArgParser.hh:72
UnicodeKeymap::KeyInfo KeyInfo
Definition: Keyboard.cc:40
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
Keys::KeyCode getDeadkeyHostKey(unsigned n) const
ArgsInfo valueArg(string_view name, T &value)
Definition: TclArgParser.hh:85
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
KeyCode
Constants that identify keys and key modifiers.
Definition: Keys.hh:26
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:191
auto begin(const string_view &x)
Definition: string_view.hh:151
void registerKeyboard(Keyboard &keyboard_)
void serialize(Archive &ar, unsigned version)
Definition: Keyboard.cc:1305
uint32_t getUnicode() const
Definition: InputEvents.hh:30
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
Keyboard(MSXMotherBoard &motherBoard, Scheduler &scheduler, CommandController &commandController, EventDistributor &eventDistributor, MSXEventDistributor &msxEventDistributor, StateChangeDistributor &stateChangeDistributor, MatrixType matrix, const DeviceConfig &config)
Constructs a new Keyboard object.
Definition: Keyboard.cc:100
byte getRelease() const
Definition: Keyboard.cc:59
static constexpr byte CODE_MASK
static constexpr byte GRAPH_MASK
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
ReverseManager & getReverseManager()
byte getRow() const
Definition: Keyboard.cc:57
byte getRelevantMods(const KeyInfo &keyInfo) const
Returns a mask in which a bit is set iff the corresponding modifier is relevant for the given key...
EventType
Definition: Event.hh:10
bool getAutoToggleCodeKanaLock() const
Keys::KeyCode getKeyCode() const
Definition: InputEvents.hh:29
static constexpr unsigned NUM_ROWS
Rows are in the range [0..NUM_ROWS).
KeyInfo getDeadkey(unsigned n) const
static constexpr byte CAPS_MASK
bool empty() const
Definition: string_view.hh:45
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
EventPtr createInputEvent(const TclObject &str, Interpreter &interp)
KpEnterMode getKpEnterMode() const
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1006
Base class for all external MSX state changing events.
Definition: StateChange.hh:13
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:589
void transferHostKeyMatrix(const Keyboard &source)
Definition: Keyboard.cc:228
KeyInfo get(unsigned unicode) const
MappingMode getMappingMode() const
byte getPress() const
Definition: Keyboard.cc:58
unsigned getRow() const
Returns the matrix row.
EventType getType() const
Definition: Event.hh:87
size_type size() const
Definition: string_view.hh:44
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
#define OUTER(type, member)
Definition: outer.hh:38
Keys::KeyCode getCodeKanaHostKey() const
REGISTER_POLYMORPHIC_CLASS(DiskContainer, NowindRomDisk, "NowindRomDisk")
KeyMatrixState(EmuTime::param time_, byte row_, byte press_, byte release_)
Definition: Keyboard.cc:46
static constexpr byte SHIFT_MASK
void serialize(Archive &ar, unsigned)
Definition: Keyboard.cc:61
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))>>
Definition: stl.hh:325
void serialize(Archive &ar, T &t, unsigned version)
unsigned getColumn() const
Returns the matrix column.
auto end(const string_view &x)
Definition: string_view.hh:152
#define ad_printf(...)
Definition: openmsx.hh:11
std::vector< TclObject > parseTclArgs(Interpreter &interp, span< const TclObject > inArgs, span< const ArgsInfo > table)
#define UNREACHABLE
Definition: unreachable.hh:38