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
else ifeq ($(platform),bsd)
flags += -march=native
link += -Wl,-rpath=/usr/local/lib
link += -Wl,-rpath=/usr/local/lib/gcc49
link += -Wl,-export-dynamic
link += -lX11 -lXext
else

View File

@ -3,7 +3,7 @@
namespace Emulator {
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 License[] = "GPLv3";
static const char Website[] = "http://byuu.org/";

View File

@ -73,6 +73,8 @@ endif
# bsd settings
ifeq ($(platform),bsd)
flags += -I/usr/local/include
link += -Wl,-rpath=/usr/local/lib
link += -Wl,-rpath=/usr/local/lib/gcc49
endif
# 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 += input.udev input.sdl input.xlib
else ifeq ($(platform),bsd)
ruby := video.glx video.xshm
ruby := video.glx video.xv video.xshm video.sdl
ruby += audio.openal audio.oss
ruby += input.xlib
ruby += input.sdl input.xlib
endif
# ruby

View File

@ -7,6 +7,14 @@ auto InputManager::appendHotkeys() -> void {
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;
hotkey->name = "Save State";
hotkey->action = [] {

View File

@ -4,10 +4,11 @@ InputManager* inputManager = nullptr;
auto InputMapping::bind() -> void {
auto token = assignment.split("/");
if(token.size() < 3) return;
if(token.size() < 3) return unbind();
uint64_t id = token[0].hex();
unsigned group = token[1].decimal();
unsigned input = token[2].decimal();
string qualifier = token(3, "None");
for(auto& device : inputManager->devices) {
if(id != device->id) continue;
@ -15,22 +16,89 @@ auto InputMapping::bind() -> void {
this->device = device;
this->group = group;
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;
}
}
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};
this->device = &device;
this->group = group;
this->input = input;
return true;
string encoding = {hex(device.id), "/", group, "/", input};
if(isDigital()) {
if((device.isKeyboard() && group == HID::Keyboard::GroupID::Button)
|| (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 {
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;
}
@ -39,6 +107,24 @@ auto InputMapping::unbind() -> void {
this->device = nullptr;
this->group = 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();
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 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 assignment = "None";
Emulator::Interface::Device::Input* link = nullptr;
HID::Device* device = nullptr;
unsigned group = 0;
unsigned input = 0;
enum class Qualifier : unsigned { None, Lo, Hi, Rumble } qualifier = Qualifier::None;
};
struct InputHotkey : InputMapping {
@ -40,6 +48,8 @@ struct InputManager {
auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void;
auto quit() -> void;
auto findMouse() -> HID::Device*;
//hotkeys.cpp
auto appendHotkeys() -> void;
auto pollHotkeys() -> void;

View File

@ -102,6 +102,29 @@ Presentation::Presentation() {
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 {
signed width = 256;
signed height = 240;
@ -154,7 +177,9 @@ auto Presentation::toggleFullScreen() -> void {
statusBar.setVisible(false);
setResizable(true);
setFullScreen(true);
if(!input.acquired()) input.acquire();
} else {
if(input.acquired()) input.unacquire();
setFullScreen(false);
setResizable(false);
menuBar.setVisible(true);

View File

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

View File

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

View File

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

View File

@ -11,9 +11,10 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
deviceList.onChange([&] { reloadMappings(); });
mappingList.setHeaderVisible();
mappingList.onActivate([&] { assignMapping(); });
mappingList.onChange([&] {
eraseButton.setEnabled((bool)mappingList.selected());
});
mappingList.onChange([&] { updateControls(); });
assignMouse1.setVisible(false).onActivate([&] { assignMouseInput(0); });
assignMouse2.setVisible(false).onActivate([&] { assignMouseInput(1); });
assignMouse3.setVisible(false).onActivate([&] { assignMouseInput(2); });
resetButton.setText("Reset").onActivate([&] {
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();
@ -30,6 +31,26 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
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& {
return inputManager->emulators[emulatorList.selected()->offset()];
}
@ -73,10 +94,7 @@ auto InputSettings::reloadMappings() -> void {
auto InputSettings::refreshMappings() -> void {
unsigned position = 0;
for(auto& mapping : activeDevice().mappings) {
auto path = mapping->assignment.split("/");
string assignment = path.takeLast();
string device = path(0);
mappingList.item(position++)->setText(1, assignment).setText(2, device);
mappingList.item(position++)->setText(1, mapping->assignmentName()).setText(2, mapping->deviceName());
}
mappingList.resizeColumns();
}
@ -84,16 +102,30 @@ auto InputSettings::refreshMappings() -> void {
auto InputSettings::assignMapping() -> void {
inputManager->poll(); //clear any pending events first
auto item = mappingList.selected();
activeMapping = activeDevice().mappings[item->offset()];
auto mapping = mappingList.selected();
activeMapping = activeDevice().mappings[mapping->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 {
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(!device.isKeyboard() || oldValue != 0 || newValue != 1) return;
if(device.isMouse() && !allowMouseInput) return;
if(activeMapping->bind(device, group, input, oldValue, newValue)) {
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 {
panel.item(setting)->setSelected();
setVisible();

View File

@ -1,5 +1,6 @@
struct InputSettings : TabFrameItem {
InputSettings(TabFrame*);
auto updateControls() -> void;
auto activeEmulator() -> InputEmulator&;
auto activePort() -> InputPort&;
auto activeDevice() -> InputDevice&;
@ -8,7 +9,8 @@ struct InputSettings : TabFrameItem {
auto reloadMappings() -> void;
auto refreshMappings() -> 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;
@ -19,6 +21,9 @@ struct InputSettings : TabFrameItem {
ComboButton deviceList{&selectionLayout, Size{~0, 0}};
ListView mappingList{&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}};
Button resetButton{&controlLayout, Size{80, 0}};
Button eraseButton{&controlLayout, Size{80, 0}};
@ -61,6 +66,7 @@ struct AdvancedSettings : TabFrameItem {
struct SettingsManager : Window {
SettingsManager();
auto setVisible(bool visible = true) -> SettingsManager&;
auto show(unsigned setting) -> void;
VerticalLayout layout{this};