diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 2582fcda..96a9a47d 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "094.10"; + static const char Version[] = "094.11"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/emulator/interface.hpp b/emulator/interface.hpp index b46fdfe4..7c1ca515 100644 --- a/emulator/interface.hpp +++ b/emulator/interface.hpp @@ -33,7 +33,7 @@ struct Interface { unsigned id; unsigned type; //0 = digital, 1 = analog (relative), 2 = rumble string name; - unsigned guid; + uintptr_t guid; }; vector input; vector order; diff --git a/hiro/gtk/application.cpp b/hiro/gtk/application.cpp index 14783989..1cd3dfa9 100644 --- a/hiro/gtk/application.cpp +++ b/hiro/gtk/application.cpp @@ -37,14 +37,14 @@ void pApplication::initialize() { #if 1 int argc = 1; - char* argv[] = {new char[8], nullptr}; - strcpy(argv[0], "phoenix"); + char* argv[] = {new char[5], nullptr}; + strcpy(argv[0], "hiro"); #else //--g-fatal-warnings will force a trap on Gtk-CRITICAL errors - //this allows gdb to perform a backtrace to find error origin point + //this allows gdb to perform a backtrace to find an error's origin point int argc = 2; - char* argv[] = {new char[8], new char[19], nullptr}; - strcpy(argv[0], "phoenix"); + char* argv[] = {new char[5], new char[19], nullptr}; + strcpy(argv[0], "hiro"); strcpy(argv[1], "--g-fatal-warnings"); #endif char** argvp = argv; @@ -54,20 +54,20 @@ void pApplication::initialize() { g_object_set(gtkSettings, "gtk-button-images", true, nullptr); gtk_rc_parse_string(R"( - style "PhoenixWindow" + style "HiroWindow" { GtkWindow::resize-grip-width = 0 GtkWindow::resize-grip-height = 0 } - class "GtkWindow" style "PhoenixWindow" + class "GtkWindow" style "HiroWindow" - style "PhoenixTreeView" + style "HiroTreeView" { GtkTreeView::vertical-separator = 0 } - class "GtkTreeView" style "PhoenixTreeView" + class "GtkTreeView" style "HiroTreeView" - style "PhoenixTabFrameCloseButton" + style "HiroTabFrameCloseButton" { GtkWidget::focus-line-width = 0 GtkWidget::focus-padding = 0 @@ -75,7 +75,7 @@ void pApplication::initialize() { GtkButton::default-outer-border = {0, 0, 0, 0} GtkButton::inner-border = {0, 1, 0, 0} } - widget_class "*..." style "PhoenixTabFrameCloseButton" + widget_class "*..." style "HiroTabFrameCloseButton" )"); pKeyboard::initialize(); diff --git a/hiro/gtk/widget/tab-frame.cpp b/hiro/gtk/widget/tab-frame.cpp index 0ffa1243..757c771c 100644 --- a/hiro/gtk/widget/tab-frame.cpp +++ b/hiro/gtk/widget/tab-frame.cpp @@ -91,12 +91,23 @@ auto pTabFrame::append(sTabFrameItem item) -> void { } auto pTabFrame::container(mWidget& widget) -> GtkWidget* { - auto widgetLayout = widget.parentLayout(); + //TabFrame holds multiple TabFrameItem controls + //each TabFrameItem has its own GtkWindow; plus its own layout + //we need to recurse up from the widget to its topmost layout before the TabFrameItem + //once we know the topmost layout, we search through all TabFrameItems for a match + mObject* object = &widget; + while(object) { + if(object->parentTabFrameItem()) break; + if(auto layout = object->parentLayout()) { object = layout; continue; } + break; + } + unsigned position = 0; for(auto& item : state().items) { - if(item->state.layout.data() == widgetLayout) return tabs[position].child; + if(item->state.layout.data() == object) return tabs[position].child; position++; } + return nullptr; } diff --git a/hiro/gtk/window.cpp b/hiro/gtk/window.cpp index 11de8a97..443be7da 100644 --- a/hiro/gtk/window.cpp +++ b/hiro/gtk/window.cpp @@ -326,7 +326,7 @@ auto pWindow::setVisible(bool visible) -> void { auto pWindow::_append(mWidget& widget) -> void { if(!widget.self()) return; if(auto parent = widget.parentWidget(true)) { - if(parent->self()) widget.self()->gtkParent = parent->self()->container(widget); + if(auto instance = parent->self()) widget.self()->gtkParent = instance->container(widget); } else { widget.self()->gtkParent = formContainer; } diff --git a/target-tomoko/GNUmakefile b/target-tomoko/GNUmakefile index 48bdf8ff..4348863b 100644 --- a/target-tomoko/GNUmakefile +++ b/target-tomoko/GNUmakefile @@ -8,14 +8,23 @@ include sfc/GNUmakefile include gb/GNUmakefile include gba/GNUmakefile -ui_objects := ui-tomoko ui-program -ui_objects += ui-library ui-presentation +ui_objects := ui-tomoko ui-program ui-input +ui_objects += ui-library ui-settings ui-presentation ui_objects += ruby hiro # platform ifeq ($(platform),windows) + ruby := video.direct3d video.wgl video.directdraw video.gdi + ruby += audio.xaudio2 audio.directsound + ruby += input.windows else ifeq ($(platform),macosx) + ruby := video.cgl + ruby += audio.openal + ruby += input.carbon else ifeq ($(platform),linux) + ruby := video.glx video.xv video.xshm video.sdl + ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.udev input.sdl input.xlib else ifeq ($(platform),bsd) ruby := video.glx video.xshm ruby += audio.openal audio.oss @@ -42,7 +51,9 @@ obj/hiro.o: hiro/hiro.cpp $(call rwildcard,hiro/) obj/ui-tomoko.o: $(ui)/tomoko.cpp $(call rwildcard,$(ui)/) obj/ui-program.o: $(ui)/program/program.cpp $(call rwildcard,$(ui)/) +obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/) obj/ui-library.o: $(ui)/library/library.cpp $(call rwildcard,$(ui)/) +obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/) # targets diff --git a/target-tomoko/input/input.cpp b/target-tomoko/input/input.cpp new file mode 100644 index 00000000..37ae7580 --- /dev/null +++ b/target-tomoko/input/input.cpp @@ -0,0 +1,119 @@ +#include "../tomoko.hpp" +InputManager* inputManager = nullptr; + +auto InputMapping::bind() -> void { + auto token = assignment.split("/"); + if(token.size() < 3) return; + uint64_t id = token[0].hex(); + unsigned group = token[1].decimal(); + unsigned input = token[2].decimal(); + + for(auto& device : inputManager->devices) { + if(id != device->id) continue; + + this->device = device; + this->group = group; + this->input = input; + break; + } +} + +auto InputMapping::bind(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> bool { + this->assignment = {hex(device.id), "/", group, "/", input, "/", device.group[group].input[input].name}; + this->device = &device; + this->group = group; + this->input = input; + return true; +} + +auto InputMapping::poll() -> int16 { + if(device) return device->group[group].input[input].value; + return 0; +} + +// + +InputManager::InputManager() { + inputManager = this; + input.onChange = {&InputManager::onChange, this}; + + for(auto& emulator : program->emulators) { + Configuration::Node nodeEmulator; + + emulators.append(InputEmulator()); + auto& inputEmulator = emulators.last(); + inputEmulator.name = emulator->information.name; + + for(auto& port : emulator->port) { + Configuration::Node nodePort; + + inputEmulator.ports.append(InputPort()); + auto& inputPort = inputEmulator.ports.last(); + inputPort.name = port.name; + for(auto& device : port.device) { + Configuration::Node nodeDevice; + + inputPort.devices.append(InputDevice()); + auto& inputDevice = inputPort.devices.last(); + inputDevice.name = device.name; + for(auto number : device.order) { + auto& input = device.input[number]; + inputDevice.mappings.append(new InputMapping()); + auto& inputMapping = inputDevice.mappings.last(); + inputMapping->name = input.name; + inputMapping->link = &input; + input.guid = (uintptr_t)inputMapping; + + nodeDevice.append(inputMapping->assignment, inputMapping->name); + } + + nodePort.append(nodeDevice, string{inputDevice.name}.replace(" ", "")); + } + + nodeEmulator.append(nodePort, string{inputPort.name}.replace(" ", "")); + } + + config.append(nodeEmulator, string{inputEmulator.name}.replace(" ", "")); + } + + config.load({configpath(), "tomoko/settings.bml"}); + config.save({configpath(), "tomoko/settings.bml"}); + poll(); //will call bind(); +} + +auto InputManager::bind() -> void { + for(auto& emulator : emulators) { + for(auto& port : emulator.ports) { + for(auto& device : port.devices) { + for(auto& mapping : device.mappings) { + mapping->bind(); + } + } + } + } +} + +auto InputManager::poll() -> void { + auto devices = input.poll(); + bool changed = devices.size() != this->devices.size(); + if(changed == false) { + for(auto n : range(devices)) { + changed = devices[n] != this->devices[n]; + if(changed) break; + } + } + if(changed == true) { + this->devices = devices; + bind(); + } +} + +auto InputManager::onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void { + if(settingsManager->focused()) { + settingsManager->input.inputEvent(device, group, input, oldValue, newValue); + } +} + +auto InputManager::quit() -> void { + config.save({configpath(), "tomoko/settings.bml"}); +} diff --git a/target-tomoko/input/input.hpp b/target-tomoko/input/input.hpp new file mode 100644 index 00000000..83e528d3 --- /dev/null +++ b/target-tomoko/input/input.hpp @@ -0,0 +1,41 @@ +struct InputMapping { + auto bind() -> void; + auto bind(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> bool; + auto poll() -> int16; + + string name; + string assignment = "None"; + Emulator::Interface::Device::Input* link = nullptr; + HID::Device* device = nullptr; + unsigned group = 0; + unsigned input = 0; +}; + +struct InputDevice { + string name; + vector mappings; //pointers used so that addresses do not change when arrays are resized +}; + +struct InputPort { + string name; + vector devices; +}; + +struct InputEmulator { + string name; + vector ports; +}; + +struct InputManager { + InputManager(); + auto bind() -> void; + auto poll() -> void; + auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void; + auto quit() -> void; + + vector devices; + vector emulators; + Configuration::Document config; +}; + +extern InputManager* inputManager; diff --git a/target-tomoko/presentation/presentation.cpp b/target-tomoko/presentation/presentation.cpp index a5b172d6..9e67f1b2 100644 --- a/target-tomoko/presentation/presentation.cpp +++ b/target-tomoko/presentation/presentation.cpp @@ -16,18 +16,25 @@ Presentation::Presentation() { } } - superFamicomMenu.setText("Super Famicom"); + systemMenu.setVisible(false); + powerSystem.setText("Power"); + resetSystem.setText("Reset"); + unloadSystem.setText("Unload").onActivate([&] { program->unloadMedia(); }); settingsMenu.setText("Settings"); + showConfiguration.setText("Configuration ...").onActivate([&] { + settingsManager->setVisible(); + settingsManager->setFocused(); + }); toolsMenu.setText("Tools"); statusBar.setFont(Font::sans(8, "Bold")); - onClose(&Application::quit); + onClose([&] { program->quit(); }); setTitle({"tomoko v", Emulator::Version}); - setResizable(false); - setSize({640, 480}); +//setResizable(false); + setSize({512, 480}); setCentered(); } diff --git a/target-tomoko/presentation/presentation.hpp b/target-tomoko/presentation/presentation.hpp index dfccbf18..98063196 100644 --- a/target-tomoko/presentation/presentation.hpp +++ b/target-tomoko/presentation/presentation.hpp @@ -4,8 +4,13 @@ struct Presentation : Window { MenuBar menuBar{this}; Menu libraryMenu{&menuBar}; vector loadBootableMedia; - Menu superFamicomMenu{&menuBar}; + Menu systemMenu{&menuBar}; + MenuItem powerSystem{&systemMenu}; + MenuItem resetSystem{&systemMenu}; + MenuSeparator systemMenuSeparator{&systemMenu}; + MenuItem unloadSystem{&systemMenu}; Menu settingsMenu{&menuBar}; + MenuItem showConfiguration{&settingsMenu}; Menu toolsMenu{&menuBar}; VerticalLayout layout{this}; diff --git a/target-tomoko/program/interface.cpp b/target-tomoko/program/interface.cpp index 8fa24a62..cf839e4d 100644 --- a/target-tomoko/program/interface.cpp +++ b/target-tomoko/program/interface.cpp @@ -10,7 +10,11 @@ auto Program::loadRequest(unsigned id, string path) -> void { return emulator().load(id, stream); } +//request from emulation core to save non-volatile media file auto Program::saveRequest(unsigned id, string path) -> void { + string location = {mediaPaths(emulator().group(id)), path}; + filestream stream{location, file::mode::write}; + return emulator().save(id, stream); } auto Program::videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 { @@ -62,6 +66,10 @@ auto Program::audioSample(int16 lsample, int16 rsample) -> void { } auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16 { + auto guid = emulator().port[port].device[device].input[input].guid; + auto mapping = (InputMapping*)guid; + if(mapping) return mapping->poll(); + return 0; } auto Program::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) -> void { diff --git a/target-tomoko/program/media.cpp b/target-tomoko/program/media.cpp index 1c83370d..9fa9f575 100644 --- a/target-tomoko/program/media.cpp +++ b/target-tomoko/program/media.cpp @@ -13,13 +13,27 @@ auto Program::loadMedia(string location) -> void { } auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Media& media, const string& location) -> void { + unloadMedia(); + mediaPaths(0) = {userpath(), "Emulation/System/", media.name, ".sys/"}; mediaPaths(media.id) = location; - setEmulator(_emulator); + setEmulator(&_emulator); emulator().paletteUpdate(Emulator::Interface::PaletteMode::Standard); emulator().load(media.id); emulator().power(); presentation->setTitle(emulator().title()); + presentation->systemMenu.setText(emulator().information.name).setVisible(true); +} + +auto Program::unloadMedia() -> void { + if(activeEmulator == nullptr) return; + emulator().unload(); + + setEmulator(nullptr); + + presentation->setTitle({"tomoko v", Emulator::Version}); + presentation->systemMenu.setVisible(false); + drawSplashScreen(); } diff --git a/target-tomoko/program/program.cpp b/target-tomoko/program/program.cpp index 6a6d38e6..8543bb9c 100644 --- a/target-tomoko/program/program.cpp +++ b/target-tomoko/program/program.cpp @@ -9,6 +9,7 @@ Program* program = nullptr; Program::Program() { program = this; + directory::create({configpath(), "tomoko/"}); Application::onMain({&Program::main, this}); emulators.append(new Famicom::Interface); @@ -17,7 +18,9 @@ Program::Program() { emulators.append(new GameBoyAdvance::Interface); for(auto& emulator : emulators) emulator->bind = this; + new InputManager; new LibraryManager; + new SettingsManager; new Presentation; presentation->setVisible(); @@ -34,7 +37,7 @@ Program::Program() { audio.set(Audio::Latency, 80u); if(!audio.init()) { audio.driver("None"); audio.init(); } - input.driver("XInput"); + input.driver("Xlib"); input.set(Input::Handle, presentation->viewport.handle()); if(!input.init()) { input.driver("None"); input.init(); } @@ -45,19 +48,7 @@ Program::Program() { dsp.setResampler(DSP::ResampleEngine::Sinc); dsp.setResamplerFrequency(96000); - uint32* output; - unsigned length; - if(video.lock(output, length, 640, 480)) { - for(auto y : range(480)) { - uint32* dp = output + y * (length >> 2); - for(auto x : range(640)) { - *dp++ = 0xff401010; - } - } - - video.unlock(); - video.refresh(); - } + drawSplashScreen(); } auto Program::emulator() -> Emulator::Interface& { @@ -66,6 +57,8 @@ auto Program::emulator() -> Emulator::Interface& { } auto Program::main() -> void { + inputManager->poll(); + if(activeEmulator == nullptr || emulator().loaded() == false) { audio.clear(); usleep(20 * 1000); @@ -75,6 +68,37 @@ auto Program::main() -> void { emulator().run(); } -auto Program::setEmulator(Emulator::Interface& emulator) -> void { - activeEmulator = &emulator; +auto Program::quit() -> void { + unloadMedia(); + inputManager->quit(); + exit(0); +} + +auto Program::setEmulator(Emulator::Interface* emulator) -> void { + activeEmulator = emulator; +} + +auto Program::drawSplashScreen() -> void { + image emblem{string{userpath(), ".local/share/icons/tomoko.png"}}; + emblem.alphaBlend(0x000000); + emblem.scale(128, 128); + + uint32* output; + unsigned length; + if(video.lock(output, length, 512, 480)) { + for(auto y : range(480)) { + uint32* dp = output + y * (length >> 2); + for(auto x : range(640)) *dp++ = 0xff000000; + } + unsigned z = 0; + for(auto y : range(emblem.height)) { + uint32* dp = output + (480 - emblem.height + y - 8) * (length >> 2) + (512 - emblem.width - 8); + for(auto x : range(emblem.width)) { + *dp++ = emblem.read(emblem.data + z); + z += 4; + } + } + video.unlock(); + video.refresh(); + } } diff --git a/target-tomoko/program/program.hpp b/target-tomoko/program/program.hpp index d3706073..04cfd465 100644 --- a/target-tomoko/program/program.hpp +++ b/target-tomoko/program/program.hpp @@ -3,7 +3,9 @@ struct Program : Emulator::Interface::Bind { Program(); auto emulator() -> Emulator::Interface&; auto main() -> void; - auto setEmulator(Emulator::Interface&) -> void; + auto quit() -> void; + auto setEmulator(Emulator::Interface*) -> void; + auto drawSplashScreen() -> void; //interface.cpp auto loadRequest(unsigned id, string name, string type) -> void override; @@ -21,6 +23,7 @@ struct Program : Emulator::Interface::Bind { //media.cpp auto loadMedia(string location) -> void; auto loadMedia(Emulator::Interface& interface, Emulator::Interface::Media& media, const string& location) -> void; + auto unloadMedia() -> void; DSP dsp; diff --git a/target-tomoko/settings/input.cpp b/target-tomoko/settings/input.cpp new file mode 100644 index 00000000..1db9df71 --- /dev/null +++ b/target-tomoko/settings/input.cpp @@ -0,0 +1,85 @@ +InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Device::Joypad); + setText("Input"); + + layout.setMargin(5); + for(auto& emulator : inputManager->emulators) { + emulatorList.append(ComboButtonItem().setText(emulator.name)); + } + emulatorList.onChange([&] { reloadPorts(); }); + portList.onChange([&] { reloadDevices(); }); + deviceList.onChange([&] { reloadMappings(); }); + mappingList.onActivate([&] { assignMapping(); }); + mappingList.setHeaderVisible(); + resetButton.setText("Reset"); + eraseButton.setText("Erase"); + reloadPorts(); +} + +auto InputSettings::activeEmulator() -> InputEmulator& { + return inputManager->emulators[emulatorList.selected()->offset()]; +} + +auto InputSettings::activePort() -> InputPort& { + return activeEmulator().ports[portList.selected()->offset()]; +} + +auto InputSettings::activeDevice() -> InputDevice& { + return activePort().devices[deviceList.selected()->offset()]; +} + +auto InputSettings::reloadPorts() -> void { + portList.reset(); + for(auto& port : activeEmulator().ports) { + portList.append(ComboButtonItem().setText(port.name)); + } + reloadDevices(); +} + +auto InputSettings::reloadDevices() -> void { + deviceList.reset(); + for(auto& device : activePort().devices) { + deviceList.append(ComboButtonItem().setText(device.name)); + } + reloadMappings(); +} + +auto InputSettings::reloadMappings() -> void { + mappingList.reset(); + mappingList.append(ListViewColumn().setText("Name")); + mappingList.append(ListViewColumn().setText("Mapping")); + for(auto& mapping : activeDevice().mappings) { + mappingList.append(ListViewItem().setText(0, mapping->name)); + } + refreshMappings(); +} + +auto InputSettings::refreshMappings() -> void { + unsigned position = 0; + for(auto& mapping : activeDevice().mappings) { + mappingList.item(position++)->setText(1, mapping->assignment); + } + mappingList.resizeColumns(); +} + +auto InputSettings::assignMapping() -> void { + inputManager->poll(); //clear any pending events first + + auto item = mappingList.selected(); + activeMapping = activeDevice().mappings[item->offset()]; + +//settingsManager->layout.setEnabled(false); + settingsManager->statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); +} + +auto InputSettings::inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void { + if(!activeMapping) return; + if(!device.isKeyboard() || oldValue != 0 || newValue != 1) return; + + if(activeMapping->bind(device, group, input, oldValue, newValue)) { + activeMapping = nullptr; + settingsManager->statusBar.setText(""); + //settingsManager->layout.setEnabled(true); //todo: this isn't enabling child widgets properly (bug in hiro) + refreshMappings(); + } +} diff --git a/target-tomoko/settings/settings.cpp b/target-tomoko/settings/settings.cpp new file mode 100644 index 00000000..8541e638 --- /dev/null +++ b/target-tomoko/settings/settings.cpp @@ -0,0 +1,14 @@ +#include "../tomoko.hpp" +#include "input.cpp" +SettingsManager* settingsManager = nullptr; + +SettingsManager::SettingsManager() { + settingsManager = this; + + layout.setMargin(5); + statusBar.setFont(Font::sans(8, "Bold")); + + setTitle("Configuration Settings"); + setPosition({0, 0}); + setSize({600, 400}); +} diff --git a/target-tomoko/settings/settings.hpp b/target-tomoko/settings/settings.hpp new file mode 100644 index 00000000..7bcc4fd2 --- /dev/null +++ b/target-tomoko/settings/settings.hpp @@ -0,0 +1,37 @@ +struct InputSettings : TabFrameItem { + InputSettings(TabFrame*); + auto activeEmulator() -> InputEmulator&; + auto activePort() -> InputPort&; + auto activeDevice() -> InputDevice&; + auto reloadPorts() -> void; + auto reloadDevices() -> void; + auto reloadMappings() -> void; + auto refreshMappings() -> void; + auto assignMapping() -> void; + auto inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void; + + InputMapping* activeMapping = nullptr; + + VerticalLayout layout{this}; + HorizontalLayout selectionLayout{&layout, Size{~0, 0}}; + ComboButton emulatorList{&selectionLayout, Size{~0, 0}}; + ComboButton portList{&selectionLayout, Size{~0, 0}}; + ComboButton deviceList{&selectionLayout, Size{~0, 0}}; + ListView mappingList{&layout, Size{~0, ~0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button resetButton{&controlLayout, Size{80, 0}}; + Button eraseButton{&controlLayout, Size{80, 0}}; +}; + +struct SettingsManager : Window { + SettingsManager(); + + VerticalLayout layout{this}; + TabFrame panelLayout{&layout, Size{~0, ~0}}; + InputSettings input{&panelLayout}; + + StatusBar statusBar{this}; +}; + +extern SettingsManager* settingsManager; diff --git a/target-tomoko/tomoko.hpp b/target-tomoko/tomoko.hpp index ad45ac1e..e681a276 100644 --- a/target-tomoko/tomoko.hpp +++ b/target-tomoko/tomoko.hpp @@ -8,5 +8,7 @@ using namespace ruby; using namespace hiro; #include "program/program.hpp" +#include "input/input.hpp" #include "library/library.hpp" +#include "settings/settings.hpp" #include "presentation/presentation.hpp"