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