Skip to content

Commit 89ab61f

Browse files
committed
autotester + Qt IPC: "keys" action to send key sequence. Update docs.
1 parent 9e45c95 commit 89ab61f

7 files changed

Lines changed: 322 additions & 51 deletions

File tree

gui/qt/cemuopts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct CEmuOpts {
2121
QString launchPrgm;
2222
QString debugFile;
2323
QString screenshotFile;
24+
QString keySequence;
2425
QString idString;
2526
QString pidString;
2627
QStringList sendFiles;

gui/qt/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ int main(int argc, char *argv[]) {
128128
QCoreApplication::translate("main", "prgm"));
129129
parser.addOption(launchPrgm);
130130

131+
QCommandLineOption keySequence(QStringList() << QStringLiteral("keys"),
132+
QCoreApplication::translate("main", "Press physical calculator keys from a comma-separated sequence. Use key names or delay:<ms>."),
133+
QCoreApplication::translate("main", "sequence"));
134+
parser.addOption(keySequence);
135+
131136
QCommandLineOption noSettings(QStringList() << QStringLiteral("no-settings"),
132137
QCoreApplication::translate("main", "Do not restore or save settings when running"));
133138
parser.addOption(noSettings);
@@ -165,6 +170,7 @@ int main(int argc, char *argv[]) {
165170
opts.romFile = parser.value(loadRomFile);
166171
opts.settingsFile = parser.value(settingsFile);
167172
opts.launchPrgm = parser.value(launchPrgm);
173+
opts.keySequence = parser.value(keySequence);
168174
opts.imageFile = parser.value(imageFile);
169175
opts.debugFile = parser.value(debugFile);
170176
opts.screenshotFile = parser.value(screenshotFile);

gui/qt/mainwindow.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "../../core/cpu.h"
1717
#include "../../core/mem.h"
1818
#include "../../core/extras.h"
19+
#include "../../core/keypad.h"
1920
#include "../../core/link.h"
2021
#include "../../tests/autotester/crc32.hpp"
2122
#include "../../tests/autotester/autotester.h"
@@ -1413,6 +1414,21 @@ void MainWindow::sendEmuLetterKey(char letter) {
14131414
}
14141415
}
14151416

1417+
void MainWindow::sendEmuKeySequence(const QString &sequence) {
1418+
autotester::key_sequence_handlers_t handlers;
1419+
handlers.keyEvent = [](uint8_t row, uint8_t col, bool pressed) {
1420+
emu_keypad_event(row, col, pressed);
1421+
};
1422+
handlers.delay = [](unsigned int ms) {
1423+
guiDelay(static_cast<int>(ms));
1424+
};
1425+
handlers.error = [](const std::string& error) {
1426+
std::cerr << "[CEmu] " << error << std::endl;
1427+
};
1428+
1429+
autotester::runKeySequence(sequence.toStdString(), handlers);
1430+
}
1431+
14161432
void MainWindow::optSend(CEmuOpts &o) {
14171433
int speed = m_config->value(SETTING_EMUSPEED).toInt();
14181434
if (!o.autotesterFile.isEmpty()) {
@@ -1459,6 +1475,10 @@ void MainWindow::optSend(CEmuOpts &o) {
14591475
}
14601476
sendEmuKey(CE_KEY_ENTER);
14611477
}
1478+
1479+
if (!o.keySequence.isEmpty()) {
1480+
sendEmuKeySequence(o.keySequence);
1481+
}
14621482
}
14631483

14641484
void MainWindow::optLoadFiles(CEmuOpts &o) {
@@ -3182,7 +3202,8 @@ bool MainWindow::ipcSetup() {
31823202
<< opts.restoreOnOpen
31833203
<< opts.speed
31843204
<< opts.launchPrgm
3185-
<< opts.screenshotFile;
3205+
<< opts.screenshotFile
3206+
<< opts.keySequence;
31863207

31873208
// blocking call
31883209
com.send(byteArray);
@@ -3206,7 +3227,8 @@ void MainWindow::ipcCli(QDataStream &stream) {
32063227
>> o.restoreOnOpen
32073228
>> o.speed
32083229
>> o.launchPrgm
3209-
>> o.screenshotFile;
3230+
>> o.screenshotFile
3231+
>> o.keySequence;
32103232

32113233
opts.suppressTestDialog = o.suppressTestDialog;
32123234
optLoadFiles(o);

gui/qt/mainwindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ private slots:
242242
// emu keypresses
243243
void sendEmuKey(uint16_t key);
244244
void sendEmuLetterKey(char letter);
245+
void sendEmuKeySequence(const QString &sequence);
245246

246247
// console
247248
void console(const QString &str, int type = EmuThread::ConsoleNorm) const;

tests/autotester/autotester.cpp

Lines changed: 162 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include <functional>
1616
#include <unordered_map>
1717
#include <regex>
18+
#include <cctype>
19+
#include <limits>
1820

1921
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
2022
#include <windows.h>
@@ -96,18 +98,144 @@ unsigned int hashesFailed = 0;
9698
/* Will be incremented at each `hash` command */
9799
unsigned int hashesTested = 0;
98100

99-
struct coord2d { uint8_t x; uint8_t y; };
100101
// Note: we could just store the string in a char*[8][8], then search for it and calculate its row/col at runtime, but meh.
101-
static const std::unordered_map<std::string, coord2d> valid_keys = {
102-
{"graph", {0,1}}, {"trace", {1,1}}, { "zoom", {2,1}}, {"window", {3,1}}, {"y=", {4,1}}, {"2nd", {5,1}}, { "mode", {6,1}}, { "del", {7,1}},
103-
{ "on", {0,2}}, { "sto", {1,2}}, { "ln", {2,2}}, { "log", {3,2}}, {"^2", {4,2}}, { "-1", {5,2}}, { "math", {6,2}}, {"alpha", {7,2}},
104-
{ "0", {0,3}}, { "1", {1,3}}, { "4", {2,3}}, { "7", {3,3}}, { ",", {4,3}}, {"sin", {5,3}}, { "apps", {6,3}}, { "xton", {7,3}},
105-
{ ".", {0,4}}, { "2", {1,4}}, { "5", {2,4}}, { "8", {3,4}}, { "(", {4,4}}, {"cos", {5,4}}, { "prgm", {6,4}}, { "stat", {7,4}},
106-
{ "(-)", {0,5}}, { "3", {1,5}}, { "6", {2,5}}, { "9", {3,5}}, { ")", {4,5}}, {"tan", {5,5}}, { "vars", {6,5}},
107-
{"enter", {0,6}}, { "+", {1,6}}, { "-", {2,6}}, { "*", {3,6}}, { "/", {4,6}}, { "^", {5,6}}, {"clear", {6,6}},
108-
{ "down", {0,7}}, { "left", {1,7}}, {"right", {2,7}}, { "up", {3,7}}
102+
static const std::unordered_map<std::string, key_coord_t> valid_keys = {
103+
{"graph", {1,0}}, {"trace", {1,1}}, { "zoom", {1,2}}, {"window", {1,3}}, { "wind", {1,3}}, { "y=", {1,4}}, { "yequ", {1,4}}, { "2nd", {1,5}}, { "mode", {1,6}}, { "del", {1,7}},
104+
{ "on", {2,0}}, { "sto", {2,1}}, { "ln", {2,2}}, { "log", {2,3}}, { "^2", {2,4}}, { "sq", {2,4}}, { "-1", {2,5}}, { "inv", {2,5}}, { "math", {2,6}}, {"alpha", {2,7}},
105+
{ "0", {3,0}}, { "1", {3,1}}, { "4", {3,2}}, { "7", {3,3}}, { ",", {3,4}}, {"comma", {3,4}}, { "sin", {3,5}}, { "apps", {3,6}}, {"xton", {3,7}}, { "xtn", {3,7}},
106+
{ ".", {4,0}}, { "dot", {4,0}}, { "2", {4,1}}, { "5", {4,2}}, { "8", {4,3}}, { "(", {4,4}}, { "lpar", {4,4}}, { "cos", {4,5}}, { "prgm", {4,6}}, { "stat", {4,7}},
107+
{ "(-)", {5,0}}, { "neg", {5,0}}, { "3", {5,1}}, { "6", {5,2}}, { "9", {5,3}}, { ")", {5,4}}, { "rpar", {5,4}}, { "tan", {5,5}}, { "vars", {5,6}},
108+
{"enter", {6,0}}, { "+", {6,1}}, { "add", {6,1}}, { "-", {6,2}}, { "sub", {6,2}}, { "*", {6,3}}, { "mul", {6,3}}, { "/", {6,4}}, { "div", {6,4}}, { "^", {6,5}}, { "pow", {6,5}}, {"clear", {6,6}}, { "clr", {6,6}},
109+
{ "down", {7,0}}, { "left", {7,1}}, {"right", {7,2}}, { "up", {7,3}}
109110
};
110111

112+
static bool parseMilliseconds(const std::string& str, bool allowZero, unsigned int& ms)
113+
{
114+
if (str.empty()) {
115+
return false;
116+
}
117+
118+
unsigned long value = 0;
119+
for (const char c : str) {
120+
if (!std::isdigit(static_cast<unsigned char>(c))) {
121+
return false;
122+
}
123+
value = value * 10 + static_cast<unsigned long>(c - '0');
124+
if (value > std::numeric_limits<unsigned int>::max()) {
125+
return false;
126+
}
127+
}
128+
129+
if (!allowZero && value == 0) {
130+
return false;
131+
}
132+
133+
ms = static_cast<unsigned int>(value);
134+
return true;
135+
}
136+
137+
static void reportSequenceError(const key_sequence_handlers_t& handlers, const std::string& error)
138+
{
139+
if (handlers.error) {
140+
handlers.error(error);
141+
}
142+
}
143+
144+
bool keyCoordForName(const std::string& name, key_coord_t& coord)
145+
{
146+
const auto tmp = valid_keys.find(name);
147+
if (tmp == valid_keys.end()) {
148+
return false;
149+
}
150+
151+
coord = tmp->second;
152+
return true;
153+
}
154+
155+
bool runKeySequence(const std::string& sequence, const key_sequence_handlers_t& handlers)
156+
{
157+
if (!handlers.keyEvent || !handlers.delay) {
158+
reportSequenceError(handlers, "Bad 'keys' sequence handler");
159+
return false;
160+
}
161+
162+
bool ok = true;
163+
std::stringstream ss(sequence);
164+
std::string rawStep;
165+
while (std::getline(ss, rawStep, ',')) {
166+
const std::string& step = rawStep;
167+
if (step.find("delay:") == 0) {
168+
const size_t separator = step.find(':');
169+
unsigned int delayMs = 0;
170+
if (!parseMilliseconds(step.substr(separator + 1), true, delayMs)) {
171+
reportSequenceError(handlers, "Bad delay in 'keys' sequence: " + step);
172+
ok = false;
173+
continue;
174+
}
175+
handlers.delay(delayMs);
176+
continue;
177+
}
178+
179+
if (step.find("hold:") == 0) {
180+
const size_t firstSeparator = step.find(':');
181+
const size_t secondSeparator = step.find(':', firstSeparator + 1);
182+
if (secondSeparator == std::string::npos || step.find(':', secondSeparator + 1) != std::string::npos) {
183+
reportSequenceError(handlers, "Bad hold step in 'keys' sequence: " + step);
184+
ok = false;
185+
continue;
186+
}
187+
188+
unsigned int holdMs = 0;
189+
if (!parseMilliseconds(step.substr(secondSeparator + 1), false, holdMs)) {
190+
reportSequenceError(handlers, "Bad hold duration in 'keys' sequence: " + step);
191+
ok = false;
192+
continue;
193+
}
194+
195+
key_coord_t coord{};
196+
if (!keyCoordForName(step.substr(firstSeparator + 1, secondSeparator - firstSeparator - 1), coord)) {
197+
reportSequenceError(handlers, "Unknown key in 'keys' sequence: " + step);
198+
ok = false;
199+
continue;
200+
}
201+
202+
handlers.keyEvent(coord.y, coord.x, true);
203+
handlers.delay(holdMs);
204+
handlers.keyEvent(coord.y, coord.x, false);
205+
handlers.delay(handlers.delay_after_step_ms);
206+
continue;
207+
}
208+
209+
if (step.find("down:") == 0 || step.find("up:") == 0) {
210+
const size_t separator = step.find(':');
211+
key_coord_t coord{};
212+
if (!keyCoordForName(step.substr(separator + 1), coord)) {
213+
reportSequenceError(handlers, "Unknown key in 'keys' sequence: " + step);
214+
ok = false;
215+
continue;
216+
}
217+
handlers.keyEvent(coord.y, coord.x, step.find("down:") == 0);
218+
handlers.delay(handlers.delay_after_step_ms);
219+
continue;
220+
}
221+
222+
const std::string keyName = step.find("press:") == 0 ? step.substr(6) : step;
223+
key_coord_t coord{};
224+
if (!keyCoordForName(keyName, coord)) {
225+
reportSequenceError(handlers, "Unknown key in 'keys' sequence: " + keyName);
226+
ok = false;
227+
continue;
228+
}
229+
230+
handlers.keyEvent(coord.y, coord.x, true);
231+
handlers.delay(handlers.default_hold_ms);
232+
handlers.keyEvent(coord.y, coord.x, false);
233+
handlers.delay(handlers.delay_after_step_ms);
234+
}
235+
236+
return ok;
237+
}
238+
111239
void sendCSC(uint8_t csc)
112240
{
113241
int retry = 200;
@@ -278,10 +406,9 @@ static const std::unordered_map<std::string, seq_cmd_func_t> valid_seq_commands
278406
},
279407
{
280408
"key", [](const std::string& which_key) {
281-
const auto& tmp = valid_keys.find(which_key);
282-
if (tmp != valid_keys.end())
409+
key_coord_t key_coords{};
410+
if (keyCoordForName(which_key, key_coords))
283411
{
284-
const coord2d& key_coords = tmp->second;
285412
cemucore::emu_keypad_event(key_coords.y, key_coords.x, true);
286413
cemucore::emu_run(80);
287414
cemucore::emu_keypad_event(key_coords.y, key_coords.x, false);
@@ -294,6 +421,23 @@ static const std::unordered_map<std::string, seq_cmd_func_t> valid_seq_commands
294421
};
295422
}
296423
},
424+
{
425+
"keys", [](const std::string& sequence) {
426+
key_sequence_handlers_t handlers;
427+
handlers.keyEvent = [](uint8_t row, uint8_t col, bool pressed) {
428+
cemucore::emu_keypad_event(row, col, pressed);
429+
};
430+
handlers.delay = [](unsigned int ms) {
431+
cemucore::emu_run(ms);
432+
};
433+
handlers.error = [](const std::string& error) {
434+
std::cerr << "\t[Error] " << error << std::endl;
435+
};
436+
handlers.delay_after_step_ms = config.delay_after_key;
437+
438+
runKeySequence(sequence, handlers);
439+
}
440+
},
297441
{
298442
"sendCSC", [](const std::string& str) {
299443
std::vector<std::string> parts;
@@ -309,10 +453,9 @@ static const std::unordered_map<std::string, seq_cmd_func_t> valid_seq_commands
309453
}
310454
for (const auto& part : parts)
311455
{
312-
const auto& tmp = valid_keys.find(part);
313-
if (tmp != valid_keys.end())
456+
key_coord_t key_coords{};
457+
if (keyCoordForName(part, key_coords))
314458
{
315-
const coord2d& key_coords = tmp->second;
316459
sendCSC((7-key_coords.y)*8 + key_coords.x + 1);
317460
if (config.delay_after_key > 0)
318461
{
@@ -326,10 +469,9 @@ static const std::unordered_map<std::string, seq_cmd_func_t> valid_seq_commands
326469
},
327470
{
328471
"hold", [](const std::string& which_key) {
329-
const auto& tmp = valid_keys.find(which_key);
330-
if (tmp != valid_keys.end())
472+
key_coord_t key_coords{};
473+
if (keyCoordForName(which_key, key_coords))
331474
{
332-
const coord2d& key_coords = tmp->second;
333475
cemucore::emu_keypad_event(key_coords.y, key_coords.x, true);
334476
} else {
335477
std::cerr << "\t[Error] unknown key \"" << which_key << "\" was not hold." << std::endl;
@@ -338,10 +480,9 @@ static const std::unordered_map<std::string, seq_cmd_func_t> valid_seq_commands
338480
},
339481
{
340482
"release", [](const std::string& which_key) {
341-
const auto& tmp = valid_keys.find(which_key);
342-
if (tmp != valid_keys.end())
483+
key_coord_t key_coords{};
484+
if (keyCoordForName(which_key, key_coords))
343485
{
344-
const coord2d& key_coords = tmp->second;
345486
cemucore::emu_keypad_event(key_coords.y, key_coords.x, false);
346487
} else {
347488
std::cerr << "\t[Error] unknown key \"" << which_key << "\" was not released." << std::endl;

tests/autotester/autotester.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <string>
1212
#include <vector>
1313
#include <unordered_map>
14+
#include <functional>
1415

1516
#include "../../core/defines.h"
1617

@@ -30,6 +31,19 @@ namespace cemucore
3031

3132
namespace autotester
3233
{
34+
struct key_coord_t {
35+
uint8_t y; // row
36+
uint8_t x; // col
37+
};
38+
39+
struct key_sequence_handlers_t {
40+
std::function<void(uint8_t row, uint8_t col, bool pressed)> keyEvent;
41+
std::function<void(unsigned int ms)> delay;
42+
std::function<void(const std::string& error)> error;
43+
unsigned int default_hold_ms = 80;
44+
unsigned int delay_after_step_ms = 50;
45+
};
46+
3347
struct hash_params_t {
3448
std::string description;
3549
uint32_t start; /* Actually a pointer, for the CE */
@@ -121,6 +135,9 @@ namespace autotester
121135
void sendKey(uint16_t key);
122136
void sendLetterKeyPress(char letter);
123137

138+
bool keyCoordForName(const std::string& name, key_coord_t& coord);
139+
bool runKeySequence(const std::string& sequence, const key_sequence_handlers_t& handlers);
140+
124141
bool launchCommand(const std::pair<std::string, std::string>& command);
125142

126143
bool loadJSONConfig(const std::string& jsonContents);

0 commit comments

Comments
 (0)