Update to v094r19 release.

byuu says:

The input port menu was hooked up.

Alternate input support was added, although I wasn't able to test rumble
support because SDL doesn't support that, and I don't have XInput or
udev drivers on FreeBSD. This one's going to be tricky. Maybe I can test
via cross-compiling on Windows/GTK.

Added mouse capture hotkey, and auto capture/release on toggling
fullscreen (as a bonus it hides the mouse cursor.)

Added all possible video and input drivers to ruby for BSD systems.

Remaining issues before we can release v095:
- add slotted cart loader (SGB, BSX, ST)
- add DIP switch selection window (NSS)
- add timing configuration (video/audio sync)
- hide inapplicable options from system menu (eg controller ports and
  reset button from handheld systems)
This commit is contained in:
Tim Allen 2015-05-23 15:29:18 +10:00
parent fc8eba133d
commit 458775a481
14 changed files with 215 additions and 30 deletions

View File

@ -49,8 +49,6 @@ else ifeq ($(platform),linux)
link += -lX11 -lXext -ldl link += -lX11 -lXext -ldl
else ifeq ($(platform),bsd) else ifeq ($(platform),bsd)
flags += -march=native flags += -march=native
link += -Wl,-rpath=/usr/local/lib
link += -Wl,-rpath=/usr/local/lib/gcc49
link += -Wl,-export-dynamic link += -Wl,-export-dynamic
link += -lX11 -lXext link += -lX11 -lXext
else else

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.18"; static const char Version[] = "094.19";
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

@ -73,6 +73,8 @@ endif
# bsd settings # bsd settings
ifeq ($(platform),bsd) ifeq ($(platform),bsd)
flags += -I/usr/local/include flags += -I/usr/local/include
link += -Wl,-rpath=/usr/local/lib
link += -Wl,-rpath=/usr/local/lib/gcc49
endif endif
# cross-compilation support # cross-compilation support

View File

@ -26,9 +26,9 @@ else ifeq ($(platform),linux)
ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
ruby += input.udev input.sdl input.xlib ruby += input.udev input.sdl input.xlib
else ifeq ($(platform),bsd) else ifeq ($(platform),bsd)
ruby := video.glx video.xshm ruby := video.glx video.xv video.xshm video.sdl
ruby += audio.openal audio.oss ruby += audio.openal audio.oss
ruby += input.xlib ruby += input.sdl input.xlib
endif endif
# ruby # ruby

View File

