Update to v094r07 release.

byuu says:

Changelog for loki:
- added command aliases (match with * [sorry, regex lib isn't available
  everywhere yet], replace with {1}+)
- added command hotkeys
- added window geometry saving
- added save state support
- added power/reset commands
- added an input manager, so you can remap keys (limiting it to the
  keyboard for now though)

The combination of aliases and hotkeys really makes things shine. Save
states will temporarily disable your breakpoints (run/step are
technically temporary breakpoints) so as to ensure the state is captured
at a good time. In practice, this should pose about as much of a problem
as higan desyncing and breaking when capturing states ... should be
exceedingly rare to ever even notice this behavior at all, with 99.9% of
state captures happening in half an instruction boundary. But still,
keep it in mind, as you might see the CPU step one instruction ahead.
Tracing and usage map functionality is still enabled during state
synchronization.

So at this point, I have 100% of the essential stuff in. All that's left
now is to add polish / wishlist features like bass and mosaic
integration.
This commit is contained in:
Tim Allen 2014-02-09 17:04:28 +11:00
parent 3016e595f0
commit ecc651c88b
17 changed files with 357 additions and 77 deletions

View File

@ -6,8 +6,8 @@ gb := gb
gba := gba gba := gba
profile := accuracy profile := accuracy
# target := higan target := higan
target := loki # target := loki
ifeq ($(target),loki) ifeq ($(target),loki)
options += debugger options += debugger

View File

@ -3,7 +3,7 @@
namespace Emulator { namespace Emulator {
static const char Name[] = "higan"; static const char Name[] = "higan";
static const char Version[] = "094.06"; static const char Version[] = "094.07";
static const char Author[] = "byuu"; static const char Author[] = "byuu";
static const char License[] = "GPLv3"; static const char License[] = "GPLv3";
static const char Website[] = "http://byuu.org/"; static const char Website[] = "http://byuu.org/";

View File

@ -6,7 +6,7 @@ include processor/Makefile
include sfc/Makefile include sfc/Makefile
include gb/Makefile include gb/Makefile
ui_objects := ui-loki ui-settings ui_objects := ui-loki ui-settings ui-input
ui_objects += ui-interface ui-debugger ui_objects += ui-interface ui-debugger
ui_objects += ui-presentation ui-terminal ui_objects += ui-presentation ui-terminal
ui_objects += ruby phoenix ui_objects += ruby phoenix
@ -33,6 +33,7 @@ objects := $(patsubst %,obj/%.o,$(objects))
obj/ui-loki.o: $(ui)/loki.cpp $(call rwildcard,$(ui)/) obj/ui-loki.o: $(ui)/loki.cpp $(call rwildcard,$(ui)/)
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/) obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/) obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/)
obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/) obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/)

View File

@ -77,6 +77,7 @@ void Debugger::leave() {
} }
bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) { bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) {
if(savingState) return false;
for(unsigned n = 0; n < breakpoints.size(); n++) { for(unsigned n = 0; n < breakpoints.size(); n++) {
auto& bp = breakpoints[n]; auto& bp = breakpoints[n];
if(bp.source != source) continue; if(bp.source != source) continue;
@ -123,6 +124,8 @@ void Debugger::cpuExec(uint24 addr) {
} }
} }
if(savingState) return;
if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) { if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) {
echo(cpuDisassemble(), "\n"); echo(cpuDisassemble(), "\n");
return leave(); return leave();
@ -366,6 +369,8 @@ void Debugger::smpExec(uint16 addr) {
} }
} }
if(savingState) return;
if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) { if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) {
echo(smpDisassemble(), "\n"); echo(smpDisassemble(), "\n");
return leave(); return leave();
@ -423,6 +428,23 @@ string Debugger::sourceName(Source source) {
return "none"; return "none";
} }
void Debugger::stateLoad(string filename) {
auto memory = file::read(filename);
if(memory.size() == 0) return echo("Error: state file ", notdir(filename), " not found\n");
serializer s(memory.data(), memory.size());
if(emulator->unserialize(s) == false) return echo("Error: failed to unserialize state from ", notdir(filename), "\n");
echo("State loaded from ", notdir(filename), "\n");
}
void Debugger::stateSave(string filename) {
savingState = true;
serializer s = emulator->serialize();
if(file::write(filename, s.data(), s.size())) {
echo("State saved to ", notdir(filename), "\n");
}
savingState = false;
}
void Debugger::tracerDisable(Source source) { void Debugger::tracerDisable(Source source) {
if(source != Source::CPU && source != Source::SMP) return; if(source != Source::CPU && source != Source::SMP) return;
file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile); file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile);

