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