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