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