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