2017-01-15 01:54:09 +00:00
|
|
|
// Copyright 2017 Citra Emulator Project
|
2016-12-17 04:50:33 +00:00
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <chrono>
|
|
|
|
#include <cmath>
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
#include "core/core_timing.h"
|
|
|
|
#include "core/hw/gpu.h"
|
|
|
|
|
|
|
|
#include "input_core/devices/keyboard.h"
|
|
|
|
#include "input_core/devices/sdl_gamepad.h"
|
|
|
|
#include "input_core/input_core.h"
|
|
|
|
|
|
|
|
int InputCore::tick_event;
|
|
|
|
Service::HID::PadState InputCore::pad_state;
|
|
|
|
std::tuple<s16, s16> InputCore::circle_pad;
|
|
|
|
std::shared_ptr<Keyboard> InputCore::main_keyboard;
|
|
|
|
std::vector<std::shared_ptr<IDevice>>
|
|
|
|
InputCore::devices; ///< Devices that are handling input for the game
|
|
|
|
std::map<Settings::InputDeviceMapping, std::vector<Service::HID::PadState>> InputCore::key_mappings;
|
|
|
|
std::map<Service::HID::PadState, bool>
|
|
|
|
InputCore::keys_pressed; ///< keys that were pressed on previous frame.
|
|
|
|
std::mutex InputCore::pad_state_mutex;
|
|
|
|
std::mutex InputCore::touch_mutex;
|
|
|
|
u16 InputCore::touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
|
|
|
u16 InputCore::touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
|
|
|
bool InputCore::touch_pressed; ///< True if touchpad area is currently pressed, otherwise false
|
2017-01-15 01:54:09 +00:00
|
|
|
const float InputCore::input_detect_threshold =
|
2016-12-17 04:50:33 +00:00
|
|
|
0.45; ///< Applies to analog controls being used for digital 3ds inputs.
|
|
|
|
|
|
|
|
void InputCore::Init() {
|
|
|
|
ParseSettings();
|
|
|
|
tick_event = CoreTiming::RegisterEvent("InputCore::tick_event", InputTickCallback);
|
|
|
|
CoreTiming::ScheduleEvent(GPU::frame_ticks, tick_event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::Shutdown() {
|
|
|
|
CoreTiming::UnscheduleEvent(tick_event, 0);
|
|
|
|
devices.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::InputTickCallback(u64, int cycles_late) {
|
|
|
|
std::vector<std::map<Settings::InputDeviceMapping, float>> inputs;
|
|
|
|
for (auto& device : devices) {
|
|
|
|
inputs.push_back(device->ProcessInput());
|
|
|
|
}
|
|
|
|
UpdateEmulatorInputs(inputs);
|
|
|
|
|
|
|
|
Service::HID::Update();
|
|
|
|
|
|
|
|
// Reschedule recurrent event
|
|
|
|
CoreTiming::ScheduleEvent(GPU::frame_ticks - cycles_late, tick_event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::UpdateEmulatorInputs(
|
|
|
|
std::vector<std::map<Settings::InputDeviceMapping, float>> inputs) {
|
|
|
|
std::lock_guard<std::mutex> lock(pad_state_mutex);
|
|
|
|
|
|
|
|
// Apply deadzone for circle pad
|
2017-01-15 01:54:09 +00:00
|
|
|
float left_x = 0, left_y = 0;
|
2016-12-17 04:50:33 +00:00
|
|
|
float circle_pad_modifier = 1.0;
|
|
|
|
auto circle_pad_modifier_mapping = Settings::values.pad_circle_modifier;
|
|
|
|
for (auto& input_device : inputs) {
|
|
|
|
for (auto& button_states : input_device) { // Loop through all buttons from input device
|
|
|
|
float strength = button_states.second;
|
|
|
|
auto emulator_inputs = key_mappings[button_states.first];
|
|
|
|
for (auto& emulator_input : emulator_inputs) {
|
|
|
|
if (emulator_input == Service::HID::PAD_CIRCLE_UP && abs(strength) > 0) {
|
2017-01-15 01:54:09 +00:00
|
|
|
left_y = -strength;
|
2016-12-17 04:50:33 +00:00
|
|
|
} else if (emulator_input == Service::HID::PAD_CIRCLE_DOWN && abs(strength) > 0) {
|
2017-01-15 01:54:09 +00:00
|
|
|
left_y = strength;
|
2016-12-17 04:50:33 +00:00
|
|
|
} else if (emulator_input == Service::HID::PAD_CIRCLE_LEFT && abs(strength) > 0) {
|
2017-01-15 01:54:09 +00:00
|
|
|
left_x = -strength;
|
2016-12-17 04:50:33 +00:00
|
|
|
} else if (emulator_input == Service::HID::PAD_CIRCLE_RIGHT && abs(strength) > 0) {
|
2017-01-15 01:54:09 +00:00
|
|
|
left_x = strength;
|
2016-12-17 04:50:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (button_states.first == circle_pad_modifier_mapping)
|
|
|
|
circle_pad_modifier = (button_states.second > input_detect_threshold)
|
|
|
|
? Settings::values.pad_circle_modifier_scale
|
|
|
|
: 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
float deadzone = Settings::values.pad_circle_deadzone;
|
2017-01-15 01:54:09 +00:00
|
|
|
std::tuple<float, float> left_stick = ApplyDeadzone(left_x, left_y, deadzone);
|
2016-12-17 04:50:33 +00:00
|
|
|
|
|
|
|
// Set emulator circlepad values
|
|
|
|
std::get<0>(circle_pad) =
|
|
|
|
std::get<0>(left_stick) * KeyMap::MAX_CIRCLEPAD_POS * circle_pad_modifier;
|
|
|
|
std::get<1>(circle_pad) =
|
|
|
|
std::get<1>(left_stick) * KeyMap::MAX_CIRCLEPAD_POS * -1 * circle_pad_modifier;
|
|
|
|
|
|
|
|
// Set emulator digital button values
|
|
|
|
for (auto& input_device : inputs) {
|
|
|
|
for (auto& button_states : input_device) { // Loop through all buttons from input device
|
|
|
|
float strength = button_states.second;
|
|
|
|
auto emulator_inputs = key_mappings[button_states.first]; // vector of emulator inputs
|
|
|
|
// bound to physical button
|
|
|
|
for (auto& emulator_input : emulator_inputs) {
|
|
|
|
if (std::find(std::begin(KeyMap::analog_inputs), std::end(KeyMap::analog_inputs),
|
|
|
|
emulator_input) == std::end(KeyMap::analog_inputs)) {
|
|
|
|
if (abs(strength) < input_detect_threshold && // Key released
|
|
|
|
keys_pressed[emulator_input] == true) {
|
|
|
|
pad_state.hex &= ~emulator_input.hex;
|
|
|
|
keys_pressed[emulator_input] = false;
|
|
|
|
} else if (abs(strength) >= input_detect_threshold &&
|
|
|
|
keys_pressed[emulator_input] == false) { // Key pressed
|
|
|
|
pad_state.hex |= emulator_input.hex;
|
|
|
|
keys_pressed[emulator_input] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Service::HID::PadState InputCore::GetPadState() {
|
|
|
|
std::lock_guard<std::mutex> lock(pad_state_mutex);
|
|
|
|
return pad_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::SetPadState(const Service::HID::PadState& state) {
|
|
|
|
std::lock_guard<std::mutex> lock(pad_state_mutex);
|
|
|
|
pad_state.hex = state.hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<s16, s16> InputCore::GetCirclePad() {
|
|
|
|
return circle_pad;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Keyboard> InputCore::GetKeyboard() {
|
|
|
|
if (main_keyboard == nullptr) {
|
|
|
|
main_keyboard = std::make_shared<Keyboard>();
|
|
|
|
main_keyboard->InitDevice(0, Settings::InputDeviceMapping("SDL/0/Keyboard/-1"));
|
|
|
|
}
|
|
|
|
return main_keyboard;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<u16, u16, bool> InputCore::GetTouchState() {
|
|
|
|
std::lock_guard<std::mutex> lock(touch_mutex);
|
|
|
|
return std::make_tuple(touch_x, touch_y, touch_pressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::SetTouchState(std::tuple<u16, u16, bool> value) {
|
|
|
|
std::lock_guard<std::mutex> lock(touch_mutex);
|
|
|
|
std::tie(touch_x, touch_y, touch_pressed) = value;
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:54:09 +00:00
|
|
|
bool InputCore::CheckIfMappingExists(
|
|
|
|
const std::vector<Settings::InputDeviceMapping>& unique_mapping,
|
|
|
|
Settings::InputDeviceMapping mapping_to_check) {
|
|
|
|
return std::any_of(
|
|
|
|
unique_mapping.begin(), unique_mapping.end(),
|
|
|
|
[mapping_to_check](const auto& mapping) { return mapping == mapping_to_check; });
|
2016-12-17 04:50:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Settings::InputDeviceMapping> InputCore::GatherUniqueMappings() {
|
2017-01-15 01:54:09 +00:00
|
|
|
std::vector<Settings::InputDeviceMapping> unique_mappings;
|
2016-12-17 04:50:33 +00:00
|
|
|
|
|
|
|
for (const auto& mapping : Settings::values.input_mappings) {
|
2017-01-15 01:54:09 +00:00
|
|
|
if (!CheckIfMappingExists(unique_mappings, mapping)) {
|
|
|
|
unique_mappings.push_back(mapping);
|
2016-12-17 04:50:33 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-15 01:54:09 +00:00
|
|
|
if (!CheckIfMappingExists(unique_mappings, Settings::values.pad_circle_modifier)) {
|
|
|
|
unique_mappings.push_back(Settings::values.pad_circle_modifier);
|
2016-12-17 04:50:33 +00:00
|
|
|
}
|
2017-01-15 01:54:09 +00:00
|
|
|
return unique_mappings;
|
2016-12-17 04:50:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::BuildKeyMapping() {
|
|
|
|
key_mappings.clear();
|
|
|
|
for (size_t i = 0; i < Settings::values.input_mappings.size(); i++) {
|
|
|
|
auto key = Settings::values.input_mappings[i];
|
|
|
|
auto val = KeyMap::mapping_targets[i];
|
|
|
|
key_mappings.emplace(key, std::vector<Service::HID::PadState>());
|
|
|
|
key_mappings[key].push_back(val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::GenerateUniqueDevices() {
|
|
|
|
auto uniqueMappings = GatherUniqueMappings();
|
|
|
|
devices.clear();
|
|
|
|
std::shared_ptr<IDevice> input;
|
|
|
|
for (const auto& mapping : uniqueMappings) {
|
|
|
|
switch (mapping.framework) {
|
|
|
|
case Settings::DeviceFramework::SDL: {
|
|
|
|
if (mapping.device == Settings::Device::Keyboard) {
|
|
|
|
main_keyboard = std::make_shared<Keyboard>();
|
|
|
|
input = main_keyboard;
|
|
|
|
break;
|
|
|
|
} else if (mapping.device == Settings::Device::Gamepad) {
|
|
|
|
input = std::make_shared<SDLGamepad>();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
devices.push_back(input);
|
|
|
|
input->InitDevice(mapping.number, mapping);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::ParseSettings() {
|
|
|
|
GenerateUniqueDevices();
|
|
|
|
BuildKeyMapping();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<float, float> InputCore::ApplyDeadzone(float x, float y, float dead_zone) {
|
|
|
|
float magnitude = std::sqrt((x * x) + (y * y));
|
|
|
|
if (magnitude < dead_zone) {
|
|
|
|
x = 0;
|
|
|
|
y = 0;
|
|
|
|
} else {
|
|
|
|
float normalized_x = x / magnitude;
|
|
|
|
float normalized_y = y / magnitude;
|
|
|
|
x = normalized_x * ((magnitude - dead_zone) / (1 - dead_zone));
|
|
|
|
y = normalized_y * ((magnitude - dead_zone) / (1 - dead_zone));
|
|
|
|
}
|
|
|
|
return std::tuple<float, float>(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputCore::ReloadSettings() {
|
|
|
|
if (devices.empty())
|
|
|
|
return;
|
|
|
|
std::lock_guard<std::mutex> lock(pad_state_mutex);
|
|
|
|
devices.clear();
|
|
|
|
ParseSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::shared_ptr<IDevice>> InputCore::GetAllDevices() {
|
|
|
|
auto all_devices = SDLGamepad::GetAllDevices();
|
|
|
|
auto keyboard = InputCore::GetKeyboard();
|
|
|
|
all_devices.push_back(keyboard);
|
|
|
|
|
|
|
|
return all_devices;
|
|
|
|
}
|
|
|
|
|
|
|
|
Settings::InputDeviceMapping InputCore::DetectInput(int max_time,
|
2017-01-15 01:54:09 +00:00
|
|
|
std::function<void(void)> update_gui) {
|
2016-12-17 04:50:33 +00:00
|
|
|
auto devices = GetAllDevices();
|
|
|
|
for (auto& device : devices) {
|
|
|
|
device->Clear();
|
|
|
|
}
|
|
|
|
Settings::InputDeviceMapping input_device;
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
while (input_device.key == -1) {
|
2017-01-15 01:54:09 +00:00
|
|
|
update_gui();
|
2016-12-17 04:50:33 +00:00
|
|
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
std::chrono::high_resolution_clock::now() - start)
|
|
|
|
.count();
|
|
|
|
if (duration >= max_time) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (auto& device : devices) {
|
|
|
|
input_device = device->GetInput();
|
|
|
|
if (input_device.key != -1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return input_device;
|
|
|
|
}
|