#include "../bsnes.hpp" #include "_hotkeys.cpp" InputManager inputManager; auto InputMapping::bind() -> void { for (auto& binding : bindings) { binding = {}; } for (uint index : range(BindingLimit)) { auto& assignment = assignments[index]; auto& binding = bindings[index]; auto token = assignment.split("/"); if (token.size() < 3) { //skip invalid mappings continue; } uint64 id = token[0].natural(); uint group = token[1].natural(); uint input = token[2].natural(); string qualifier = token(3, "None"); for (auto& device : inputManager.devices) { if (id != device->id()) { continue; } binding.device = device; binding.group = group; binding.input = input; binding.qualifier = Qualifier::None; if (qualifier == "Lo") { binding.qualifier = Qualifier::Lo; } else if (qualifier == "Hi") { binding.qualifier = Qualifier::Hi; } else if (qualifier == "Rumble") { binding.qualifier = Qualifier::Rumble; } break; } } string text; for (auto& assignment : assignments) { text.append(assignment, ";"); } text.trimRight(";"); settings[path].setValue(text); } auto InputMapping::bind(string mapping, uint binding) -> void { if (binding >= BindingLimit) { return; } assignments[binding] = mapping; bind(); } auto InputMapping::bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool { if (device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) { return unbind(binding), true; } string encoding = {"0x", 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) { return bind(encoding, binding), true; } } if ( (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger) ) { if (newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi return bind({encoding, "/Lo"}, binding), true; } if (newValue > +16384) { return bind({encoding, "/Hi"}, binding), 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) { return bind(encoding, binding), true; } } } if (isRumble() && device->isJoypad() && group == HID::Joypad::GroupID::Button && newValue) { return bind({encoding, "/Rumble"}, binding), true; } return false; } auto InputMapping::unbind(uint binding) -> void { bindings[binding] = {}; assignments[binding] = {}; string text; for (auto& assignment : assignments) { text.append(assignment, ";"); } text.trimRight(";"); settings[path].setValue(text); } auto InputMapping::poll() -> int16 { if (turboID) { auto& mapping = inputManager.ports[portID].devices[deviceID].mappings[turboID()]; auto result = mapping.poll(); if (result) { return inputManager.turboCounter >= inputManager.turboFrequency; } } uint validBindings = 0; int16 result = 0; for (auto& binding : bindings) { if (!binding.device) { //unbound continue; } validBindings++; auto& device = binding.device; auto& group = binding.group; auto& input = binding.input; auto& qualifier = binding.qualifier; auto value = device->group(group).input(input).value(); if (isDigital()) { boolean output; if ( (device->isKeyboard() && group == HID::Keyboard::GroupID::Button) || (device->isMouse() && group == HID::Mouse::GroupID::Button) || (device->isJoypad() && group == HID::Joypad::GroupID::Button) ) { output = value != 0; } if ( (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger) ) { if (qualifier == Qualifier::Lo) { output = value < -16384; } else if (qualifier == Qualifier::Hi) { output = value > +16384; } } if (logic() == Logic::AND && output == 0) { return 0; } if (logic() == Logic::OR && output == 1) { return 1; } } if (isAnalog()) { //logic does not apply to analog inputs ... always combinatorial if (device->isMouse() && group == HID::Mouse::GroupID::Axis) { result += value; } if (device->isJoypad() && group == HID::Joypad::GroupID::Axis) { result += value >> 8; } if (device->isJoypad() && group == HID::Joypad::GroupID::Hat) { result += value < 0 ? -1 : value > 0 ? +1 : 0; } } } if (isDigital() && logic() == Logic::AND && validBindings > 0) { return 1; } return result; } auto InputMapping::rumble(bool enable) -> void { for (auto& binding : bindings) { if (!binding.device) { continue; } input.rumble(binding.device->id(), enable); } } // auto InputMapping::Binding::icon() -> image { if (device) { if (device->isKeyboard()) { return Icon::Device::Keyboard; } if (device->isMouse()) { return Icon::Device::Mouse; } if (device->isJoypad()) { return Icon::Device::Joypad; } } return {}; } auto InputMapping::Binding::name() -> string { //if ruby drivers cannot report accurate vendor/product IDs (eg SDL), //and the user closes the emulator, changes gamepads, and restarts it, //it is possible the wrong device will now be mapped to the input IDs. //that could potentially make the group/input IDs go out of bounds. if (!device || group >= device->size() || input >= device->group(group).size()) { return {}; } if (device->isKeyboard() || device->isMouse()) { return device->group(group).input(input).name(); } if (device->isJoypad()) { string name{Hash::CRC16(string{device->id()}).digest().upcase()}; name.append(" ", device->group(group).name()); name.append(" ", device->group(group).input(input).name()); if (qualifier == Qualifier::Lo) { name.append(" Lo"); } else if (qualifier == Qualifier::Hi) { name.append(" Hi"); } else if (qualifier == Qualifier::Rumble) { name.append(" Rumble"); } return name; } return {}; } // auto InputManager::initialize() -> void { devices.reset(); ports.reset(); hotkeys.reset(); input.onChange({&InputManager::onChange, this}); lastPoll = 0; //force a poll event immediately frequency = max(1u, settings.input.frequency); turboCounter = 0; turboFrequency = max(1, settings.input.turbo.frequency); auto information = emulator->information(); auto ports = emulator->ports(); for (uint portID : range(ports.size())) { auto& port = ports[portID]; InputPort inputPort{port.id, port.name}; auto devices = emulator->devices(port.id); for (uint deviceID : range(devices.size())) { auto& device = devices[deviceID]; InputDevice inputDevice{device.id, device.name}; auto inputs = emulator->inputs(device.id); for (uint inputID : range(inputs.size())) { auto& input = inputs[inputID]; InputMapping inputMapping; inputMapping.portID = portID; inputMapping.deviceID = deviceID; inputMapping.inputID = inputID; inputMapping.name = input.name; inputMapping.type = input.type; inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", ""); auto assignments = settings(inputMapping.path).text().split(";"); for (uint index : range(BindingLimit)) { inputMapping.assignments[index] = assignments(index); } inputDevice.mappings.append(inputMapping); } for (uint inputID : range(inputs.size())) { auto& input = inputs[inputID]; if (input.type != InputMapping::Type::Button && input.type != InputMapping::Type::Trigger) { continue; } uint turboID = inputDevice.mappings.size(); InputMapping inputMapping; inputMapping.portID = portID; inputMapping.deviceID = deviceID; inputMapping.inputID = turboID; inputMapping.name = string{"Turbo ", input.name}; inputMapping.type = input.type; inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", ""); auto assignments = settings(inputMapping.path).text().split(";"); for (uint index : range(BindingLimit)) { inputMapping.assignments[index] = assignments(index); } inputDevice.mappings.append(inputMapping); inputDevice.mappings[inputID].turboID = turboID; } inputPort.devices.append(inputDevice); } this->ports.append(inputPort); } bindHotkeys(); poll(); } auto InputManager::bind() -> void { for (auto& port : ports) { for (auto& device : port.devices) { for (auto& mapping : device.mappings) { mapping.bind(); } } } for (auto& hotkey : hotkeys) { hotkey.bind(); } } auto InputManager::poll() -> void { if (Application::modal()) { return; } //polling actual hardware devices is time-consuming; skip if poll was called too recently auto thisPoll = chrono::millisecond(); if (thisPoll - lastPoll < frequency) { return; } lastPoll = thisPoll; auto devices = input.poll(); bool changed = devices.size() != this->devices.size(); if (!changed) { for (auto n : range(devices.size())) { changed = devices[n] != this->devices[n]; if (changed) { break; } } } if (changed) { this->devices = devices; bind(); } } auto InputManager::frame() -> void { if (++turboCounter >= turboFrequency * 2) { turboCounter = 0; } } auto InputManager::onChange(shared_pointer device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void { if (settingsWindow.focused()) { inputSettings.inputEvent(device, group, input, oldValue, newValue); hotkeySettings.inputEvent(device, group, input, oldValue, newValue); } } auto InputManager::mapping(uint port, uint device, uint input) -> maybe { if (!emulator) { return nothing; } for (auto& inputPort : ports) { if (inputPort.id != port) { continue; } for (auto& inputDevice : inputPort.devices) { if (inputDevice.id != device || input >= inputDevice.mappings.size()) { continue; } return inputDevice.mappings[input]; } } return nothing; } auto InputManager::findMouse() -> shared_pointer { for (auto& device : devices) { if (device->isMouse()) { return device; } } return {}; }