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"
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
34namespace 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
45static 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
50static constexpr bool SANE_CAPSLOCK_BEHAVIOR = true;
51#endif
52
53
54static constexpr int TRY_AGAIN = 0x80; // see pressAscii()
55
57
58class KeyMatrixState final : public StateChange
59{
60public:
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 }
84private:
85 byte row, press, release;
86};
88
89
90constexpr const char* const defaultKeymapForMatrix[] = {
91 "int", // MATRIX_MSX
92 "svi", // MATRIX_SVI
93 "cvjoy", // MATRIX_CVJOY
94 "sega_int", // MATRIX_SEGA
95};
96
97constexpr 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
128static 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
358template<unsigned NUM_ROWS>
359static 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
418const 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 */
460void 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
472void 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
482void 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
492byte Keyboard::needsLockToggle(const UnicodeKeymap::KeyInfo& keyInfo) const
493{
494 return modifierIsLock
495 & (locksOn ^ keyInfo.modmask)
496 & unicodeKeymap.getRelevantMods(keyInfo);
497}
498
499void 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
515void 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
534void 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 */
551bool 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 */
620void 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 */
633void 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 */
646void 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
662void Keyboard::executeUntil(EmuTime::param time)
663{
664 debug("Releasing CAPS lock\n");
665 updateKeyMatrix(time, false, modifierPos[KeyInfo::CAPS]);
666}
667
668void 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,
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 */
686void 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 */
703void 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 */
739bool 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) {
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
857void Keyboard::processCmd(Interpreter& interp, std::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 */
913bool 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 */
990int 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 */
1078void 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 */
1101bool 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
1110void 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
1123Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd(
1124 CommandController& commandController_,
1125 StateChangeDistributor& stateChangeDistributor_,
1126 Scheduler& scheduler_)
1127 : RecordedCommand(commandController_, stateChangeDistributor_,
1128 scheduler_, "keymatrixup")
1129{
1130}
1131
1132void Keyboard::KeyMatrixUpCmd::execute(
1133 std::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
1140std::string Keyboard::KeyMatrixUpCmd::help(std::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
1148Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController_,
1149 StateChangeDistributor& stateChangeDistributor_,
1150 Scheduler& scheduler_)
1151 : RecordedCommand(commandController_, stateChangeDistributor_,
1152 scheduler_, "keymatrixdown")
1153{
1154}
1155
1156void Keyboard::KeyMatrixDownCmd::execute(std::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
1164std::string Keyboard::KeyMatrixDownCmd::help(std::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
1172Keyboard::MsxKeyEventQueue::MsxKeyEventQueue(
1173 Scheduler& scheduler_, Interpreter& interp_)
1174 : Schedulable(scheduler_)
1175 , interp(interp_)
1176{
1177}
1178
1179void 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
1189void Keyboard::MsxKeyEventQueue::clear()
1190{
1191 eventQueue.clear();
1192 removeSyncPoint();
1193}
1194
1195void 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
1227Keyboard::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
1243void Keyboard::KeyInserter::execute(
1244 std::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
1271std::string Keyboard::KeyInserter::help(std::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
1278void 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
1285void 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
1298void 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
1352void Keyboard::KeyInserter::reschedule(EmuTime::param time)
1353{
1354 setSyncPoint(time + EmuDuration::hz(typingFrequency));
1355}
1356
1357
1358// Commands for conversion between msxcode <-> unicode.
1359
1360Keyboard::Msxcode2UnicodeCmd::Msxcode2UnicodeCmd(CommandController& commandController_)
1361 : Command(commandController_, "msxcode2unicode")
1362{
1363}
1364
1365void Keyboard::Msxcode2UnicodeCmd::execute(std::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
1395std::string Keyboard::Msxcode2UnicodeCmd::help(std::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
1410Keyboard::Unicode2MsxcodeCmd::Unicode2MsxcodeCmd(CommandController& commandController_)
1411 : Command(commandController_, "unicode2msxcode")
1412{
1413}
1414
1415void Keyboard::Unicode2MsxcodeCmd::execute(std::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
1445std::string Keyboard::Unicode2MsxcodeCmd::help(std::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 */
1472Keyboard::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
1483Keyboard::CapsLockAligner::~CapsLockAligner()
1484{
1485 eventDistributor.unregisterEventListener(EventType::FOCUS, *this);
1486 eventDistributor.unregisterEventListener(EventType::BOOT, *this);
1487}
1488
1489int 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();
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
1512void 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:
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 */
1540void 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
1561Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard_)
1562 : SimpleDebuggable(motherBoard_, "keymatrix", "MSX Keyboard Matrix",
1563 KeyMatrixPosition::NUM_ROWS)
1564{
1565}
1566
1567byte Keyboard::KeybDebuggable::read(unsigned address)
1568{
1569 auto& keyboard = OUTER(Keyboard, keybDebuggable);
1570 return keyboard.getKeys()[address];
1571}
1572
1573void Keyboard::KeybDebuggable::write(unsigned /*address*/, byte /*value*/)
1574{
1575 // ignore
1576}
1577
1578
1579template<typename Archive>
1580void 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).
1617template<typename Archive>
1618void 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
1658template<typename Archive>
1659void 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}
1684INSTANTIATE_SERIALIZE_METHODS(Keyboard::MsxKeyEventQueue);
1685
1686} // namespace openmsx
Definition: one_of.hh:7
static constexpr EmuDuration sec(unsigned x)
Definition: EmuDuration.hh:40
static constexpr EmuDuration hz(unsigned x)
Definition: EmuDuration.hh:46
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
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
constexpr double e
Definition: Math.hh:18
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:727
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr std::array< KeyMatrixPosition, UnicodeKeymap::KeyInfo::NUM_MODIFIERS > modifierPosForMatrix[]
Definition: Keyboard.cc:98
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:652
ArgsInfo valueArg(std::string_view name, T &value)
Definition: TclArgParser.hh:85
REGISTER_POLYMORPHIC_CLASS(StateChange, AutofireStateChange, "AutofireStateChange")
std::vector< TclObject > parseTclArgs(Interpreter &interp, std::span< const TclObject > inArgs, std::span< const ArgsInfo > table)
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:644
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
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:256
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:208
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:378
#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:266
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:133
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)