// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #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 InputCore::circle_pad; std::shared_ptr InputCore::main_keyboard; std::vector> InputCore::devices; ///< Devices that are handling input for the game std::map> InputCore::key_mappings; std::map 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 const float InputCore::input_detect_threshold = 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> 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> inputs) { std::lock_guard lock(pad_state_mutex); // Apply deadzone for circle pad float left_x = 0, left_y = 0; 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) { left_y = -strength; } else if (emulator_input == Service::HID::PAD_CIRCLE_DOWN && abs(strength) > 0) { left_y = strength; } else if (emulator_input == Service::HID::PAD_CIRCLE_LEFT && abs(strength) > 0) { left_x = -strength; } else if (emulator_input == Service::HID::PAD_CIRCLE_RIGHT && abs(strength) > 0) { left_x = strength; } } 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; std::tuple left_stick = ApplyDeadzone(left_x, left_y, deadzone); // 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 lock(pad_state_mutex); return pad_state; } void InputCore::SetPadState(const Service::HID::PadState& state) { std::lock_guard lock(pad_state_mutex); pad_state.hex = state.hex; } std::tuple InputCore::GetCirclePad() { return circle_pad; } std::shared_ptr InputCore::GetKeyboard() { if (main_keyboard == nullptr) { main_keyboard = std::make_shared(); main_keyboard->InitDevice(0, Settings::InputDeviceMapping("SDL/0/Keyboard/-1")); } return main_keyboard; } std::tuple InputCore::GetTouchState() { std::lock_guard lock(touch_mutex); return std::make_tuple(touch_x, touch_y, touch_pressed); } void InputCore::SetTouchState(std::tuple value) { std::lock_guard lock(touch_mutex); std::tie(touch_x, touch_y, touch_pressed) = value; } bool InputCore::CheckIfMappingExists( const std::vector& 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; }); } std::vector InputCore::GatherUniqueMappings() { std::vector unique_mappings; for (const auto& mapping : Settings::values.input_mappings) { if (!CheckIfMappingExists(unique_mappings, mapping)) { unique_mappings.push_back(mapping); } } if (!CheckIfMappingExists(unique_mappings, Settings::values.pad_circle_modifier)) { unique_mappings.push_back(Settings::values.pad_circle_modifier); } return unique_mappings; } 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()); key_mappings[key].push_back(val); } } void InputCore::GenerateUniqueDevices() { auto uniqueMappings = GatherUniqueMappings(); devices.clear(); std::shared_ptr input; for (const auto& mapping : uniqueMappings) { switch (mapping.framework) { case Settings::DeviceFramework::SDL: { if (mapping.device == Settings::Device::Keyboard) { main_keyboard = std::make_shared(); input = main_keyboard; break; } else if (mapping.device == Settings::Device::Gamepad) { input = std::make_shared(); break; } } } devices.push_back(input); input->InitDevice(mapping.number, mapping); } } void InputCore::ParseSettings() { GenerateUniqueDevices(); BuildKeyMapping(); } std::tuple 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(x, y); } void InputCore::ReloadSettings() { if (devices.empty()) return; std::lock_guard lock(pad_state_mutex); devices.clear(); ParseSettings(); } std::vector> 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, std::function update_gui) { 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) { update_gui(); auto duration = std::chrono::duration_cast( 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; }