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