summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYour Name <you@example.com>2024-03-28 16:18:36 -0400
committerYour Name <you@example.com>2024-03-28 16:18:36 -0400
commitc644379e79262a7478a7fb9ea8001f315fdb6eaf (patch)
treed71b15e396ebca28b76f0b5fd1971dd352877e4e /src
downloadnerdhack-master.tar.gz
nerdhack-master.tar.bz2
nerdhack-master.zip
Initial commitHEADmaster
Diffstat (limited to 'src')
-rw-r--r--src/nerdhack.cc20
-rw-r--r--src/nerdhack.d1
-rw-r--r--src/nerdhack.obin0 -> 39968 bytes
-rw-r--r--src/ui.cc51
-rw-r--r--src/ui.d1
-rw-r--r--src/ui.h22
-rw-r--r--src/ui.obin0 -> 403008 bytes
-rw-r--r--src/ui_private.h21
-rw-r--r--src/ui_terminal.cc279
-rw-r--r--src/ui_terminal.h102
-rw-r--r--src/ui_terminal_callbacks.cc129
-rw-r--r--src/ui_terminal_callbacks.d2
-rw-r--r--src/vm.cc85
-rw-r--r--src/vm.h25
14 files changed, 738 insertions, 0 deletions
diff --git a/src/nerdhack.cc b/src/nerdhack.cc
new file mode 100644
index 0000000..1363096
--- /dev/null
+++ b/src/nerdhack.cc
@@ -0,0 +1,20 @@
+#include <iostream>
+#include <fstream>
+#include "ui.h"
+#include "vm.h"
+
+int main(int argc, char* argv[]) {
+ if (argc != 2) {
+ std::cerr << "Requires path to config as an argument\n";
+ return 1;
+ }
+ vm::clearAllVMs();
+ auto vm = vm::VM();
+ auto ui = ui::UI();
+ // Read config
+ std::ifstream ifs(argv[1]);
+ std::string config((std::istreambuf_iterator<char>(ifs)),(std::istreambuf_iterator<char>()));
+ vm.start(config);
+ ui.registerCallbacks(vm);
+ return 0;
+}
diff --git a/src/nerdhack.d b/src/nerdhack.d
new file mode 100644
index 0000000..bf6d988
--- /dev/null
+++ b/src/nerdhack.d
@@ -0,0 +1 @@
+src/nerdhack.o: src/nerdhack.cc src/ui.h src/ui_terminal.h src/vm.h
diff --git a/src/nerdhack.o b/src/nerdhack.o
new file mode 100644
index 0000000..ff95227
--- /dev/null
+++ b/src/nerdhack.o
Binary files differ
diff --git a/src/ui.cc b/src/ui.cc
new file mode 100644
index 0000000..37f8078
--- /dev/null
+++ b/src/ui.cc
@@ -0,0 +1,51 @@
+#include "ui.h"
+#include "ui_private.h"
+#include "ui_terminal.h"
+#include <cstring>
+#include <iostream>
+#include <ctype.h>
+#include <format>
+
+#define TERMLINES 24
+#define TERMCOLS 80
+
+
+namespace ui {
+
+ UI::UI() : data(new uiImpl()) {
+ initscr();
+ start_color();
+ data->terminal = newwin(TERMLINES, TERMCOLS, 1, COLS-TERMCOLS-1);
+ data->termBorder = newwin(TERMLINES+2, TERMCOLS+2, 0, COLS-TERMCOLS-2);
+ scrollok(data->terminal, TRUE);
+ idlok(data->terminal, TRUE);
+ noecho();
+ cbreak();
+ box(data->termBorder, 0, 0);
+ mvwprintw(data->termBorder, 0, 1, "TERMINAL");
+ refresh();
+ wrefresh(data->termBorder);
+ wrefresh(data->terminal);
+ data->parser = CommandParser(getCommandCallbacks(this->data->terminal));
+ }
+
+ UI::~UI() {
+ endwin();
+ }
+
+ void UI::registerCallbacks(vm::VM& vm) {
+ vm.connect([this](const char *data, size_t nbytes) {
+ for(size_t i = 0; i < nbytes; i++) {
+ std::cerr << std::format("0x{:x}|", data[i]);
+ this->data->parser.parse(data[i]);
+ }
+ wrefresh(this->data->terminal);
+ return int(nbytes);
+ },
+ [](char *data, size_t nbytes) {
+ data[0] = getch();
+ return 1;
+ });
+ }
+
+}
diff --git a/src/ui.d b/src/ui.d
new file mode 100644
index 0000000..4897dc8
--- /dev/null
+++ b/src/ui.d
@@ -0,0 +1 @@
+src/ui.o: src/ui.cc src/ui.h src/ui_terminal.h src/vm.h src/ui_private.h
diff --git a/src/ui.h b/src/ui.h
new file mode 100644
index 0000000..daeab78
--- /dev/null
+++ b/src/ui.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <memory>
+#include <ncurses.h>
+#include "ui_terminal.h"
+#include "vm.h"
+
+namespace ui {
+ struct uiImpl;
+
+ class UI {
+ public:
+ UI();
+ ~UI();
+
+ void registerCallbacks(vm::VM& vm);
+
+ private:
+ std::shared_ptr<uiImpl> data;
+ std::map<commands, std::function<void(std::vector<int>)>> getCommandCallbacks(WINDOW *window);
+ };
+
+}
diff --git a/src/ui.o b/src/ui.o
new file mode 100644
index 0000000..b1e2010
--- /dev/null
+++ b/src/ui.o
Binary files differ
diff --git a/src/ui_private.h b/src/ui_private.h
new file mode 100644
index 0000000..e45c57b
--- /dev/null
+++ b/src/ui_private.h
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace ui {
+
+ struct uiImpl {
+ WINDOW *termBorder;
+ WINDOW *terminal;
+ CommandParser parser;
+ int savedXDEC = 0;
+ int savedYDEC = 0;
+ attr_t savedAttrs;
+ short savedColor = 0;
+ int savedColorInt = 0;
+ int savedXCP = 0;
+ int savedYCP = 0;
+ // Modes
+ bool DECCRM = false;
+ bool DECIM = false;
+ };
+
+}
diff --git a/src/ui_terminal.cc b/src/ui_terminal.cc
new file mode 100644
index 0000000..f7cc023
--- /dev/null
+++ b/src/ui_terminal.cc
@@ -0,0 +1,279 @@
+#include "ui_terminal.h"
+#include <string>
+#include <cctype>
+#include <cassert>
+#include <algorithm>
+#include <set>
+#include <iostream> //TODO: Remove this
+#include <format> // Ditto
+
+namespace ui {
+
+ // Parse all commands found in https://www.man7.org/linux/man-pages/man4/console_codes.4.html
+ // Structure is inspired by https://github.com/tmux/tmux/blob/master/input.c
+ static const std::map<std::tuple<int,int,int>, commands> commandTable = {
+ // Control, intermediary, terminator -> command
+ {{0x07, 0, 0}, BEL},
+ {{0x08, 0, 0}, BS},
+ {{0x09, 0, 0}, HT},
+ {{0x0A, 0, 0}, LF},
+ {{0x0B, 0, 0}, VT},
+ {{0x0C, 0, 0}, FF},
+ {{0x0D, 0, 0}, CR},
+ {{0x0E, 0, 0}, SO},
+ {{0x0F, 0, 0}, SI},
+ {{0x7F, 0, 0}, DEL},
+ {{0x1B, '(', '0'}, SCSG0_ON},
+ {{0x1B, ')', '0'}, SCSG1_ON},
+ {{0x1B, 0, '7'}, DECSC},
+ {{0x1B, 0, '8'}, DECRC},
+ {{0x1B, '#', '8'}, DECALN},
+ {{0x1B, 0, '='}, DECPAM},
+ {{0x1B, 0, '>'}, DECPNM},
+ {{0x1B, '(', 'B'}, SCSG0_OFF},
+ {{0x1B, ')', 'B'}, SCSG1_OFF},
+ {{0x1B, 0, 'D'}, IND},
+ {{0x1B, 0, 'E'}, NEL},
+ {{0x1B, 0, 'H'}, HTS},
+ {{0x1B, 0, 'M'}, RI},
+ {{0x1B, 0, '\\'}, ST},
+ {{0x1B, 0, 'c'}, RIS},
+ // CSI sequences (ESC [ is mapped to CSI)
+ {{0x9B, 0, '@'}, ICH},
+ {{0x9B, 0, 'A'}, CUU},
+ {{0x9B, 0, 'B'}, CUD},
+ {{0x9B, 0, 'C'}, CUF},
+ {{0x9B, 0, 'D'}, CUB},
+ {{0x9B, 0, 'E'}, CNL},
+ {{0x9B, 0, 'F'}, CPL},
+ {{0x9B, 0, 'G'}, CHA},
+ {{0x9B, 0, 'H'}, CUP},
+ {{0x9B, 0, 'J'}, ED},
+ {{0x9B, 0, 'K'}, EL},
+ {{0x9B, 0, 'L'}, IL},
+ {{0x9B, 0, 'M'}, DL},
+ {{0x9B, 0, 'P'}, DCH},
+ {{0x9B, 0, 'S'}, SU},
+ {{0x9B, '?', 'S'}, SM_GRAPHICS},
+ {{0x9B, 0, 'T'}, SD},
+ {{0x9B, 0, 'X'}, ECH},
+ {{0x9B, 0, 'Z'}, CBT},
+ {{0x9B, 0, '`'}, HPA},
+ {{0x9B, 0, 'a'}, HPR},
+ {{0x9B, 0, 'b'}, REP},
+ {{0x9B, 0, 'c'}, DA},
+ {{0x9B, 0, 'd'}, VPA},
+ {{0x9B, 0, 'e'}, VPR},
+ {{0x9B, 0, 'f'}, HVP},
+ {{0x9B, 0, 'g'}, TBC},
+ {{0x9B, 0, 'h'}, SM},
+ {{0x9B, '?', 'h'}, SM_PRIVATE},
+ {{0x9B, 0, 'l'}, RM},
+ {{0x9B, '?', 'l'}, RM_PRIVATE},
+ {{0x9B, 0, 'm'}, SGR},
+ {{0x9B, 0, 'n'}, DSR},
+ {{0x9B, 0, 'r'}, DECSTBM},
+ {{0x9B, 0, 's'}, SCP},
+ {{0x9B, 0, 't'}, WINOPS},
+ {{0x9B, 0, 'u'}, RCP}
+ };
+
+ std::set<int> getIntermediaries() {
+ std::set<int> ret;
+ for(auto pair : commandTable) {
+ if(std::get<1>(pair.first)) ret.insert(std::get<1>(pair.first));
+ }
+ return ret;
+ }
+ static const std::set<int> intermediaries = getIntermediaries();
+
+ static const std::map<commands, std::string> commands2names = {
+ {BEL, "BEL"},
+ {BS, "BS"},
+ {HT, "HT"},
+ {LF, "LF"},
+ {VT, "VT"},
+ {FF, "FF"},
+ {CR, "CR"},
+ {SO, "SO"},
+ {SI, "SI"},
+ {DEL, "DEL"},
+ {DECALN, "DECALN"},
+ {DECPAM, "DECPAM"},
+ {DECPNM, "DECPNM"},
+ {DECRC, "DECRC"},
+ {DECSC, "DECSC"},
+ {HTS, "HTS"},
+ {IND, "IND"},
+ {NEL, "NEL"},
+ {RI, "RI"},
+ {RIS, "RIS"},
+ {SCSG0_OFF, "SCSG0_OFF"},
+ {SCSG0_ON, "SCSG0_ON"},
+ {SCSG1_OFF, "SCSG1_OFF"},
+ {SCSG1_ON, "SCSG1_ON"},
+ {ST, "ST"},
+ {CBT, "CBT"},
+ {CNL, "CNL"},
+ {CPL, "CPL"},
+ {CUB, "CUB"},
+ {CUD, "CUD"},
+ {CUF, "CUF"},
+ {CHA, "CHA"},
+ {CUP, "CUP"},
+ {CUU, "CUU"},
+ {DA, "DA"},
+ {DCH, "DCH"},
+ {DECSTBM, "DECSTBM"},
+ {DL, "DL"},
+ {DSR, "DSR"},
+ {ECH, "ECH"},
+ {ED, "ED"},
+ {EL, "EL"},
+ {HPA, "HPA"},
+ {HPR, "HPR"},
+ {HVP, "HVP"},
+ {ICH, "ICH"},
+ {IL, "IL"},
+ {RCP, "RCP"},
+ {REP, "REP"},
+ {RM, "RM"},
+ {RM_PRIVATE, "RM_PRIVATE"},
+ {SCP, "SCP"},
+ {SD, "SD"},
+ {SGR, "SGR"},
+ {SM, "SM"},
+ {SM_PRIVATE, "SM_PRIVATE"},
+ {SM_GRAPHICS, "SM_GRAPHICS"},
+ {SU, "SU"},
+ {TBC, "TBC"},
+ {VPA, "VPA"},
+ {VPR, "VPR"},
+ {WINOPS, "WINOPS"},
+ };
+
+ static const int NPAR=16;
+
+ struct commandParserImpl {
+ std::wstring escape;
+ std::map<commands, std::function<void(std::vector<int>)>> callbacks;
+ };
+
+
+ CommandParser::CommandParser() : data(new commandParserImpl()) {}
+
+ CommandParser::CommandParser(std::map<commands, std::function<void(std::vector<int>)>> callbacks) : data(new commandParserImpl()) {
+ data->callbacks = callbacks;
+ }
+
+ // Assumes that escape has already undergone conversion of ESC[ -> CSI, interrupting control characters processed, etc.
+ // Returns Control, intermediary, terminator, params
+ std::tuple<int,int,int,std::vector<int>> extractParts(std::wstring seq) {
+ int ctrl=0, interm=0, term=0;
+ std::vector<int> params;
+ if(seq.length() >= 1) {
+ ctrl = seq[0];
+ seq = seq.substr(1); // Peel off ctrl
+ }
+ if(seq.length() >= 1 and intermediaries.contains(seq[0])) {
+ interm = seq[0];
+ seq = seq.substr(1); // Peel off interm
+ }
+ while(seq.length() >= 1 and ((seq[0] >= '0' and seq[0] <= '9') or seq[0] == ';') and params.size() < NPAR) {
+ if(seq[0] == ';') {
+ params.push_back(0);
+ seq = seq.substr(1);
+ continue;
+ }
+ auto end = seq.find_first_not_of(L"1234567890");
+ if(end == std::string::npos) end = seq.length();
+ int param = 0;
+ try { param = (int) std::stol(seq.substr(0, end)); }
+ catch(std::exception& e) {}
+ params.push_back(param);
+ seq = seq.substr(end);
+ if(seq.length() >= 1 and seq[0] == ';') seq = seq.substr(1); // Remove trailing ';'
+ }
+ if(seq.length() >= 1) {
+ term = seq[0];
+ seq = seq.substr(1); // Peel off term
+ }
+ assert(seq.empty() && "Parsed sequence is not empty");
+ return {ctrl, interm, term, params};
+ }
+
+ std::vector<std::tuple<int,int,int>> getCmdsPossible(std::tuple<int,int,int,std::vector<int>> parsed) {
+ std::vector<std::tuple<int,int,int>> cmds;
+ for(const auto &keyval : commandTable) {
+ if((std::get<0>(parsed) == 0 or std::get<0>(parsed) == std::get<0>(keyval.first)) and
+ (std::get<1>(parsed) == 0 or std::get<1>(parsed) == std::get<1>(keyval.first)) and
+ (std::get<2>(parsed) == 0 or std::get<2>(parsed) == std::get<2>(keyval.first))) {
+ cmds.push_back(keyval.first);
+ }
+ }
+ return cmds;
+ }
+
+ std::string formatParsed(std::tuple<int,int,int,std::vector<int>> parsed, commands command) {
+ auto sanatize = [](int c){ if(c >= 32 and c < 127) return std::string(1, c); else return std::format("0x{:x}", c); };
+ std::string formatted = sanatize(std::get<0>(parsed)) + "," + sanatize(std::get<1>(parsed)) + "," + sanatize(std::get<2>(parsed));
+ if(commands2names.count(command)) formatted += "[" + commands2names.at(command) + "]";
+ formatted += "(";
+ for(int i : std::get<3>(parsed)) {
+ formatted += std::to_string(i) + ";";
+ }
+ if(formatted[formatted.length()-1] == ';') formatted = formatted.substr(0, formatted.length()-1);
+ formatted += ")";
+ return formatted;
+ }
+
+ std::tuple<commands, std::vector<int>> parseInternal(int c, std::wstring &escape) {
+ // First test if c is a control char BEL, BS, HT, LF, VT, FF, CR, SO, SI, or DEL
+ std::set<int> ctrlCharActions = {0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x7F};
+ if(ctrlCharActions.find(c) != ctrlCharActions.end()) return {commandTable.at({c,0,0}), std::vector<int>()};
+ if(c == 0x18 or c == 0x1A) { // Next handle CAN and SUB
+ escape.clear();
+ return {commands::INCOMPLETE, std::vector<int>()};
+ }
+ if(c == 0x1B or c == 0x9B) { // Finally, ESC and CSI
+ escape.clear();
+ } else if(escape.empty()) { // We are not in an escape sequence
+ return {commands::NOTCOMMAND, {c}};
+ }
+ escape.push_back(c);
+ // Handle ESC[ -> CSI conversion
+ if(escape.starts_with(L"\033[")) {
+ escape.replace(0, 2, L"\233");
+ }
+ // Handle echoed function key
+ if(escape.length() >= 3 and escape[1] == '[') {
+ escape.clear();
+ return {commands::INCOMPLETE, std::vector<int>()};
+ }
+ auto parsed = extractParts(escape);
+ auto cmds = getCmdsPossible(parsed);
+ if(cmds.empty()) {
+ escape.clear();
+ std::cerr << "\nError processing " << formatParsed(parsed, commands::INVALID) << "\n";
+ return {commands::INVALID, std::get<3>(parsed)};
+ }
+ if(cmds.size() > 1 or std::get<2>(parsed) == 0) {
+ return {commands::INCOMPLETE, std::get<3>(parsed)};
+ }
+ assert(cmds.size() == 1 && "Error parsing commands");
+ auto command = commandTable.at(cmds[0]);
+ escape.clear();
+ std::cerr << "\nSuccess processing " << formatParsed(parsed, command) << "\n";
+ return {command, std::get<3>(parsed)};
+ }
+
+ std::tuple<commands, std::vector<int>> CommandParser::parse(int c) {
+ auto parsed = parseInternal(c, this->data->escape);
+ auto command = std::get<0>(parsed);
+ if(this->data->callbacks.count(command)) {
+ if(command != commands::NOTCOMMAND) std::cerr << "Executing command " << commands2names.at(command) << "\n";
+ this->data->callbacks.at(command)(std::get<1>(parsed));
+ }
+ return {command, std::get<1>(parsed)};
+ }
+ }
diff --git a/src/ui_terminal.h b/src/ui_terminal.h
new file mode 100644
index 0000000..266fc07
--- /dev/null
+++ b/src/ui_terminal.h
@@ -0,0 +1,102 @@
+#pragma once
+#include <vector>
+#include <map>
+#include <tuple>
+#include <functional>
+#include <memory>
+
+namespace ui {
+
+ // Escape (ESC) and Control (CSI) commands.
+ enum commands {
+ // Specific to this codebase
+ INCOMPLETE, // We are in the middle of parsing a not-yet-completed command
+ NOTCOMMAND, // We are not currently parsing a command; the character is placed in callback args
+ INVALID, // Command was fully parsed but was invalid
+ // Control characters
+ BEL,
+ BS,
+ HT,
+ LF,
+ VT,
+ FF,
+ CR,
+ SO,
+ SI,
+ DEL,
+ // ESC commands
+ DECALN,
+ DECPAM,
+ DECPNM,
+ DECRC,
+ DECSC,
+ HTS,
+ IND,
+ NEL,
+ RI,
+ RIS,
+ SCSG0_OFF,
+ SCSG0_ON,
+ SCSG1_OFF,
+ SCSG1_ON,
+ ST,
+ // CSI commands
+ CBT,
+ CNL,
+ CPL,
+ CUB,
+ CUD,
+ CUF,
+ CHA,
+ CUP,
+ CUU,
+ DA,
+ DCH,
+ DECSTBM,
+ DL,
+ DSR,
+ ECH,
+ ED,
+ EL,
+ HPA,
+ HPR,
+ HVP,
+ ICH,
+ IL,
+ RCP,
+ REP,
+ RM,
+ RM_PRIVATE,
+ SCP,
+ SD,
+ SGR,
+ SM,
+ SM_PRIVATE,
+ SM_GRAPHICS,
+ SU,
+ TBC,
+ VPA,
+ VPR,
+ WINOPS
+ };
+
+ struct commandParserImpl;
+
+ class CommandParser {
+ public:
+ // Construct a parser which does not automatically execute commands
+ CommandParser();
+ // Construct a parser that calls a function when a command is parsed
+ CommandParser(std::map<commands, std::function<void(std::vector<int>)>>);
+ ~CommandParser() {}
+
+ // Parse the next character in the stream. This is stateful.
+ std::tuple<commands, std::vector<int>> parse(int);
+
+
+ private:
+ std::shared_ptr<commandParserImpl> data;
+
+ };
+
+}
diff --git a/src/ui_terminal_callbacks.cc b/src/ui_terminal_callbacks.cc
new file mode 100644
index 0000000..a742e27
--- /dev/null
+++ b/src/ui_terminal_callbacks.cc
@@ -0,0 +1,129 @@
+#include "ui.h"
+#include "ui_terminal.h"
+#include "ui_private.h"
+
+namespace ui {
+
+#define CURYX int y, x; getyx(terminal, y, x);
+#define EXPARGS(len, defaultValue) while(args.size() < len) args.push_back(defaultValue);
+
+
+ void setMode(std::shared_ptr<uiImpl> data, int mode, bool setting) {
+ switch(mode) {
+ case 3:
+ data->DECCRM = setting; //TODO: Implement
+ break;
+ case 4:
+ data->DECIM = setting;
+ break;
+ case 20:
+ if(setting) nl();
+ else nonl();
+ break;
+ default:
+ break;
+ }
+ }
+
+ void SGR(std::vector<int> args, WINDOW *terminal) {
+ switch(args[0]) {
+ case 0:
+ wattr_on(terminal, A_NORMAL);
+ break;
+ case 1:
+ wattr_on(terminal, A_BOLD);
+ break;
+ case 2:
+ wattr_on(terminal, A_DIM);
+ break;
+ case 3:
+ wattr_on(terminal, A_ITALIC);
+ break;
+ case 4:
+ case 21:
+ wattr_on(terminal, A_UNDERLINE);
+ break;
+ case 5:
+ wattr_on(terminal, A_BLINK);
+ break;
+ case 7:
+ wattr_on(terminal, A_REVERSE);
+ break;
+ case 10: // TODO: Investigate behavior of 10-12
+ wattr_off(terminal, A_ALTCHARSET);
+ break;
+ case 11:
+ wattr_on(terminal, A_ALTCHARSET);
+ break;
+ case 12:
+ wattr_on(terminal, A_ALTCHARSET);
+ break;
+ case 22:
+ wattr_off(terminal, A_DIM);
+ wattr_off(terminal, A_BOLD);
+ break;
+ case 23:
+ wattr_off(terminal, A_ITALIC);
+ break;
+ case 24:
+ wattr_off(terminal, A_UNDERLINE);
+ break;
+ case 25:
+ wattr_off(terminal, A_BLINK);
+ break;
+ case 27:
+ wattr_off(terminal, A_REVERSE);
+ break;
+ case 30:
+ wattr_on(terminal, );
+ break;
+ case :
+ wattr_on(terminal, );
+ break;
+ }
+ }
+
+ std::map<commands, std::function<void(std::vector<int>)>> UI::getCommandCallbacks(WINDOW *terminal) {
+ std::map<commands, std::function<void(std::vector<int>)>> cmdMap = {
+ {commands::NOTCOMMAND, [terminal,data=data](std::vector<int> args) { if(data->DECIM) winsch(terminal, args[0]); else waddch(terminal, args[0]); }},
+ {commands::BS, [terminal](std::vector<int> args) { wprintw(terminal, "\b"); }}, //TODO: Probably incorrect, see https://github.com/tmux/tmux/blob/master/screen-write.c#L982
+ {commands::HT, [terminal](std::vector<int> args) { wprintw(terminal, "\t"); }},
+ {commands::LF, [terminal](std::vector<int> args) { wprintw(terminal, "\n"); }},
+ {commands::CR, [terminal](std::vector<int> args) { wprintw(terminal, "\r"); }},
+ {commands::RIS, [terminal](std::vector<int> args) { wclear(terminal); wmove(terminal, 0, 0); wattr_on(terminal, A_NORMAL); }},
+ {commands::IND, [terminal](std::vector<int> args) { CURYX wmove(terminal, y+1, x); }},
+ {commands::RI, [terminal](std::vector<int> args) { CURYX wmove(terminal, y-1, x); }},
+ {commands::DECSC, [terminal,data=data](std::vector<int> args) { getyx(terminal, data->savedYDEC, data->savedXDEC); wattr_get(terminal, &data->savedAttrs, &data->savedColor, &data->savedColorInt); }},
+ {commands::DECRC, [terminal,data=data](std::vector<int> args) { wmove(terminal, data->savedYDEC, data->savedXDEC); wattr_set(terminal, data->savedAttrs, data->savedColor, &data->savedColorInt); }},
+ {commands::CUU, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y-args[0], x); }},
+ {commands::CUD, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y+args[0], x); }},
+ {commands::CUF, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y, x+args[0]); }},
+ {commands::CUB, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y, x-args[0]); }},
+ {commands::CNL, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y+args[0], 0); }},
+ {commands::CPL, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y-args[0], 0); }},
+ {commands::CHA, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y, args[0]-1); (void)x; }},
+ {commands::CUP, [terminal](std::vector<int> args) { EXPARGS(2, 1) wmove(terminal, args[0]-1, args[1]-1); }},
+ {commands::ED, [terminal](std::vector<int> args) { EXPARGS(1, 0) CURYX if(args[0] == 0) wclrtobot(terminal); else if(args[0] == 1) { for(int i=0; i<x; i++) mvwaddch(terminal, y, i, ' '); for(int i=0; i<y; i++) {wmove(terminal, i, 0); wclrtoeol(terminal);} wmove(terminal, y, x); } else if(args[0] == 2 or args[0] == 3) wclear(terminal); }},
+ {commands::EL, [terminal](std::vector<int> args) { EXPARGS(1, 0) CURYX if(args[0] == 0) wclrtoeol(terminal); else if(args[0] == 1) { for(int i=0; i<x; i++) mvwaddch(terminal, y, i, ' '); wmove(terminal, y, x); } else if(args[0] == 2) { wmove(terminal, y, 0); wclrtoeol(terminal); wmove(terminal, y, x); } }},
+ {commands::IL, [terminal](std::vector<int> args) { EXPARGS(1, 1) winsdelln(terminal, args[0]); }},
+ {commands::DL, [terminal](std::vector<int> args) { EXPARGS(1, 1) winsdelln(terminal, -1*args[0]); }},
+ {commands::DCH, [terminal](std::vector<int> args) { EXPARGS(1, 1) for(int i=0; i<args[0]; i++) wdelch(terminal); }},
+ {commands::ECH, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX for(int i=0; i<args[0]; i++) waddch(terminal, ' '); wmove(terminal, y, x); }},
+ {commands::VPA, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, args[0]-1, x); (void)y; }},
+ {commands::VPR, [terminal](std::vector<int> args) { EXPARGS(1, 1) CURYX wmove(terminal, y+args[0], x); }},
+ {commands::SCP, [terminal,data=data](std::vector<int> args) { getyx(terminal, data->savedYCP, data->savedXCP); }},
+ {commands::RCP, [terminal,data=data](std::vector<int> args) { wmove(terminal, data->savedYCP, data->savedXCP); }},
+ {commands::SM, [data=data](std::vector<int> args) { EXPARGS(1, 0) setMode(data, args[0], true); }},
+ {commands::RM, [data=data](std::vector<int> args) { EXPARGS(1, 0) setMode(data, args[0], false); }},
+ {commands::SGR, [terminal](std::vector<int> args) { EXPARGS(1, 0) SGR(args, terminal); }},
+ };
+ cmdMap.insert({commands::VT, cmdMap[commands::LF]});
+ cmdMap.insert({commands::FF, cmdMap[commands::LF]});
+ cmdMap.insert({commands::NEL, cmdMap[commands::LF]});
+ cmdMap.insert({commands::HPR, cmdMap[commands::CUF]});
+ cmdMap.insert({commands::HVP, cmdMap[commands::CUP]});
+ cmdMap.insert({commands::HPA, cmdMap[commands::CHA]});
+
+ return cmdMap;
+ }
+}
diff --git a/src/ui_terminal_callbacks.d b/src/ui_terminal_callbacks.d
new file mode 100644
index 0000000..4d6f7db
--- /dev/null
+++ b/src/ui_terminal_callbacks.d
@@ -0,0 +1,2 @@
+src/ui_terminal_callbacks.o: src/ui_terminal_callbacks.cc src/ui.h \
+ src/ui_terminal.h src/vm.h src/ui_private.h
diff --git a/src/vm.cc b/src/vm.cc
new file mode 100644
index 0000000..29fcc43
--- /dev/null
+++ b/src/vm.cc
@@ -0,0 +1,85 @@
+#include "vm.h"
+#include <iostream>
+#include <thread>
+
+namespace vm {
+ virConnectPtr conn = NULL;
+
+ struct vmImpl {
+ virDomainPtr dom;
+ virStreamPtr stream;
+ std::thread receiver;
+ };
+
+ void initConn() {
+ if (conn == NULL) {
+ conn = virConnectOpen("qemu:///system");
+ }
+ if (conn == NULL) {
+ std::cerr << "Failed to open connection to qemu:///system\n";
+ }
+ }
+
+ void clearAllVMs() {
+ initConn();
+ virDomainPtr *domains;
+ int numDomains = virConnectListAllDomains(conn, &domains, 0);
+ for (int i = 0; i < numDomains; i++) {
+ virDomainDestroy(domains[i]);
+ virDomainFree(domains[i]);
+ }
+ free(domains);
+ }
+
+ VM::VM() : data(new vmImpl()) {
+ initConn();
+ data->stream = virStreamNew(conn, 0);
+ }
+
+ VM::~VM() {
+ virStreamAbort(data->stream);
+ virStreamFree(data->stream);
+ }
+
+ int VM::start(std::string config) {
+ data->dom = virDomainCreateXML(conn, config.c_str(), VIR_DOMAIN_START_AUTODESTROY);
+ if (!data->dom) {
+ std::cerr << "Domain creation failed\n";
+ return 1;
+ }
+ return 0;
+ }
+
+ void VM::stop() {
+ if (data->dom) {
+ virDomainFree(data->dom);
+ }
+ }
+
+ void receiveTask(virStreamPtr stream, std::function<int(const char*,size_t)> receiver) {
+
+ virStreamRecvAll(stream,
+ [](virStreamPtr stream, const char *data, size_t nbytes, void *opaque) {
+ auto *receiver = static_cast<std::function<int(const char*,size_t)>*>(opaque);
+ return (*receiver)(data, nbytes);
+ },
+ &receiver);
+ }
+
+ void sendTask(virStreamPtr stream, std::function<int(char*,size_t)> sender) {
+ virStreamSendAll(stream,
+ [](virStreamPtr stream, char *data, size_t nbytes, void *opaque) {
+ auto *sender = static_cast<std::function<int(char*,size_t)>*>(opaque);
+ return (*sender)(data, nbytes);
+ },
+ &sender);
+ }
+
+ void VM::connect(std::function<int(const char*,size_t)> receiver, std::function<int(char*,size_t)> sender) {
+ if (virDomainOpenConsole(data->dom, NULL, data->stream, 0)) {
+ std::cerr << "Failed to open console\n";
+ }
+ data->receiver = std::thread(receiveTask, data->stream, receiver);
+ sendTask(data->stream, sender); // Blocking
+ }
+}
diff --git a/src/vm.h b/src/vm.h
new file mode 100644
index 0000000..a959511
--- /dev/null
+++ b/src/vm.h
@@ -0,0 +1,25 @@
+#pragma once
+#include <libvirt/libvirt.h>
+#include <memory>
+#include <functional>
+
+namespace vm {
+ struct vmImpl;
+
+ void clearAllVMs();
+
+ class VM {
+ public:
+ VM();
+ ~VM();
+
+ int start(std::string config);
+ void stop();
+
+ void connect(std::function<int(const char*,size_t)> receiver, std::function<int(char*,size_t)> sender);
+
+ private:
+ std::shared_ptr<vmImpl> data;
+ };
+
+}