@ -7,6 +7,14 @@ auto InputManager::appendHotkeys() -> void {
hotkeys.append(hotkey); hotkeys.append(hotkey);
} }
{ auto hotkey = new InputHotkey;
hotkey->name = "Toggle Mouse Capture";
hotkey->action = [] {
input.acquired() ? input.unacquire() : input.acquire();
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey; { auto hotkey = new InputHotkey;
hotkey->name = "Save State"; hotkey->name = "Save State";
hotkey->action = [] { hotkey->action = [] {

View File

@ -4,10 +4,11 @@ InputManager* inputManager = nullptr;
auto InputMapping::bind() -> void { auto InputMapping::bind() -> void {
auto token = assignment.split("/"); auto token = assignment.split("/");
if(token.size() < 3) return; if(token.size() < 3) return unbind();
uint64_t id = token[0].hex(); uint64_t id = token[0].hex();
unsigned group = token[1].decimal(); unsigned group = token[1].decimal();
unsigned input = token[2].decimal(); unsigned input = token[2].decimal();
string qualifier = token(3, "None");
for(auto& device : inputManager->devices) { for(auto& device : inputManager->devices) {
if(id != device->id) continue; if(id != device->id) continue;
@ -15,22 +16,89 @@ auto InputMapping::bind() -> void {
this->device = device; this->device = device;
this->group = group; this->group = group;
this->input = input; this->input = input;
this->qualifier = Qualifier::None;
if(qualifier == "Lo") this->qualifier = Qualifier::Lo;
if(qualifier == "Hi") this->qualifier = Qualifier::Hi;
if(qualifier == "Rumble") this->qualifier = Qualifier::Rumble;
break; break;
} }
} }
auto InputMapping::bind(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> bool { auto InputMapping::bind(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> bool {
if(device.group[group].input[input].name == "Escape") return unbind(), true; if(device.isNull() || (device.isKeyboard() && device.group[group].input[input].name == "Escape")) {
return unbind(), true;
}
this->assignment = {hex(device.id), "/", group, "/", input, "/", device.group[group].input[input].name}; string encoding = {hex(device.id), "/", group, "/", input};
this->device = &device;
this->group = group; if(isDigital()) {
this->input = input; if((device.isKeyboard() && group == HID::Keyboard::GroupID::Button)
return true; || (device.isMouse() && group == HID::Mouse::GroupID::Button)
|| (device.isJoypad() && group == HID::Joypad::GroupID::Button)) {
if(newValue) {
this->assignment = encoding;
return bind(), true;
}
}
if((device.isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device.isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(newValue < -16384) {
this->assignment = {encoding, "/Lo"};
return bind(), true;
}
if(newValue > +16384) {
this->assignment = {encoding, "/Hi"};
return bind(), true;
}
}
}
if(isAnalog()) {
if((device.isMouse() && group == HID::Mouse::GroupID::Axis)
|| (device.isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device.isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(newValue < -16384 || newValue > +16384) {
this->assignment = encoding;
return bind(), true;
}
}
}
if(isRumble()) {
if(device.isJoypad() && group == HID::Joypad::GroupID::Button) {
if(newValue) {
encoding = {this->assignment, "/Rumble"};
return bind(), true;
}
}
}
return false;
} }
auto InputMapping::poll() -> int16 { auto InputMapping::poll() -> int16 {
if(device) return device->group[group].input[input].value; if(!device) return 0;
auto value = device->group[group].input[input].value;
if(isDigital()) {
if(device->isKeyboard() && group == HID::Keyboard::GroupID::Button) return value != 0;
if(device->isMouse() && group == HID::Mouse::GroupID::Button) return value != 0;
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) return value != 0;
if((device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(qualifier == Qualifier::Lo) return value < -16384;
if(qualifier == Qualifier::Hi) return value > +16384;
}
}
if(isAnalog()) {
if(device->isMouse() && group == HID::Mouse::GroupID::Axis) return value;
if(device->isJoypad() && group == HID::Joypad::GroupID::Axis) return value >> 8;
if(device->isJoypad() && group == HID::Joypad::GroupID::Hat) return value < 0 ? -1 : value > 0 ? +1 : 0;
}
return 0; return 0;
} }
@ -39,6 +107,24 @@ auto InputMapping::unbind() -> void {
this->device = nullptr; this->device = nullptr;
this->group = 0; this->group = 0;
this->input = 0; this->input = 0;
this->qualifier = Qualifier::None;
}
auto InputMapping::assignmentName() const -> string {
if(!device) return "None";
string path;
path.append(device->name);
path.append(".", device->group[group].name);
path.append(".", device->group[group].input[input].name);
if(qualifier == Qualifier::Lo) path.append(".Lo");
if(qualifier == Qualifier::Hi) path.append(".Hi");
if(qualifier == Qualifier::Rumble) path.append(".Rumble");
return path;
}
auto InputMapping::deviceName() const -> string {
if(!device) return "";
return device->id;
} }
// //
@ -137,3 +223,10 @@ auto InputManager::quit() -> void {
emulators.reset(); emulators.reset();
hotkeys.reset(); hotkeys.reset();
} }
auto InputManager::findMouse() -> HID::Device* {
for(auto device : devices) {
if(device->isMouse()) return device;
}
return nullptr;
}

View File

@ -4,12 +4,20 @@ struct InputMapping {
auto poll() -> int16; auto poll() -> int16;
auto unbind() -> void; auto unbind() -> void;
auto isDigital() const -> bool { return !link || link->type == 0; }
auto isAnalog() const -> bool { return link && link->type == 1; }
auto isRumble() const -> bool { return link && link->type == 2; }
auto assignmentName() const -> string;
auto deviceName() const -> string;
string name; string name;
string assignment = "None"; string assignment = "None";
Emulator::Interface::Device::Input* link = nullptr; Emulator::Interface::Device::Input* link = nullptr;
HID::Device* device = nullptr; HID::Device* device = nullptr;
unsigned group = 0; unsigned group = 0;
unsigned input = 0; unsigned input = 0;
enum class Qualifier : unsigned { None, Lo, Hi, Rumble } qualifier = Qualifier::None;
}; };
struct InputHotkey : InputMapping { struct InputHotkey : InputMapping {
@ -40,6 +48,8 @@ struct InputManager {
auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void; auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void;
auto quit() -> void; auto quit() -> void;
auto findMouse() -> HID::Device*;
//hotkeys.cpp //hotkeys.cpp
auto appendHotkeys() -> void; auto appendHotkeys() -> void;
auto pollHotkeys() -> void; auto pollHotkeys() -> void;

View File

@ -102,6 +102,29 @@ Presentation::Presentation() {
resizeViewport(); resizeViewport();
} }
auto Presentation::updateEmulator() -> void {
inputPort1.reset();
inputPort2.reset();
if(!emulator) return;
for(auto n : range(emulator->port)) {
if(n >= 2) break;
auto& port = emulator->port[n];
auto& menu = (n == 0 ? inputPort1 : inputPort2);
menu.setText(port.name);
vector<wMenuRadioItem> items;
for(auto& device : port.device) {
MenuRadioItem item{&menu};
item.setText(device.name).onActivate([=] {
emulator->connect(port.id, device.id);
});
items.append(item);
}
MenuRadioItem::group(items);
}
}
auto Presentation::resizeViewport() -> void { auto Presentation::resizeViewport() -> void {
signed width = 256; signed width = 256;
signed height = 240; signed height = 240;
@ -154,7 +177,9 @@ auto Presentation::toggleFullScreen() -> void {
statusBar.setVisible(false); statusBar.setVisible(false);
setResizable(true); setResizable(true);
setFullScreen(true); setFullScreen(true);
if(!input.acquired()) input.acquire();
} else { } else {
if(input.acquired()) input.unacquire();
setFullScreen(false); setFullScreen(false);
setResizable(false); setResizable(false);
menuBar.setVisible(true); menuBar.setVisible(true);

View File

@ -1,5 +1,6 @@
struct Presentation : Window { struct Presentation : Window {
Presentation(); Presentation();
auto updateEmulator() -> void;
auto resizeViewport() -> void; auto resizeViewport() -> void;
auto toggleFullScreen() -> void; auto toggleFullScreen() -> void;
auto drawSplashScreen() -> void; auto drawSplashScreen() -> void;
@ -10,7 +11,10 @@ struct Presentation : Window {
Menu systemMenu{&menuBar}; Menu systemMenu{&menuBar};
MenuItem powerSystem{&systemMenu}; MenuItem powerSystem{&systemMenu};
MenuItem resetSystem{&systemMenu}; MenuItem resetSystem{&systemMenu};
MenuSeparator systemMenuSeparator{&systemMenu}; MenuSeparator systemMenuSeparatorPorts{&systemMenu};
Menu inputPort1{&systemMenu};
Menu inputPort2{&systemMenu};
MenuSeparator systemMenuSeparatorUnload{&systemMenu};
MenuItem unloadSystem{&systemMenu}; MenuItem unloadSystem{&systemMenu};
Menu settingsMenu{&menuBar}; Menu settingsMenu{&menuBar};
Menu videoScaleMenu{&settingsMenu}; Menu videoScaleMenu{&settingsMenu};

View File

@ -29,6 +29,7 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
presentation->setTitle(emulator->title()); presentation->setTitle(emulator->title());
presentation->systemMenu.setVisible(true); presentation->systemMenu.setVisible(true);
presentation->toolsMenu.setVisible(true); presentation->toolsMenu.setVisible(true);
presentation->updateEmulator();
toolsManager->cheatEditor.loadCheats(); toolsManager->cheatEditor.loadCheats();
toolsManager->stateManager.doRefresh(); toolsManager->stateManager.doRefresh();
} }

View File

@ -33,10 +33,7 @@ auto HotkeySettings::reloadMappings() -> void {
auto HotkeySettings::refreshMappings() -> void { auto HotkeySettings::refreshMappings() -> void {
unsigned position = 0; unsigned position = 0;
for(auto& hotkey : inputManager->hotkeys) { for(auto& hotkey : inputManager->hotkeys) {
auto path = hotkey->assignment.split("/"); mappingList.item(position++)->setText(1, hotkey->assignmentName()).setText(2, hotkey->deviceName());
string assignment = path.takeLast();
string device = path(0);
mappingList.item(position++)->setText(1, assignment).setText(2, device);
} }
mappingList.resizeColumns(); mappingList.resizeColumns();
} }

View File

@ -11,9 +11,10 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
deviceList.onChange([&] { reloadMappings(); }); deviceList.onChange([&] { reloadMappings(); });
mappingList.setHeaderVisible(); mappingList.setHeaderVisible();
mappingList.onActivate([&] { assignMapping(); }); mappingList.onActivate([&] { assignMapping(); });
mappingList.onChange([&] { mappingList.onChange([&] { updateControls(); });
eraseButton.setEnabled((bool)mappingList.selected()); assignMouse1.setVisible(false).onActivate([&] { assignMouseInput(0); });
}); assignMouse2.setVisible(false).onActivate([&] { assignMouseInput(1); });
assignMouse3.setVisible(false).onActivate([&] { assignMouseInput(2); });
resetButton.setText("Reset").onActivate([&] { resetButton.setText("Reset").onActivate([&] {
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == 0) { if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == 0) {
for(auto& mapping : activeDevice().mappings) mapping->unbind(); for(auto& mapping : activeDevice().mappings) mapping->unbind();
@ -30,6 +31,26 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
reloadPorts(); reloadPorts();
} }
auto InputSettings::updateControls() -> void {
eraseButton.setEnabled((bool)mappingList.selected());
assignMouse1.setVisible(false);
assignMouse2.setVisible(false);
assignMouse3.setVisible(false);
if(auto mapping = mappingList.selected()) {
auto input = activeDevice().mappings[mapping->offset()];
if(input->isDigital()) {
assignMouse1.setVisible().setText("Mouse Left");
assignMouse2.setVisible().setText("Mouse Middle");
assignMouse3.setVisible().setText("Mouse Right");
} else if(input->isAnalog()) {
assignMouse1.setVisible().setText("Mouse X-axis");
assignMouse2.setVisible().setText("Mouse Y-axis");
}
}
}
auto InputSettings::activeEmulator() -> InputEmulator& { auto InputSettings::activeEmulator() -> InputEmulator& {
return inputManager->emulators[emulatorList.selected()->offset()]; return inputManager->emulators[emulatorList.selected()->offset()];
} }
@ -73,10 +94,7 @@ auto InputSettings::reloadMappings() -> void {
auto InputSettings::refreshMappings() -> void { auto InputSettings::refreshMappings() -> void {
unsigned position = 0; unsigned position = 0;
for(auto& mapping : activeDevice().mappings) { for(auto& mapping : activeDevice().mappings) {
auto path = mapping->assignment.split("/"); mappingList.item(position++)->setText(1, mapping->assignmentName()).setText(2, mapping->deviceName());
string assignment = path.takeLast();
string device = path(0);
mappingList.item(position++)->setText(1, assignment).setText(2, device);
} }
mappingList.resizeColumns(); mappingList.resizeColumns();
} }
@ -84,16 +102,30 @@ auto InputSettings::refreshMappings() -> void {
auto InputSettings::assignMapping() -> void { auto InputSettings::assignMapping() -> void {
inputManager->poll(); //clear any pending events first inputManager->poll(); //clear any pending events first
auto item = mappingList.selected(); auto mapping = mappingList.selected();
activeMapping = activeDevice().mappings[item->offset()]; activeMapping = activeDevice().mappings[mapping->offset()];
//settingsManager->layout.setEnabled(false); //settingsManager->layout.setEnabled(false);
settingsManager->statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); 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 { auto InputSettings::assignMouseInput(unsigned id) -> void {
if(auto mouse = inputManager->findMouse()) {
if(auto mapping = mappingList.selected()) {
activeMapping = activeDevice().mappings[mapping->offset()];
if(activeMapping->isDigital()) {
return inputEvent(*mouse, HID::Mouse::GroupID::Button, id, 0, 1, true);
} else if(activeMapping->isAnalog()) {
return inputEvent(*mouse, HID::Mouse::GroupID::Axis, id, 0, +32767, true);
}
}
}
}
auto InputSettings::inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue, bool allowMouseInput) -> void {
if(!activeMapping) return; if(!activeMapping) return;
if(!device.isKeyboard() || oldValue != 0 || newValue != 1) return; if(device.isMouse() && !allowMouseInput) return;
if(activeMapping->bind(device, group, input, oldValue, newValue)) { if(activeMapping->bind(device, group, input, oldValue, newValue)) {
activeMapping = nullptr; activeMapping = nullptr;

View File

@ -20,6 +20,15 @@ SettingsManager::SettingsManager() {
}); });
} }
auto SettingsManager::setVisible(bool visible) -> SettingsManager& {
if(visible) {
input.refreshMappings();
hotkeys.refreshMappings();
}
Window::setVisible(visible);
return *this;
}
auto SettingsManager::show(unsigned setting) -> void { auto SettingsManager::show(unsigned setting) -> void {
panel.item(setting)->setSelected(); panel.item(setting)->setSelected();
setVisible(); setVisible();

View File

@ -1,5 +1,6 @@
struct InputSettings : TabFrameItem { struct InputSettings : TabFrameItem {
InputSettings(TabFrame*); InputSettings(TabFrame*);
auto updateControls() -> void;
auto activeEmulator() -> InputEmulator&; auto activeEmulator() -> InputEmulator&;
auto activePort() -> InputPort&; auto activePort() -> InputPort&;
auto activeDevice() -> InputDevice&; auto activeDevice() -> InputDevice&;
@ -8,7 +9,8 @@ struct InputSettings : TabFrameItem {
auto reloadMappings() -> void; auto reloadMappings() -> void;
auto refreshMappings() -> void; auto refreshMappings() -> void;
auto assignMapping() -> void; auto assignMapping() -> void;
auto inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void; auto assignMouseInput(unsigned id) -> void;
auto inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void;
InputMapping* activeMapping = nullptr; InputMapping* activeMapping = nullptr;
@ -19,6 +21,9 @@ struct InputSettings : TabFrameItem {
ComboButton deviceList{&selectionLayout, Size{~0, 0}}; ComboButton deviceList{&selectionLayout, Size{~0, 0}};
ListView mappingList{&layout, Size{~0, ~0}}; ListView mappingList{&layout, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}}; HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button assignMouse1{&controlLayout, Size{100, 0}};
Button assignMouse2{&controlLayout, Size{100, 0}};
Button assignMouse3{&controlLayout, Size{100, 0}};
Widget spacer{&controlLayout, Size{~0, 0}}; Widget spacer{&controlLayout, Size{~0, 0}};
Button resetButton{&controlLayout, Size{80, 0}}; Button resetButton{&controlLayout, Size{80, 0}};
Button eraseButton{&controlLayout, Size{80, 0}}; Button eraseButton{&controlLayout, Size{80, 0}};
@ -61,6 +66,7 @@ struct AdvancedSettings : TabFrameItem {
struct SettingsManager : Window { struct SettingsManager : Window {
SettingsManager(); SettingsManager();
auto setVisible(bool visible = true) -> SettingsManager&;
auto show(unsigned setting) -> void; auto show(unsigned setting) -> void;
VerticalLayout layout{this}; VerticalLayout layout{this};