Update to v094r11 release.

byuu says:

I've hooked up the input subsystem, and the input manager to assign
hotkeys.

So far I only have digital buttons working (keyboard only), and I'm not
planning on supporting input groups again (mapping multiple physical
buttons to one emulated button), but it's progress. As with the rest of
tomoko, the code's a lot more compact. The nice thing about redoing code
so many times is that each time you get a little bit better at it.

The input configuration is saved to ~/.config/tomoko/settings.bml (just
realized that I'm an idiot and need to rename it to input.bml)

Also hooked up game saves and cartridge unloading. Active controller
changing isn't hooked up yet, and I'll probably do it differently.

Oh, and I declared the ruby lines for other platforms.

Still need to add Cydrak's Windows compilation fixes. I am nothing if
not lazy :P
This commit is contained in:
Tim Allen 2015-03-02 20:13:28 +11:00
parent 80c1c9c2ef
commit 4a069761f9
18 changed files with 422 additions and 41 deletions

View File

@ -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/";

View File

@ -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> input;
vector<unsigned> order;

View File

@ -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 "*.<GtkNotebook>.<GtkHBox>.<GtkButton>" style "PhoenixTabFrameCloseButton"
widget_class "*.<GtkNotebook>.<GtkHBox>.<GtkButton>" style "HiroTabFrameCloseButton"
)");
pKeyboard::initialize();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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"});
}

View File

@ -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<InputMapping*> mappings; //pointers used so that addresses do not change when arrays are resized
};
struct InputPort {
string name;
vector<InputDevice> devices;
};
struct InputEmulator {
string name;
vector<InputPort> 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<HID::Device*> devices;
vector<InputEmulator> emulators;
Configuration::Document config;
};
extern InputManager* inputManager;

View File

@ -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();
}

View File

@ -4,8 +4,13 @@ struct Presentation : Window {
MenuBar menuBar{this};
Menu libraryMenu{&menuBar};
vector<MenuItem*> 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};

View File

@ -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 {

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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});
}

View File

@ -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;

View File

@ -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"