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