View File

@ -60,12 +60,15 @@ struct Debugger {
void smpRead(uint16 addr, uint8 data); void smpRead(uint16 addr, uint8 data);
void smpWrite(uint16 addr, uint8 data); void smpWrite(uint16 addr, uint8 data);
string sourceName(Source source); string sourceName(Source source);
void stateLoad(string filename);
void stateSave(string filename);
void tracerDisable(Source source); void tracerDisable(Source source);
void tracerEnable(Source source, string filename); void tracerEnable(Source source, string filename);
void tracerMaskDisable(Source source); void tracerMaskDisable(Source source);
void tracerMaskEnable(Source source); void tracerMaskEnable(Source source);
bool running = false; bool running = false; //emulation runs asynchronously (cooperatively) to terminal commands
bool savingState = false; //suppresses all break events to allow state to be captured synchronously
uint8* apuUsage = nullptr; uint8* apuUsage = nullptr;
vector<Breakpoint> breakpoints; vector<Breakpoint> breakpoints;

View File

@ -0,0 +1,82 @@
#include "../loki.hpp"
InputManager* inputManager = nullptr;
void AbstractInput::bind() {
for(auto device : inputManager->devices) {
if(device->isKeyboard() == false) continue;
if(auto group = device->find("Button")) {
if(auto input = device->group[group()].find(mapping)) {
this->device = device;
this->group = group();
this->input = input();
break;
}
}
}
}
int16_t AbstractInput::poll() {
if(device == nullptr) return 0;
return device->group[group].input[input].value;
}
InputManager::InputManager() {
inputManager = this;
}
void InputManager::load() {
unsigned guid = 0;
Configuration::Node emulatorNode;
for(auto& port : emulator->port) {
Configuration::Node portNode;
for(auto& device : port.device) {
Configuration::Node deviceNode;
for(auto& number : device.order) {
auto& input = device.input[number];
input.guid = guid++;
auto abstract = new AbstractInput;
abstract->name = string{input.name}.replace(" ", "");
abstract->mapping = "None";
inputMap.append(abstract);
deviceNode.append(abstract->mapping, abstract->name);
}
portNode.append(deviceNode, string{device.name}.replace(" ", ""));
}
emulatorNode.append(portNode, string{port.name}.replace(" ", ""));
}
append(emulatorNode, "SuperFamicom");
Configuration::Document::load(program->path("input.bml"));
Configuration::Document::save(program->path("input.bml"));
}
void InputManager::unload() {
Configuration::Document::save(program->path("input.bml"));
}
void InputManager::bind() {
for(auto input : inputMap) input->bind();
}
void InputManager::poll() {
auto devices = input.poll();
bool changed = devices.size() != this->devices.size();
if(changed == false) {
for(unsigned n = 0; n < devices.size(); n++) {
changed = devices[n] != this->devices[n];
if(changed) break;
}
}
if(changed == true) {
this->devices = devices;
bind();
}
}

View File

@ -0,0 +1,25 @@
struct AbstractInput {
void bind();
int16_t poll();
string name;
string mapping;
HID::Device* device = nullptr;
unsigned group = 0;
unsigned input = 0;
};
struct InputManager : Configuration::Document {
InputManager();
void load();
void unload();
void bind();
void poll();
vector<HID::Device*> devices;
vector<AbstractInput*> inputMap;
};
extern InputManager* inputManager;

View File

@ -39,25 +39,6 @@ void Interface::unload() {
emulator->unload(); emulator->unload();
} }
void Interface::inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue) {
if(device.isKeyboard() == false) return;
switch(input) {
case 84: gamepad.up = newValue; break;
case 85: gamepad.down = newValue; break;
case 86: gamepad.left = newValue; break;
case 87: gamepad.right = newValue; break;
case 60: gamepad.b = newValue; break;
case 58: gamepad.a = newValue; break;
case 35: gamepad.y = newValue; break;
case 53: gamepad.x = newValue; break;
case 38: gamepad.l = newValue; break;
case 37: gamepad.r = newValue; break;
case 65: gamepad.select = newValue; break;
case 89: gamepad.start = newValue; break;
}
}
//bindings //bindings
void Interface::loadRequest(unsigned id, string name, string type) { void Interface::loadRequest(unsigned id, string name, string type) {
@ -100,8 +81,6 @@ void Interface::videoRefresh(const uint32_t* palette, const uint32_t* data, unsi
video.unlock(); video.unlock();
video.refresh(); video.refresh();
} }
input.poll();
} }
void Interface::audioSample(int16_t lsample, int16_t rsample) { void Interface::audioSample(int16_t lsample, int16_t rsample) {
@ -115,25 +94,8 @@ void Interface::audioSample(int16_t lsample, int16_t rsample) {
} }
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) { int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
if(presentation->focused() == false) return 0; unsigned guid = emulator->port[port].device[device].input[input].guid;
if(port != 0 || device != 0) return 0; return inputManager->inputMap[guid]->poll();
switch(input) {
case 0: return gamepad.b;
case 1: return gamepad.y;
case 2: return gamepad.select;
case 3: return gamepad.start;
case 4: return gamepad.up;
case 5: return gamepad.down;
case 6: return gamepad.left;
case 7: return gamepad.right;
case 8: return gamepad.a;
case 9: return gamepad.x;
case 10: return gamepad.l;
case 11: return gamepad.r;
}
return 0;
} }
void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) {

View File

@ -2,7 +2,6 @@ struct Interface : Emulator::Interface::Bind {
Interface(); Interface();
bool load(string pathname); bool load(string pathname);
void unload(); void unload();
void inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue);
//bindings //bindings
void loadRequest(unsigned id, string name, string type); void loadRequest(unsigned id, string name, string type);
@ -20,20 +19,6 @@ struct Interface : Emulator::Interface::Bind {
string pathname; //path to game folder string pathname; //path to game folder
lstring pathnames; lstring pathnames;
struct Gamepad {
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool b = false;
bool a = false;
bool y = false;
bool x = false;
bool l = false;
bool r = false;
bool select = false;
bool start = false;
} gamepad;
}; };
extern Interface* interface; extern Interface* interface;

