1
0
Florian Fleissner cf878783ad
Turned the virtual architectures virtual core to an arduino core
This makes the build much simpler as the FQBN
can now e.g. be given as keyboardio:virtual:model01
and the build process will automatically find the
virtual arduino core in virtual/cores/arduino

Signed-off-by: Florian Fleissner <florian.fleissner@inpartik.de>
2019-12-05 12:00:43 -08:00

168 lines
9.4 KiB
C++

#include "virtual_io.h"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string.h>
#include <stdlib.h> // exit()
#include <sys/types.h> // mkdir()
#include <sys/stat.h> // mkdir()
#include <errno.h>
static bool interactive = false;
static bool test_function = false;
static std::istream* input = NULL;
static std::ostream* usbstream = NULL;
static std::ostream* ledstream = NULL;
static unsigned cycle = 0;
bool isInteractive(void) {
return interactive;
}
bool testFunctionExecutionRequested() {
return test_function;
}
unsigned currentCycle(void) {
return cycle;
}
void nextCycle(void) {
cycle++;
}
void logUSBEvent(std::string descrip, void* data, int length) {
if (usbstream) {
*usbstream << "Cycle " << std::dec << currentCycle() << ": " << descrip << ": 0x" << std::hex;
unsigned char* report = (unsigned char*) data;
for (int i = 0; i < length; i++) *usbstream << std::setfill('0') << std::setw(2) << (unsigned int)(report[i]); // pad with 0's to total of 2 characters
*usbstream << std::endl;
}
}
void logUSBEvent_keyboard(std::string descrip) {
if (usbstream) {
*usbstream << "Cycle " << std::dec << currentCycle() << ": " << descrip << std::endl;
}
}
void logLEDStates(std::string descrip) {
if (ledstream) {
*ledstream << "Cycle " << std::dec << currentCycle() << ": " << descrip << std::endl;
}
}
bool initVirtualInput(int argc, char* argv[]) {
if (argc < 2 || strcmp(argv[1], "?") == 0) {
printHelp();
return false;
} else if (argc > 2) {
std::cerr << "Error: more arguments than expected (got " << (argc - 1) << ")" << std::endl;
return false;
}
if (strcmp(argv[1], "-i") == 0) {
interactive = true;
input = &std::cin;
} else if (strcmp(argv[1], "-t") == 0) {
test_function = true;
} else {
interactive = false;
input = new std::ifstream(argv[1]);
if (!input || !(*input)) {
std::cerr << "Error opening input file \"" << argv[1] << "\"" << std::endl;
return false;
}
}
if (mkdir("results", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) && errno != EEXIST) {
std::cerr << "Error creating directory 'results', errno " << errno << std::endl;
return false;
}
usbstream = new std::ofstream("results/USB.txt");
ledstream = new std::ofstream("results/LED.txt");
return true;
}
std::string getLineOfInput(bool anythingHeld) {
if (interactive) {
std::cout << "Enter a command for this scan cycle, or ? or 'help' for help." << std::endl;
if (anythingHeld) std::cout << "+> ";
else std::cout << "> ";
}
std::string line;
std::getline(*input, line);
if (!interactive && !(*input)) exit(0); // reached EOF or other file error
return line;
}
void printHelp(void) {
std::cout << "\nUsage:\n" << std::endl;
std::cout << "(Running with no arguments or with the argument '?' will print this help message and quit.)\n" << std::endl;
std::cout << "This program expects a single argument, which is either:" << std::endl;
std::cout << " 1. An input file/script, with format given below, or" << std::endl;
std::cout << " 2. \"-i\", to run interactively, where you can interactively enter commands and see results." << std::endl;
std::cout << " 3. \"-t\", to run a test function that is specified in the sketch file." << std::endl;
std::cout << "\nIn either case, for each scan cycle you will specify zero or more input 'commands', that is," << std::endl;
std::cout << " actions to take on the keys of the virtual keyboard. Each line of the input file, or each" << std::endl;
std::cout << " prompt (in interactive mode), represents one scan cycle; a blank line or empty prompt means" << std::endl;
std::cout << " to do nothing to the inputs this scan cycle (held keys will still remain held, though)." << std::endl;
std::cout << "\nOutput, in terms of HID reports (packets sent to the host computer, for real hardware), is" << std::endl;
std::cout << " printed to stdout as it happens, in summarized/human-readable form. Raw HID output and" << std::endl;
std::cout << " serial output (through the 'Serial' object) are collected and redirected to various files" << std::endl;
std::cout << " in a subdirectory \"results\" of the current directory." << std::endl;
std::cout << "\nSerial input is currently unsupported - sketches requesting it will still build, but will" << std::endl;
std::cout << " find nothing is ever transmitted to them on the serial port." << std::endl;
std::cout << "\n--- Commands ---" << std::endl;
std::cout << "\n1. BASICS\n" << std::endl;
std::cout << "In any given scan cycle, you can 'tap' a virtual key simply by entering its name." << std::endl;
std::cout << "To 'tap' multiple keys in one cycle, enter each of their names separated by a space." << std::endl;
std::cout << "Keys can be identified either by their (row,col) coordinate, or by their \"physical\" names." << std::endl;
std::cout << "Keys' coordinate names are simply of the form (row,col). E.g. (0,1) or (2,10) or (1,0)." << std::endl;
std::cout << " (Don't put any extra whitespace inside the coordinate name.)" << std::endl;
std::cout << "A key's \"physical\" name is the (unshifted) text printed on the key on the standard QWERTY" << std::endl;
std::cout << " Model 01. The key always has the same name regardless of what the keymap in the current" << std::endl;
std::cout << " Kaleidoscope sketch may or may not be doing. As an exception to the printed-name rule, we" << std::endl;
std::cout << " distinguish physical keys with the same text (ctrl, shift, and fn) with 'l' or 'r' indicating the hand." << std::endl;
std::cout << "Here is a list of all the valid key \"physical\" names:" << std::endl;
std::cout << " prog 1 2 3 4 5 led any 6 7 8 9 0 num ` q w e r t y u i o p = pgup a s d f g tab enter h j k l ; '" << std::endl;
std::cout << " pgdn z x c v b esc fly n m , . / - lctrl bksp cmd lshift lfn rshift alt space rctrl rfn" << std::endl;
std::cout << "The comment character '#' instructs the program to ignore the rest of the line (either in the" << std::endl;
std::cout << " script, or in interactive mode)." << std::endl;
std::cout << "\nExample script:" << std::endl;
std::cout << " t # first scan cycle: tap the physical T key" << std::endl;
std::cout << " esc # next scan cycle: tap the physcial esc key" << std::endl;
std::cout << " # take no action for a scan cycle" << std::endl;
std::cout << " (2,1) # tap the key in row 2, column 1" << std::endl;
std::cout << " lshift e # tap the lshift and e keys simultaneously" << std::endl;
std::cout << " s (1,3) (3,8) # tap the s key, the key at (1,3), and the key at (3,8) simultaneously" << std::endl;
std::cout << " p q lfn fly # tap the p, q, lfn, and fly keys simultaneously" << std::endl;
std::cout << "\n2. ADVANCED\n" << std::endl;
std::cout << "In addition to key names and the comment command '#', there are various other commands available." << std::endl;
std::cout << "Key names are always in all lowercase (defined as symbols that appear in the unshifted positions" << std::endl;
std::cout << " on the standard QWERTY Model 01); uppercase/shifted symbols denote commands." << std::endl;
std::cout << "Commands can be inserted anywhere in the input line, and affect the handling of keys following." << std::endl;
std::cout << "The default command, which we used above, is 'tap' (where the key is 'down' for just this cycle)." << std::endl;
std::cout << "'tap' can also be explicitly specified by 'T', as in \"T b\" to 'tap' the physical B key." << std::endl;
std::cout << "You can hold virtual keys down using the 'D' (down) command. The key will remain held until you" << std::endl;
std::cout << " say otherwise. In interactive mode, while keys are held, the prompt changes from '>' to '+>'." << std::endl;
std::cout << "You can release a previously held virtual key using the 'U' command." << std::endl;
std::cout << "Commands affect all following keys within the line unless overridden. So, \"D lshift u\" holds" << std::endl;
std::cout << " both lshift and u. To hold lshift and tap u, either enter \"D lshift T u\", or \"u D lshift\"." << std::endl;
std::cout << "An exception to the above rule is the command 'C', which releases all currently held keys." << std::endl;
std::cout << "One final command, 'Q', will quit the program. In non-interactive mode (i.e. with an input" << std::endl;
std::cout << " script), the end of the script also implicitly indicates the end of the program." << std::endl;
std::cout << "\nAdvanced script example:" << std::endl;
std::cout << " h # tap the physical H key" << std::endl;
std::cout << " D lshift # hold the physical lshift key down" << std::endl;
std::cout << " c (0,3) # tap both c and the key at (0,3) (with lshift held)" << std::endl;
std::cout << " D alt # hold alt (in addition to lshift)" << std::endl;
std::cout << " U lshift T e # Release lshift, and tap e in the same cycle" << std::endl;
std::cout << " # Do nothing for a scan cycle (but keep alt held)" << std::endl;
std::cout << " C # Release all held keys (in this case, just alt)" << std::endl;
std::cout << " enter D (1,12) # Tap the physical enter key, and hold the key at (1,12)" << std::endl;
std::cout << " fly # Tap the fly key (with (1,12) held)" << std::endl;
std::cout << " Q # Quit the program" << std::endl;
std::cout << std::endl;
}