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