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