View File

@ -15,6 +15,7 @@ string Program::path(string name) {
} }
void Program::main() { void Program::main() {
inputManager->poll();
debugger->main(); debugger->main();
} }
@ -27,6 +28,7 @@ Program::Program(string pathname) {
directory::create(userpath); directory::create(userpath);
new Settings; new Settings;
new InputManager;
new Interface; new Interface;
new Debugger; new Debugger;
new Presentation; new Presentation;
@ -51,7 +53,7 @@ Program::Program(string pathname) {
input.driver(settings->input.driver); input.driver(settings->input.driver);
input.set(Input::Handle, presentation->viewport.handle()); input.set(Input::Handle, presentation->viewport.handle());
if(input.init() == false) { input.driver("None"); input.init(); } if(input.init() == false) { input.driver("None"); input.init(); }
input.onChange = {&Interface::inputEvent, interface}; input.onChange = {&Terminal::inputEvent, terminal};
dspaudio.setPrecision(16); dspaudio.setPrecision(16);
dspaudio.setBalance(0.0); dspaudio.setBalance(0.0);
@ -61,15 +63,19 @@ Program::Program(string pathname) {
presentation->showSplash(); presentation->showSplash();
inputManager->load();
interface->load(pathname); interface->load(pathname);
debugger->load(); debugger->load();
terminal->load();
Application::main = {&Program::main, this}; Application::main = {&Program::main, this};
Application::run(); Application::run();
terminal->unload();
debugger->unload(); debugger->unload();
interface->unload(); interface->unload();
settings->save(); inputManager->unload();
settings->unload();
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {

View File

@ -22,6 +22,7 @@ using namespace ruby;
using namespace phoenix; using namespace phoenix;
#include "settings/settings.hpp" #include "settings/settings.hpp"
#include "input/input.hpp"
#include "interface/interface.hpp" #include "interface/interface.hpp"
#include "debugger/debugger.hpp" #include "debugger/debugger.hpp"
#include "presentation/presentation.hpp" #include "presentation/presentation.hpp"

View File

@ -3,9 +3,12 @@ Presentation* presentation = nullptr;
Presentation::Presentation() { Presentation::Presentation() {
presentation = this; presentation = this;
if(settings->geometry.presentation.empty()) {
settings->geometry.presentation = "64,64,512,480";
}
setTitle({"loki v", Emulator::Version}); setTitle({"loki v", Emulator::Version});
setWindowGeometry({0, 0, 512, 480}); setGeometry(settings->geometry.presentation);
setResizable(false); setResizable(false);
layout.append(viewport, {0, 0, 512, 480}); layout.append(viewport, {0, 0, 512, 480});

View File

@ -1,10 +1,11 @@
struct Presentation : Window { struct Presentation : Window {
FixedLayout layout;
Viewport viewport;
nall::image splash;
Presentation(); Presentation();
void showSplash(); void showSplash();
FixedLayout layout;
Viewport viewport;
nall::image splash;
}; };
extern Presentation* presentation; extern Presentation* presentation;

View File

@ -16,15 +16,23 @@ Settings::Settings() {
input.append(input.driver = ruby::input.optimalDriver(), "Driver"); input.append(input.driver = ruby::input.optimalDriver(), "Driver");
append(input, "Input"); append(input, "Input");
geometry.append(geometry.presentation = "", "Presentation");
geometry.append(geometry.terminal = "", "Terminal");
append(geometry, "Geometry");
load(); load();
} }
void Settings::load() { void Settings::load() {
Configuration::Document::load(program->path("settings.bml")); Configuration::Document::load(program->path("settings.bml"));
save(); //create configuration file if it does not exist Configuration::Document::save(program->path("settings.bml"));
} }
void Settings::save() { void Settings::unload() {
//remember window geometry for next run
geometry.presentation = presentation->geometry().text();
geometry.terminal = terminal->geometry().text();
Configuration::Document::save(program->path("settings.bml")); Configuration::Document::save(program->path("settings.bml"));
} }

View File

@ -14,9 +14,15 @@ struct Settings : Configuration::Document {
string driver; string driver;
} input; } input;
struct Geometry : Configuration::Node {
string presentation;
string terminal;
} geometry;
Settings(); Settings();
void load(); void load();
void save(); void unload();
void command(string s, lstring args); void command(string s, lstring args);
}; };

View File

@ -3,9 +3,13 @@ Terminal* terminal = nullptr;
Terminal::Terminal() { Terminal::Terminal() {
terminal = this; terminal = this;
if(settings->geometry.terminal.empty()) {
unsigned y = 64 + presentation->geometry().height + presentation->frameMargin().height;
settings->geometry.terminal = {"64,", y, ",800,480"};
}
setTitle({"loki v", Emulator::Version}); setTitle({"loki v", Emulator::Version});
setWindowGeometry({0, 480 + frameMargin().height, 800, 480}); setGeometry(settings->geometry.terminal);
console.setFont(Font::monospace(8)); console.setFont(Font::monospace(8));
console.setPrompt("$ "); console.setPrompt("$ ");
@ -17,7 +21,52 @@ Terminal::Terminal() {
console.onActivate = {&Terminal::command, this}; console.onActivate = {&Terminal::command, this};
} }
void Terminal::load() {
if(file::exists(program->path("aliases.cfg"))) {
string filedata = file::read(program->path("aliases.cfg"));
lstring lines = filedata.split("\n");
for(auto& line : lines) {
lstring part = line.split<1>(" => ");
if(part.size() != 2) continue;
aliases.append({part[0], part[1]});
}
}
if(file::exists(program->path("hotkeys.cfg"))) {
string filedata = file::read(program->path("hotkeys.cfg"));
lstring lines = filedata.split("\n");
for(auto& line : lines) {
lstring part = line.split<1>(" => ");
if(part.size() != 2) continue;
hotkeys.append({part[0], part[1]});
}
}
}
void Terminal::unload() {
file fp;
if(fp.open(program->path("aliases.cfg"), file::mode::write)) {
for(auto& alias : aliases) fp.print(alias.name, " => ", alias.mapping, "\n");
fp.close();
}
if(fp.open(program->path("hotkeys.cfg"), file::mode::write)) {
for(auto& hotkey : hotkeys) fp.print(hotkey.name, " => ", hotkey.mapping, "\n");
fp.close();
}
}
void Terminal::command(string t) { void Terminal::command(string t) {
for(auto& alias : aliases) {
lstring tokens;
if(tokenize(tokens, t, alias.name) == false) continue;
string output = alias.mapping;
for(unsigned n = 0; n < tokens.size(); n++) {
output.replace(string{"{", 1 + n, "}"}, tokens[n]);
}
t = output;
break;
}
auto source = Debugger::Source::CPU; auto source = Debugger::Source::CPU;
if(t.beginsWith("cpu/" )) { source = Debugger::Source::CPU; t.ltrim<1>("cpu/" ); } if(t.beginsWith("cpu/" )) { source = Debugger::Source::CPU; t.ltrim<1>("cpu/" ); }
if(t.beginsWith("smp/" )) { source = Debugger::Source::SMP; t.ltrim<1>("smp/" ); } if(t.beginsWith("smp/" )) { source = Debugger::Source::SMP; t.ltrim<1>("smp/" ); }
@ -32,14 +81,41 @@ void Terminal::command(string t) {
if(source == Debugger::Source::CPU) t.replace("$", hex(SFC::cpu.regs.pc)); if(source == Debugger::Source::CPU) t.replace("$", hex(SFC::cpu.regs.pc));
if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc)); if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc));
lstring args = t.strip().qsplit(" ").strip(); lstring part = t.strip().split<1>(" "), args;
string s = args.takeFirst(); string s = part(0);
string p = part(1);
if(p) args = p.qsplit(" ").strip();
unsigned argc = args.size(); unsigned argc = args.size();
if(s.empty()) return; if(s.empty()) return;
if(s.beginsWith("settings.")) return settings->command(s, args); if(s.beginsWith("settings.")) return settings->command(s, args);
if(s == "aliases") {
echoAliases();
return;
}
if(s == "aliases.append") {
lstring part = p.qsplit<1>("=>").strip();
if(part.size() == 2) aliases.append({part[0], part[1]});
echoAliases();
return;
}
if(s == "aliases.remove" && argc == 1) {
unsigned n = decimal(args[0]);
if(n < aliases.size()) aliases.remove(n);
echoAliases();
return;
}
if(s == "aliases.reset") {
aliases.reset();
echo("All aliases removed\n");
return;
}
if(s == "break") { if(s == "break") {
debugger->stop(); debugger->stop();
return; return;
@ -74,6 +150,7 @@ void Terminal::command(string t) {
if(s == "breakpoints.reset") { if(s == "breakpoints.reset") {
debugger->breakpoints.reset(); debugger->breakpoints.reset();
echo("All breakpoints removed\n");
return; return;
} }
@ -112,6 +189,37 @@ void Terminal::command(string t) {
return; return;
} }
if(s == "hotkeys") {
echoHotkeys();
return;
}
if(s == "hotkeys.append") {
lstring part = p.qsplit<1>("=>").strip();
if(part.size() == 2) hotkeys.append({part[0], part[1]});
echoHotkeys();
return;
}
if(s == "hotkeys.remove" && argc == 1) {
unsigned n = decimal(args[0]);
if(n < hotkeys.size()) hotkeys.remove(n);
echoHotkeys();
return;
}
if(s == "hotkeys.reset") {
hotkeys.reset();
echo("All hotkeys removed\n");
return;
}
if(s == "power") {
emulator->power();
echo("System has been power cycled\n");
return;
}
if(s == "quit") { if(s == "quit") {
Application::quit(); Application::quit();
return; return;
@ -124,6 +232,12 @@ void Terminal::command(string t) {
return; return;
} }
if(s == "reset") {
emulator->reset();
echo("System has been reset\n");
return;
}
if(s == "run" && argc == 0) { if(s == "run" && argc == 0) {
debugger->run(); debugger->run();
return; return;
@ -143,6 +257,18 @@ void Terminal::command(string t) {
return; return;
} }
if(s == "state.load" && argc == 1) {
string pathname = {interface->pathname, "loki/state-", args[0], ".bst"};
debugger->stateLoad(pathname);
return;
}
if(s == "state.save" && argc == 1) {
string pathname = {interface->pathname, "loki/state-", args[0], ".bst"};
debugger->stateSave(pathname);
return;
}
if(s == "step" && argc == 0) { if(s == "step" && argc == 0) {
debugger->run(); debugger->run();
if(source == Debugger::Source::CPU) debugger->cpuStepFor = 1u; if(source == Debugger::Source::CPU) debugger->cpuStepFor = 1u;
@ -204,6 +330,36 @@ void Terminal::command(string t) {
echo("Error: unrecognized command: ", s, "\n"); echo("Error: unrecognized command: ", s, "\n");
} }
void Terminal::echoAliases() {
if(aliases.size() == 0) return echo("No aliases defined\n");
echo("# alias\n");
echo("--- -----\n");
for(unsigned n = 0; n < aliases.size(); n++) {
echo(format<-3>(n), " ", aliases[n].name, " => ", aliases[n].mapping, "\n");
}
}
void Terminal::echoHotkeys() {
if(hotkeys.size() == 0) return echo("No hotkeys defined\n");
echo("# hotkey\n");
echo("--- ------\n");
for(unsigned n = 0; n < hotkeys.size(); n++) {
echo(format<-3>(n), " ", hotkeys[n].name, " => ", hotkeys[n].mapping, "\n");
}
}
void Terminal::inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue) {
if(focused() == false) return;
if(device.isKeyboard() == false) return; //only capture keyboard events
if(oldValue != 0 || newValue != 1) return; //only capture key down events
string name = device.group[group].input[input].name;
for(auto& hotkey : hotkeys) {
if(name != hotkey.name) continue;
command(hotkey.mapping);
}
}
void Terminal::reset() { void Terminal::reset() {
console.reset(); console.reset();
} }

View File

@ -1,11 +1,30 @@
struct Terminal : Window { struct Terminal : Window {
struct Alias {
string name;
string mapping;
};
struct Hotkey {
string name;
string mapping;
};
Terminal();
void load();
void unload();
void command(string s);
void echoAliases();
void echoHotkeys();
void inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue);
void reset();
void print(const string& text);
VerticalLayout layout; VerticalLayout layout;
Console console; Console console;
Terminal(); vector<Alias> aliases;
void command(string s); vector<Hotkey> hotkeys;
void reset();
void print(const string& text);
}; };
extern Terminal* terminal; extern Terminal* terminal;