duckstation/src/util/input_manager.cpp
Stenzek 9113a6e6a6
Qt: Add 'Multiple Devices' to automatic mapping
Also populate the "current device" label with the device from
the config when running the setup wizard, instead of always setting
the label to Keyboard.
2025-01-22 18:38:37 +10:00

2220 lines
72 KiB
C++

// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "input_manager.h"
#include "imgui_manager.h"
#include "input_source.h"
#include "core/controller.h"
#include "core/host.h"
#include "core/system.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "IconsPromptFont.h"
#include "fmt/core.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <memory>
#include <mutex>
#include <sstream>
#include <unordered_map>
#include <variant>
#include <vector>
LOG_CHANNEL(InputManager);
namespace InputManager {
// ------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------
enum : u32
{
FIRST_EXTERNAL_INPUT_SOURCE = static_cast<u32>(InputSourceType::Pointer) + 1u,
LAST_EXTERNAL_INPUT_SOURCE = static_cast<u32>(InputSourceType::Count),
};
// ------------------------------------------------------------------------
// Binding Type
// ------------------------------------------------------------------------
// This class tracks both the keys which make it up (for chords), as well
// as the state of all buttons. For button callbacks, it's fired when
// all keys go active, and for axis callbacks, when all are active and
// the value changes.
namespace {
struct InputBinding
{
InputBindingKey keys[MAX_KEYS_PER_BINDING] = {};
InputEventHandler handler;
u8 num_keys = 0;
u8 full_mask = 0;
u8 current_mask = 0;
};
struct PadVibrationBinding
{
struct Motor
{
InputBindingKey binding;
Timer::Value last_update_time;
InputSource* source;
float last_intensity;
};
u32 pad_index = 0;
Motor motors[MAX_MOTORS_PER_PAD] = {};
/// Returns true if the two motors are bound to the same host motor.
ALWAYS_INLINE bool AreMotorsCombined() const { return motors[0].binding == motors[1].binding; }
/// Returns the intensity when both motors are combined.
ALWAYS_INLINE float GetCombinedIntensity() const
{
return std::max(motors[0].last_intensity, motors[1].last_intensity);
}
};
struct MacroButton
{
std::vector<u32> buttons; ///< Buttons to activate.
u16 toggle_frequency; ///< Interval at which the buttons will be toggled, if not 0.
u16 toggle_counter; ///< When this counter reaches zero, buttons will be toggled.
bool toggle_state; ///< Current state for turbo.
bool trigger_state; ///< Whether the macro button is active.
bool trigger_toggle; ///< Whether the macro is trigged by holding or press.
u8 trigger_pressure; ///< Pressure to apply when macro is active.
};
} // namespace
// ------------------------------------------------------------------------
// Forward Declarations (for static qualifier)
// ------------------------------------------------------------------------
static std::optional<InputBindingKey> ParseHostKeyboardKey(std::string_view source, std::string_view sub_binding);
static std::optional<InputBindingKey> ParsePointerKey(std::string_view source, std::string_view sub_binding);
static std::optional<InputBindingKey> ParseSensorKey(std::string_view source, std::string_view sub_binding);
static std::vector<std::string_view> SplitChord(std::string_view binding);
static bool SplitBinding(std::string_view binding, std::string_view* source, std::string_view* sub_binding);
static void PrettifyInputBindingPart(std::string_view binding, BindingIconMappingFunction mapper, SmallString& ret,
bool& changed);
static void AddBindings(const std::vector<std::string>& bindings, const InputEventHandler& handler);
static void UpdatePointerCount();
static bool IsAxisHandler(const InputEventHandler& handler);
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
static void AddHotkeyBindings(const SettingsInterface& si);
static void AddPadBindings(const SettingsInterface& si, const std::string& section, u32 pad,
const Controller::ControllerInfo& cinfo);
static void UpdateContinuedVibration();
static void GenerateRelativeMouseEvents();
static bool DoEventHook(InputBindingKey key, float value);
static bool PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key);
static bool ProcessEvent(InputBindingKey key, float value, bool skip_button_handlers);
static void LoadMacroButtonConfig(const SettingsInterface& si, const std::string& section, u32 pad,
const Controller::ControllerInfo& cinfo);
static void ApplyMacroButton(u32 pad, const MacroButton& mb);
static void UpdateMacroButtons();
static void UpdateInputSourceState(const SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock,
InputSourceType type, std::unique_ptr<InputSource> (*factory_function)());
// ------------------------------------------------------------------------
// Local Variables
// ------------------------------------------------------------------------
// This is a multimap containing any binds related to the specified key.
using BindingMap = std::unordered_multimap<InputBindingKey, std::shared_ptr<InputBinding>, InputBindingKeyHash>;
using VibrationBindingArray = std::vector<PadVibrationBinding>;
static BindingMap s_binding_map;
static VibrationBindingArray s_pad_vibration_array;
static std::recursive_mutex s_mutex;
// Hooks/intercepting (for setting bindings)
static InputInterceptHook::Callback m_event_intercept_callback;
// Input sources. Keyboard/mouse don't exist here.
static std::array<std::unique_ptr<InputSource>, static_cast<u32>(InputSourceType::Count)> s_input_sources;
// Macro buttons.
static std::array<std::array<MacroButton, InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER>,
NUM_CONTROLLER_AND_CARD_PORTS>
s_macro_buttons;
// ------------------------------------------------------------------------
// Hotkeys
// ------------------------------------------------------------------------
static const HotkeyInfo* const s_hotkey_list[] = {g_common_hotkeys, g_host_hotkeys};
// ------------------------------------------------------------------------
// Tracking host mouse movement and turning into relative events
// 4 axes: pointer left/right, wheel vertical/horizontal. Last/Next/Normalized.
// ------------------------------------------------------------------------
static constexpr const std::array<const char*, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_names = {
{"X", "Y", "WheelX", "WheelY"}};
static constexpr const std::array<const char*, 3> s_pointer_button_names = {
{"LeftButton", "RightButton", "MiddleButton"}};
static constexpr const std::array<const char*, 3> s_sensor_accelerometer_names = {{"Turn", "Tilt", "Rotate"}};
struct PointerAxisState
{
std::atomic<s32> delta;
float last_value;
};
static std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES>
s_host_pointer_positions;
static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis::Count)>,
InputManager::MAX_POINTER_DEVICES>
s_pointer_state;
static u32 s_pointer_count = 0;
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
static std::vector<std::pair<u32, PointerMoveCallback>> s_pointer_move_callbacks;
// Window size, used for clamping the mouse position in raw input modes.
static std::array<float, 2> s_window_size = {};
static bool s_relative_mouse_mode = false;
static bool s_relative_mouse_mode_active = false;
static bool s_hide_host_mouse_cursor = false;
static bool s_hide_host_mouse_cusor_active = false;
} // namespace InputManager
// ------------------------------------------------------------------------
// Binding Parsing
// ------------------------------------------------------------------------
std::vector<std::string_view> InputManager::SplitChord(std::string_view binding)
{
std::vector<std::string_view> parts;
// under an if for RVO
if (!binding.empty())
{
std::string_view::size_type last = 0;
std::string_view::size_type next;
while ((next = binding.find('&', last)) != std::string_view::npos)
{
if (last != next)
{
std::string_view part(StringUtil::StripWhitespace(binding.substr(last, next - last)));
if (!part.empty())
parts.push_back(std::move(part));
}
last = next + 1;
}
if (last < (binding.size() - 1))
{
std::string_view part(StringUtil::StripWhitespace(binding.substr(last)));
if (!part.empty())
parts.push_back(std::move(part));
}
}
return parts;
}
bool InputManager::SplitBinding(std::string_view binding, std::string_view* source, std::string_view* sub_binding)
{
const std::string_view::size_type slash_pos = binding.find('/');
if (slash_pos == std::string_view::npos)
{
WARNING_LOG("Malformed binding: '{}'", binding);
return false;
}
*source = std::string_view(binding).substr(0, slash_pos);
*sub_binding = std::string_view(binding).substr(slash_pos + 1);
return true;
}
std::optional<InputBindingKey> InputManager::ParseInputBindingKey(std::string_view binding)
{
std::string_view source, sub_binding;
if (!SplitBinding(binding, &source, &sub_binding))
return std::nullopt;
// lameee, string matching
if (source.starts_with("Keyboard"))
{
return ParseHostKeyboardKey(source, sub_binding);
}
else if (source.starts_with("Pointer"))
{
return ParsePointerKey(source, sub_binding);
}
else if (source.starts_with("Sensor"))
{
return ParseSensorKey(source, sub_binding);
}
else
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
std::optional<InputBindingKey> key = s_input_sources[i]->ParseKeyString(source, sub_binding);
if (key.has_value())
return key;
}
}
}
return std::nullopt;
}
bool InputManager::ParseBindingAndGetSource(std::string_view binding, InputBindingKey* key, InputSource** source)
{
std::string_view source_string, sub_binding;
if (!SplitBinding(binding, &source_string, &sub_binding))
return false;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
std::optional<InputBindingKey> parsed_key = s_input_sources[i]->ParseKeyString(source_string, sub_binding);
if (parsed_key.has_value())
{
*key = parsed_key.value();
*source = s_input_sources[i].get();
return true;
}
}
}
return false;
}
std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key)
{
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer ||
binding_type == InputBindingInfo::Type::Device)
{
// pointer and device bindings don't have a data part
if (key.source_type == InputSourceType::Pointer)
{
return GetPointerDeviceName(key.source_index);
}
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
{
// This assumes that it always follows the Type/Binding form.
std::string keystr(s_input_sources[static_cast<u32>(key.source_type)]->ConvertKeyToString(key));
std::string::size_type pos = keystr.find('/');
if (pos != std::string::npos)
keystr.erase(pos);
return keystr;
}
}
else
{
if (key.source_type == InputSourceType::Keyboard)
{
const std::optional<std::string> str(ConvertHostKeyboardCodeToString(key.data));
if (str.has_value() && !str->empty())
return fmt::format("Keyboard/{}", str->c_str());
}
else if (key.source_type == InputSourceType::Pointer)
{
if (key.source_subtype == InputSubclass::PointerButton)
{
if (key.data < s_pointer_button_names.size())
return fmt::format("Pointer-{}/{}", u32{key.source_index}, s_pointer_button_names[key.data]);
else
return fmt::format("Pointer-{}/Button{}", u32{key.source_index}, key.data);
}
else if (key.source_subtype == InputSubclass::PointerAxis)
{
return fmt::format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_names[key.data],
key.modifier == InputModifier::Negate ? '-' : '+');
}
}
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
{
return std::string(s_input_sources[static_cast<u32>(key.source_type)]->ConvertKeyToString(key));
}
}
return {};
}
std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type,
const InputBindingKey* keys, size_t num_keys)
{
// can't have a chord of devices/pointers
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer ||
binding_type == InputBindingInfo::Type::Device)
{
// so only take the first
if (num_keys > 0)
return ConvertInputBindingKeyToString(binding_type, keys[0]);
}
std::stringstream ss;
for (size_t i = 0; i < num_keys; i++)
{
const std::string keystr(ConvertInputBindingKeyToString(binding_type, keys[i]));
if (keystr.empty())
return std::string();
if (i > 0)
ss << " & ";
ss << keystr;
}
return ss.str();
}
bool InputManager::PrettifyInputBinding(SmallStringBase& binding, BindingIconMappingFunction mapper /*= nullptr*/)
{
if (binding.empty())
return false;
mapper = mapper ? mapper : [](std::string_view v) { return v; };
const std::string_view binding_view = binding.view();
SmallString ret;
bool changed = false;
std::string_view::size_type last = 0;
std::string_view::size_type next;
while ((next = binding_view.find('&', last)) != std::string_view::npos)
{
if (last != next)
{
const std::string_view part = StringUtil::StripWhitespace(binding_view.substr(last, next - last));
if (!part.empty())
{
if (!ret.empty())
ret.append(" + ");
PrettifyInputBindingPart(part, mapper, ret, changed);
}
}
last = next + 1;
}
if (last < (binding_view.size() - 1))
{
const std::string_view part = StringUtil::StripWhitespace(binding_view.substr(last));
if (!part.empty())
{
if (!ret.empty())
ret.append(" + ");
PrettifyInputBindingPart(part, mapper, ret, changed);
}
}
if (changed)
binding = ret;
return changed;
}
void InputManager::PrettifyInputBindingPart(const std::string_view binding, BindingIconMappingFunction mapper,
SmallString& ret, bool& changed)
{
std::string_view source, sub_binding;
if (!SplitBinding(binding, &source, &sub_binding))
return;
// lameee, string matching
if (source.starts_with("Keyboard"))
{
std::optional<InputBindingKey> key = ParseHostKeyboardKey(source, sub_binding);
const char* icon = key.has_value() ? ConvertHostKeyboardCodeToIcon(key->data) : nullptr;
if (icon)
{
ret.append(icon);
changed = true;
return;
}
}
else if (source.starts_with("Pointer"))
{
const std::optional<InputBindingKey> key = ParsePointerKey(source, sub_binding);
if (key.has_value())
{
if (key->source_subtype == InputSubclass::PointerButton)
{
static constexpr const char* button_icons[] = {
ICON_PF_MOUSE_BUTTON_1, ICON_PF_MOUSE_BUTTON_2, ICON_PF_MOUSE_BUTTON_3,
ICON_PF_MOUSE_BUTTON_4, ICON_PF_MOUSE_BUTTON_5,
};
if (key->data < std::size(button_icons))
{
ret.append(button_icons[key->data]);
changed = true;
return;
}
}
}
}
else if (source.starts_with("Sensor"))
{
}
else
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
std::optional<InputBindingKey> key = s_input_sources[i]->ParseKeyString(source, sub_binding);
if (key.has_value())
{
const TinyString icon = s_input_sources[i]->ConvertKeyToIcon(key.value(), mapper);
if (!icon.empty())
{
ret.append(icon);
changed = true;
return;
}
break;
}
}
}
}
ret.append(binding);
}
void InputManager::AddBindings(const std::vector<std::string>& bindings, const InputEventHandler& handler)
{
for (const std::string& binding : bindings)
AddBinding(binding, handler);
}
void InputManager::AddBinding(std::string_view binding, const InputEventHandler& handler)
{
std::shared_ptr<InputBinding> ibinding;
const std::vector<std::string_view> chord_bindings(SplitChord(binding));
for (const std::string_view& chord_binding : chord_bindings)
{
std::optional<InputBindingKey> key = ParseInputBindingKey(chord_binding);
if (!key.has_value())
{
ERROR_LOG("Invalid binding: '{}'", binding);
ibinding.reset();
break;
}
if (!ibinding)
{
ibinding = std::make_shared<InputBinding>();
ibinding->handler = handler;
}
if (ibinding->num_keys == MAX_KEYS_PER_BINDING)
{
ERROR_LOG("Too many chord parts, max is {} ({})", static_cast<unsigned>(MAX_KEYS_PER_BINDING), binding.size());
ibinding.reset();
break;
}
ibinding->keys[ibinding->num_keys] = key.value();
ibinding->full_mask |= (static_cast<u8>(1) << ibinding->num_keys);
ibinding->num_keys++;
}
if (!ibinding)
return;
// plop it in the input map for all the keys
for (u32 i = 0; i < ibinding->num_keys; i++)
s_binding_map.emplace(ibinding->keys[i].MaskDirection(), ibinding);
}
void InputManager::AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding,
InputSource* motor_0_source, const InputBindingKey* motor_1_binding,
InputSource* motor_1_source)
{
PadVibrationBinding vib;
vib.pad_index = pad_index;
if (motor_0_binding)
{
vib.motors[0].binding = *motor_0_binding;
vib.motors[0].source = motor_0_source;
}
if (motor_1_binding)
{
vib.motors[1].binding = *motor_1_binding;
vib.motors[1].source = motor_1_source;
}
s_pad_vibration_array.push_back(std::move(vib));
}
// ------------------------------------------------------------------------
// Key Decoders
// ------------------------------------------------------------------------
InputBindingKey InputManager::MakeHostKeyboardKey(u32 key_code)
{
InputBindingKey key = {};
key.source_type = InputSourceType::Keyboard;
key.data = key_code;
return key;
}
InputBindingKey InputManager::MakePointerButtonKey(u32 index, u32 button_index)
{
InputBindingKey key = {};
key.source_index = index;
key.source_type = InputSourceType::Pointer;
key.source_subtype = InputSubclass::PointerButton;
key.data = button_index;
return key;
}
InputBindingKey InputManager::MakePointerAxisKey(u32 index, InputPointerAxis axis)
{
InputBindingKey key = {};
key.data = static_cast<u32>(axis);
key.source_index = index;
key.source_type = InputSourceType::Pointer;
key.source_subtype = InputSubclass::PointerAxis;
return key;
}
InputBindingKey InputManager::MakeSensorAxisKey(InputSubclass sensor, u32 axis)
{
InputBindingKey key = {};
key.data = static_cast<u32>(axis);
key.source_index = 0;
key.source_type = InputSourceType::Sensor;
key.source_subtype = sensor;
return key;
}
// ------------------------------------------------------------------------
// Bind Encoders
// ------------------------------------------------------------------------
static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input_class_names = {{
"Keyboard",
"Pointer",
"Sensor",
#ifdef _WIN32
"DInput",
"XInput",
"RawInput",
#endif
#ifdef ENABLE_SDL
"SDL",
#endif
#ifdef __ANDROID__
"Android",
#endif
}};
InputSource* InputManager::GetInputSourceInterface(InputSourceType type)
{
return s_input_sources[static_cast<u32>(type)].get();
}
const char* InputManager::InputSourceToString(InputSourceType clazz)
{
return s_input_class_names[static_cast<u32>(clazz)];
}
bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
{
switch (type)
{
case InputSourceType::Keyboard:
case InputSourceType::Pointer:
return true;
#ifdef _WIN32
case InputSourceType::DInput:
return false;
case InputSourceType::XInput:
return false;
case InputSourceType::RawInput:
return false;
#endif
#ifdef ENABLE_SDL
case InputSourceType::SDL:
return true;
#endif
#ifdef __ANDROID__
case InputSourceType::Android:
return true;
#endif
default:
return false;
}
}
std::optional<InputSourceType> InputManager::ParseInputSourceString(std::string_view str)
{
for (u32 i = 0; i < static_cast<u32>(InputSourceType::Count); i++)
{
if (str == s_input_class_names[i])
return static_cast<InputSourceType>(i);
}
return std::nullopt;
}
std::optional<InputBindingKey> InputManager::ParseHostKeyboardKey(std::string_view source, std::string_view sub_binding)
{
if (source != "Keyboard")
return std::nullopt;
const std::optional<s32> code = ConvertHostKeyboardStringToCode(sub_binding);
if (!code.has_value())
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::Keyboard;
key.data = static_cast<u32>(code.value());
return key;
}
std::optional<InputBindingKey> InputManager::ParsePointerKey(std::string_view source, std::string_view sub_binding)
{
const std::optional<s32> pointer_index = StringUtil::FromChars<s32>(source.substr(8));
if (!pointer_index.has_value() || pointer_index.value() < 0)
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::Pointer;
key.source_index = static_cast<u32>(pointer_index.value());
if (sub_binding.starts_with("Button"))
{
const std::optional<s32> button_number = StringUtil::FromChars<s32>(sub_binding.substr(6));
if (!button_number.has_value() || button_number.value() < 0)
return std::nullopt;
key.source_subtype = InputSubclass::PointerButton;
key.data = static_cast<u32>(button_number.value());
return key;
}
for (u32 i = 0; i < s_pointer_axis_names.size(); i++)
{
if (sub_binding.starts_with(s_pointer_axis_names[i]))
{
key.source_subtype = InputSubclass::PointerAxis;
key.data = i;
const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_names[i])));
if (dir_part == "+")
key.modifier = InputModifier::None;
else if (dir_part == "-")
key.modifier = InputModifier::Negate;
else
return std::nullopt;
return key;
}
}
for (u32 i = 0; i < s_pointer_button_names.size(); i++)
{
if (sub_binding == s_pointer_button_names[i])
{
key.source_subtype = InputSubclass::PointerButton;
key.data = i;
return key;
}
}
return std::nullopt;
}
std::optional<u32> InputManager::GetIndexFromPointerBinding(std::string_view source)
{
if (!source.starts_with("Pointer-"))
return std::nullopt;
const std::optional<s32> pointer_index = StringUtil::FromChars<s32>(source.substr(8));
if (!pointer_index.has_value() || pointer_index.value() < 0)
return std::nullopt;
return static_cast<u32>(pointer_index.value());
}
std::string InputManager::GetPointerDeviceName(u32 pointer_index)
{
return fmt::format("Pointer-{}", pointer_index);
}
std::optional<InputBindingKey> InputManager::ParseSensorKey(std::string_view source, std::string_view sub_binding)
{
if (source != "Sensor")
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::Sensor;
key.source_index = 0;
for (u32 i = 0; i < s_sensor_accelerometer_names.size(); i++)
{
if (sub_binding.starts_with(s_sensor_accelerometer_names[i]))
{
key.source_subtype = InputSubclass::SensorAccelerometer;
key.data = i;
const std::string_view dir_part(sub_binding.substr(std::strlen(s_sensor_accelerometer_names[i])));
if (dir_part == "+")
key.modifier = InputModifier::None;
else if (dir_part == "-")
key.modifier = InputModifier::Negate;
else
return std::nullopt;
return key;
}
}
return std::nullopt;
}
// ------------------------------------------------------------------------
// Binding Enumeration
// ------------------------------------------------------------------------
float InputManager::ApplySingleBindingScale(float scale, float deadzone, float value)
{
const float svalue = std::clamp(value * scale, 0.0f, 1.0f);
return (deadzone > 0.0f && svalue < deadzone) ? 0.0f : svalue;
}
std::vector<const HotkeyInfo*> InputManager::GetHotkeyList()
{
std::vector<const HotkeyInfo*> ret;
for (const HotkeyInfo* hotkey_list : s_hotkey_list)
{
for (const HotkeyInfo* hotkey = hotkey_list; hotkey->name != nullptr; hotkey++)
ret.push_back(hotkey);
}
return ret;
}
void InputManager::AddHotkeyBindings(const SettingsInterface& si)
{
for (const HotkeyInfo* hotkey_list : s_hotkey_list)
{
for (const HotkeyInfo* hotkey = hotkey_list; hotkey->name != nullptr; hotkey++)
{
const std::vector<std::string> bindings(si.GetStringList("Hotkeys", hotkey->name));
if (bindings.empty())
continue;
AddBindings(bindings, InputButtonEventHandler{hotkey->handler});
}
}
}
void InputManager::AddPadBindings(const SettingsInterface& si, const std::string& section, u32 pad_index,
const Controller::ControllerInfo& cinfo)
{
bool vibration_binding_valid = false;
PadVibrationBinding vibration_binding = {};
vibration_binding.pad_index = pad_index;
for (const Controller::ControllerBindingInfo& bi : cinfo.bindings)
{
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bi.name));
switch (bi.type)
{
case InputBindingInfo::Type::Button:
case InputBindingInfo::Type::HalfAxis:
case InputBindingInfo::Type::Axis:
{
if (!bindings.empty())
{
const float sensitivity =
si.GetFloatValue(section.c_str(), TinyString::from_format("{}Scale", bi.name), 1.0f);
const float deadzone =
si.GetFloatValue(section.c_str(), TinyString::from_format("{}Deadzone", bi.name), 0.0f);
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity,
deadzone](float value) {
if (!System::IsValid())
return;
Controller* c = System::GetController(pad_index);
if (c)
c->SetBindState(bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
break;
case InputBindingInfo::Type::RelativePointer:
{
auto cb = [pad_index, base = bi.bind_index](InputBindingKey key, float value) {
if (!System::IsValid())
return;
Controller* c = System::GetController(pad_index);
if (c)
c->SetBindState(base + key.data, value);
};
// bind pointer 0 by default
if (bindings.empty())
{
s_pointer_move_callbacks.emplace_back(0, std::move(cb));
}
else
{
for (const std::string& binding : bindings)
{
const std::optional<u32> key(GetIndexFromPointerBinding(binding));
if (!key.has_value())
continue;
s_pointer_move_callbacks.emplace_back(key.value(), cb);
}
}
}
break;
case InputBindingInfo::Type::Motor:
{
DebugAssert(bi.bind_index < std::size(vibration_binding.motors));
if (bindings.empty())
continue;
if (bindings.size() > 1)
WARNING_LOG("More than one vibration motor binding for {}:{}", pad_index, bi.name);
vibration_binding_valid |=
ParseBindingAndGetSource(bindings.front(), &vibration_binding.motors[bi.bind_index].binding,
&vibration_binding.motors[bi.bind_index].source);
}
break;
case InputBindingInfo::Type::Pointer:
case InputBindingInfo::Type::Device:
// handled in device
break;
default:
ERROR_LOG("Unhandled binding info type {}", static_cast<u32>(bi.type));
break;
}
}
if (vibration_binding_valid)
s_pad_vibration_array.push_back(std::move(vibration_binding));
for (u32 macro_button_index = 0; macro_button_index < NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++)
{
const std::vector<std::string> bindings(
si.GetStringList(section.c_str(), TinyString::from_format("Macro{}", macro_button_index + 1u).c_str()));
if (!bindings.empty())
{
const float deadzone = si.GetFloatValue(
section.c_str(), TinyString::from_format("Macro{}Deadzone", macro_button_index + 1).c_str(), 0.0f);
for (const std::string& binding : bindings)
{
// We currently can't use chords with a deadzone.
if (binding.find('&') != std::string::npos || deadzone == 0.0f)
{
if (deadzone != 0.0f)
WARNING_LOG("Chord binding {} not supported with trigger deadzone {}.", binding, deadzone);
AddBinding(binding, InputButtonEventHandler{[pad_index, macro_button_index](bool state) {
if (!System::IsValid())
return;
SetMacroButtonState(pad_index, macro_button_index, state);
}});
}
else
{
AddBindings(bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](float value) {
if (!System::IsValid())
return;
const bool state = (value > deadzone);
SetMacroButtonState(pad_index, macro_button_index, state);
}});
}
}
}
}
}
// ------------------------------------------------------------------------
// Event Handling
// ------------------------------------------------------------------------
bool InputManager::HasAnyBindingsForKey(InputBindingKey key)
{
std::unique_lock lock(s_mutex);
return (s_binding_map.find(key.MaskDirection()) != s_binding_map.end());
}
bool InputManager::HasAnyBindingsForSource(InputBindingKey key)
{
std::unique_lock lock(s_mutex);
for (const auto& it : s_binding_map)
{
const InputBindingKey& okey = it.first;
if (okey.source_type == key.source_type && okey.source_index == key.source_index &&
okey.source_subtype == key.source_subtype)
{
return true;
}
}
return false;
}
bool InputManager::IsAxisHandler(const InputEventHandler& handler)
{
return std::holds_alternative<InputAxisEventHandler>(handler);
}
bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key)
{
if (DoEventHook(key, value))
return true;
// If imgui ate the event, don't fire our handlers.
const bool skip_button_handlers = PreprocessEvent(key, value, generic_key);
return ProcessEvent(key, value, skip_button_handlers);
}
bool InputManager::ProcessEvent(InputBindingKey key, float value, bool skip_button_handlers)
{
// find all the bindings associated with this key
const InputBindingKey masked_key = key.MaskDirection();
const auto range = s_binding_map.equal_range(masked_key);
if (range.first == s_binding_map.end())
return false;
// Now we can actually fire/activate bindings.
u32 min_num_keys = 0;
for (auto it = range.first; it != range.second; ++it)
{
InputBinding* binding = it->second.get();
// find the key which matches us
for (u32 i = 0; i < binding->num_keys; i++)
{
if (binding->keys[i].MaskDirection() != masked_key)
continue;
const u8 bit = static_cast<u8>(1) << i;
const bool negative = binding->keys[i].modifier == InputModifier::Negate;
const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f));
float value_to_pass = 0.0f;
switch (binding->keys[i].modifier)
{
case InputModifier::None:
if (value > 0.0f)
value_to_pass = value;
break;
case InputModifier::Negate:
if (value < 0.0f)
value_to_pass = -value;
break;
case InputModifier::FullAxis:
value_to_pass = value * 0.5f + 0.5f;
break;
}
// handle inverting, needed for some wheels.
value_to_pass = binding->keys[i].invert ? (1.0f - value_to_pass) : value_to_pass;
// axes are fired regardless of a state change, unless they're zero
// (but going from not-zero to zero will still fire, because of the full state)
// for buttons, we can use the state of the last chord key, because it'll be 1 on press,
// and 0 on release (when the full state changes).
if (IsAxisHandler(binding->handler))
{
if (value_to_pass >= 0.0f && (!skip_button_handlers || value_to_pass == 0.0f))
std::get<InputAxisEventHandler>(binding->handler)(value_to_pass);
}
else if (binding->num_keys >= min_num_keys)
{
// update state based on whether the whole chord was activated
const u8 new_mask =
((new_state && !skip_button_handlers) ? (binding->current_mask | bit) : (binding->current_mask & ~bit));
const bool prev_full_state = (binding->current_mask == binding->full_mask);
const bool new_full_state = (new_mask == binding->full_mask);
binding->current_mask = new_mask;
// Workaround for multi-key bindings that share the same keys.
if (binding->num_keys > 1 && new_full_state && prev_full_state != new_full_state && range.first != range.second)
{
// Because the binding map isn't ordered, we could iterate in the order of Shift+F1 and then
// F1, which would mean that F1 wouldn't get cancelled and still activate. So, to handle this
// case, we skip activating any future bindings with a fewer number of keys.
min_num_keys = std::max<u32>(min_num_keys, binding->num_keys);
// Basically, if we bind say, F1 and Shift+F1, and press shift and then F1, we'll fire bindings
// for both F1 and Shift+F1, when we really only want to fire the binding for Shift+F1. So,
// when we activate a multi-key chord (key press), we go through the binding map for all the
// other keys in the chord, and cancel them if they have a shorter chord. If they're longer,
// they could still activate and take precedence over us, so we leave them alone.
for (u32 j = 0; j < binding->num_keys; j++)
{
const auto range2 = s_binding_map.equal_range(binding->keys[j].MaskDirection());
for (auto it2 = range2.first; it2 != range2.second; ++it2)
{
InputBinding* other_binding = it2->second.get();
if (other_binding == binding || IsAxisHandler(other_binding->handler) ||
other_binding->num_keys >= binding->num_keys)
{
continue;
}
// We only need to cancel the binding if it was fully active before. Which in the above
// case of Shift+F1 / F1, it will be.
if (other_binding->current_mask == other_binding->full_mask)
std::get<InputButtonEventHandler>(other_binding->handler)(-1);
// Zero out the current bits so that we don't release this binding, if the other part
// of the chord releases first.
other_binding->current_mask = 0;
}
}
}
if (prev_full_state != new_full_state && binding->num_keys >= min_num_keys)
{
const s32 pressed = skip_button_handlers ? -1 : static_cast<s32>(value_to_pass > 0.0f);
std::get<InputButtonEventHandler>(binding->handler)(pressed);
}
}
// bail out, since we shouldn't have the same key twice in the chord
break;
}
}
return true;
}
void InputManager::ClearBindStateFromSource(InputBindingKey key)
{
// Why are we doing it this way? Because any of the bindings could cause a reload and invalidate our iterators :(.
// Axis handlers should be fine, so we'll do those as a first pass.
for (const auto& [match_key, binding] : s_binding_map)
{
if (key.source_type != match_key.source_type || key.source_subtype != match_key.source_subtype ||
key.source_index != match_key.source_index || !IsAxisHandler(binding->handler))
{
continue;
}
for (u32 i = 0; i < binding->num_keys; i++)
{
if (binding->keys[i].MaskDirection() != match_key)
continue;
std::get<InputAxisEventHandler>(binding->handler)(0.0f);
break;
}
}
// Now go through the button handlers, and pick them off.
bool matched;
do
{
matched = false;
for (const auto& [match_key, binding] : s_binding_map)
{
if (key.source_type != match_key.source_type || key.source_subtype != match_key.source_subtype ||
key.source_index != match_key.source_index || IsAxisHandler(binding->handler))
{
continue;
}
for (u32 i = 0; i < binding->num_keys; i++)
{
if (binding->keys[i].MaskDirection() != match_key)
continue;
// Skip if we weren't pressed.
const u8 bit = static_cast<u8>(1) << i;
if ((binding->current_mask & bit) == 0)
continue;
// Only fire handler if we're changing from active state.
const u8 current_mask = binding->current_mask;
binding->current_mask &= ~bit;
if (current_mask == binding->full_mask)
{
std::get<InputButtonEventHandler>(binding->handler)(-1);
matched = true;
break;
}
}
// Need to start again, might've reloaded.
if (matched)
break;
}
} while (matched);
}
bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key)
{
// does imgui want the event?
if (key.source_type == InputSourceType::Keyboard)
{
if (ImGuiManager::ProcessHostKeyEvent(key, value))
return true;
}
else if (key.source_type == InputSourceType::Pointer && key.source_subtype == InputSubclass::PointerButton)
{
if (ImGuiManager::ProcessPointerButtonEvent(key, value))
return true;
}
else if (generic_key != GenericInputBinding::Unknown)
{
if (ImGuiManager::ProcessGenericInputEvent(generic_key, value) && value != 0.0f)
return true;
}
return false;
}
void InputManager::GenerateRelativeMouseEvents()
{
const bool system_running = System::IsRunning();
for (u32 device = 0; device < s_pointer_count; device++)
{
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
{
PointerAxisState& state = s_pointer_state[device][axis];
const int deltai = state.delta.load(std::memory_order_acquire);
state.delta.fetch_sub(deltai, std::memory_order_release);
const float delta = static_cast<float>(deltai) / 65536.0f;
const float unclamped_value = delta * s_pointer_axis_scale[axis];
const float value = std::clamp(unclamped_value, -1.0f, 1.0f);
const InputBindingKey key(MakePointerAxisKey(device, static_cast<InputPointerAxis>(axis)));
if (device == 0 && axis >= static_cast<u32>(InputPointerAxis::WheelX) && delta != 0.0f &&
ImGuiManager::ProcessPointerAxisEvent(key, delta))
{
continue;
}
// only generate axis-bound events when it hasn't changed
if (value != state.last_value)
{
state.last_value = value;
if (system_running)
InvokeEvents(key, value, GenericInputBinding::Unknown);
}
// and pointer events only when it hasn't moved
if (delta != 0.0f && system_running)
{
for (const std::pair<u32, PointerMoveCallback>& pmc : s_pointer_move_callbacks)
{
if (pmc.first == device)
pmc.second(key, delta);
}
}
}
}
}
void InputManager::UpdatePointerCount()
{
if (!IsUsingRawInput())
{
s_pointer_count = 1;
return;
}
#ifdef _WIN32
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
DebugAssert(ris);
s_pointer_count = 0;
for (const auto& [key, identifier, device_name] : ris->EnumerateDevices())
{
if (key.source_type == InputSourceType::Pointer)
s_pointer_count++;
}
#endif
}
u32 InputManager::GetPointerCount()
{
return s_pointer_count;
}
std::pair<float, float> InputManager::GetPointerAbsolutePosition(u32 index)
{
DebugAssert(index < s_host_pointer_positions.size());
return std::make_pair(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)],
s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)]);
}
void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
{
if (index >= MAX_POINTER_DEVICES || s_relative_mouse_mode_active) [[unlikely]]
return;
const float dx = x - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)], x);
const float dy = y - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)], y);
if (dx != 0.0f)
{
s_pointer_state[index][static_cast<u8>(InputPointerAxis::X)].delta.fetch_add(static_cast<s32>(dx * 65536.0f),
std::memory_order_acq_rel);
}
if (dy != 0.0f)
{
s_pointer_state[index][static_cast<u8>(InputPointerAxis::Y)].delta.fetch_add(static_cast<s32>(dy * 65536.0f),
std::memory_order_acq_rel);
}
if (index == 0)
ImGuiManager::UpdateMousePosition(x, y);
}
void InputManager::ResetPointerRelativeDelta(u32 index)
{
if (index >= MAX_POINTER_DEVICES || s_relative_mouse_mode_active) [[unlikely]]
return;
s_pointer_state[index][static_cast<u8>(InputPointerAxis::X)].delta.store(0, std::memory_order_release);
s_pointer_state[index][static_cast<u8>(InputPointerAxis::Y)].delta.store(0, std::memory_order_release);
}
void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input)
{
if (index >= MAX_POINTER_DEVICES || (axis < InputPointerAxis::WheelX && !s_relative_mouse_mode_active))
return;
s_host_pointer_positions[index][static_cast<u8>(axis)] += d;
s_pointer_state[index][static_cast<u8>(axis)].delta.fetch_add(static_cast<s32>(d * 65536.0f),
std::memory_order_release);
// We need to clamp the position ourselves in relative mode.
if (axis <= InputPointerAxis::Y)
{
s_host_pointer_positions[index][static_cast<u8>(axis)] =
std::clamp(s_host_pointer_positions[index][static_cast<u8>(axis)], 0.0f, s_window_size[static_cast<u8>(axis)]);
// Imgui also needs to be updated, since the absolute position won't be set above.
if (index == 0)
ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]);
}
}
void InputManager::UpdateRelativeMouseMode()
{
// Check for relative mode bindings, and enable if there's anything using it.
// Raw input needs to force relative mode/clipping, because it's now disconnected from the system pointer.
bool has_relative_mode_bindings = !s_pointer_move_callbacks.empty() || IsUsingRawInput();
if (!has_relative_mode_bindings)
{
for (const auto& it : s_binding_map)
{
const InputBindingKey& key = it.first;
if (key.source_type == InputSourceType::Pointer && key.source_subtype == InputSubclass::PointerAxis &&
key.data >= static_cast<u32>(InputPointerAxis::X) && key.data <= static_cast<u32>(InputPointerAxis::Y))
{
has_relative_mode_bindings = true;
break;
}
}
}
const bool hide_mouse_cursor = has_relative_mode_bindings || ImGuiManager::HasSoftwareCursor(0);
if (s_relative_mouse_mode == has_relative_mode_bindings && s_hide_host_mouse_cursor == hide_mouse_cursor)
return;
#ifndef __ANDROID__
s_relative_mouse_mode = has_relative_mode_bindings;
s_hide_host_mouse_cursor = hide_mouse_cursor;
#endif
UpdateHostMouseMode();
}
void InputManager::UpdateHostMouseMode()
{
const bool can_change = System::IsRunning();
const bool wanted_relative_mouse_mode = (s_relative_mouse_mode && can_change);
const bool wanted_hide_host_mouse_cursor = (s_hide_host_mouse_cursor && can_change);
if (wanted_relative_mouse_mode == s_relative_mouse_mode_active &&
wanted_hide_host_mouse_cursor == s_hide_host_mouse_cusor_active)
{
return;
}
s_relative_mouse_mode_active = wanted_relative_mouse_mode;
s_hide_host_mouse_cusor_active = wanted_hide_host_mouse_cursor;
Host::SetMouseMode(wanted_relative_mouse_mode, wanted_hide_host_mouse_cursor);
}
bool InputManager::IsRelativeMouseModeActive()
{
return s_relative_mouse_mode_active;
}
bool InputManager::IsUsingRawInput()
{
#if defined(_WIN32)
return static_cast<bool>(s_input_sources[static_cast<u32>(InputSourceType::RawInput)]);
#else
return false;
#endif
}
void InputManager::SetDisplayWindowSize(float width, float height)
{
s_window_size[0] = width;
s_window_size[1] = height;
}
std::pair<float, float> InputManager::GetDisplayWindowSize()
{
return std::make_pair(s_window_size[0], s_window_size[1]);
}
void InputManager::SetDefaultSourceConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
si.SetBoolValue("InputSources", "SDL", true);
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
si.SetBoolValue("InputSources", "SDLPS5PlayerLED", false);
si.SetBoolValue("InputSources", "XInput", false);
si.SetBoolValue("InputSources", "RawInput", false);
}
void InputManager::ClearPortBindings(SettingsInterface& si, u32 port)
{
const std::string section = Controller::GetSettingsSection(port);
const TinyString type = si.GetTinyStringValue(
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(port)).name);
const Controller::ControllerInfo* info = Controller::GetControllerInfo(type);
if (!info)
return;
for (const Controller::ControllerBindingInfo& bi : info->bindings)
si.DeleteValue(section.c_str(), bi.name);
}
void InputManager::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config /*= true*/, bool copy_source_config /*= true*/,
bool copy_pad_bindings /*= true*/, bool copy_hotkey_bindings /*= true*/)
{
if (copy_pad_config)
dest_si->CopyStringValue(src_si, "ControllerPorts", "MultitapMode");
if (copy_source_config)
{
for (u32 type = 0; type < static_cast<u32>(InputSourceType::Count); type++)
{
dest_si->CopyBoolValue(src_si, "InputSources",
InputManager::InputSourceToString(static_cast<InputSourceType>(type)));
}
#ifdef ENABLE_SDL
// I hate this, but there isn't a better location for it...
if (dest_si->GetBoolValue("InputSources", "SDL"))
InputSource::CopySDLSourceSettings(dest_si, src_si);
#endif
}
for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
{
if (Controller::PadIsMultitapSlot(port))
{
const auto [mt_port, mt_slot] = Controller::ConvertPadToPortAndSlot(port);
if (!g_settings.IsMultitapPortEnabled(mt_port))
continue;
}
const std::string section(Controller::GetSettingsSection(port));
const TinyString type = src_si.GetTinyStringValue(
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(port)).name);
if (copy_pad_config)
dest_si->SetStringValue(section.c_str(), "Type", type.c_str());
const Controller::ControllerInfo* info = Controller::GetControllerInfo(type);
if (!info)
return;
if (copy_pad_bindings)
{
for (const Controller::ControllerBindingInfo& bi : info->bindings)
{
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
dest_si->CopyFloatValue(src_si, section.c_str(), TinyString::from_format("{}Scale", bi.name));
dest_si->CopyFloatValue(src_si, section.c_str(), TinyString::from_format("{}Deadzone", bi.name));
}
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
dest_si->CopyStringListValue(src_si, section.c_str(), TinyString::from_format("Macro{}", i + 1));
dest_si->CopyStringValue(src_si, section.c_str(), TinyString::from_format("Macro{}Binds", i + 1));
dest_si->CopyFloatValue(src_si, section.c_str(), TinyString::from_format("Macro{}Deadzone", i + 1));
dest_si->CopyFloatValue(src_si, section.c_str(), TinyString::from_format("Macro{}Pressure", i + 1));
dest_si->CopyUIntValue(src_si, section.c_str(), TinyString::from_format("Macro{}Frequency", i + 1));
dest_si->CopyBoolValue(src_si, section.c_str(), TinyString::from_format("Macro{}Toggle", i + 1));
}
}
if (copy_pad_config)
{
for (const SettingInfo& csi : info->settings)
{
switch (csi.type)
{
case SettingInfo::Type::Boolean:
dest_si->CopyBoolValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::Integer:
case SettingInfo::Type::IntegerList:
dest_si->CopyIntValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::Float:
dest_si->CopyFloatValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::String:
case SettingInfo::Type::Path:
dest_si->CopyStringValue(src_si, section.c_str(), csi.name);
break;
default:
break;
}
}
}
}
if (copy_hotkey_bindings)
{
std::vector<const HotkeyInfo*> hotkeys(InputManager::GetHotkeyList());
for (const HotkeyInfo* hki : hotkeys)
dest_si->CopyStringListValue(src_si, "Hotkeys", hki->name);
}
}
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
const GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
const char* bind_name, bool clear_existing_mappings)
{
// find the mapping it corresponds to
const std::string* found_mapping = nullptr;
for (const std::pair<GenericInputBinding, std::string>& it : mapping)
{
if (it.first == generic_name)
{
found_mapping = &it.second;
break;
}
}
if (found_mapping)
{
INFO_LOG("Map {}/{} to '{}'", section, bind_name, *found_mapping);
if (clear_existing_mappings)
si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
else
si.AddToStringList(section.c_str(), bind_name, found_mapping->c_str());
return 1;
}
else
{
if (clear_existing_mappings)
si.DeleteValue(section.c_str(), bind_name);
return 0;
}
}
bool InputManager::MapController(SettingsInterface& si, u32 controller,
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping,
bool clear_existing_mappings)
{
const std::string section = Controller::GetSettingsSection(controller);
const TinyString type = si.GetTinyStringValue(
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(controller)).name);
const Controller::ControllerInfo* info = Controller::GetControllerInfo(type);
if (!info)
return false;
u32 num_mappings = 0;
for (const Controller::ControllerBindingInfo& bi : info->bindings)
{
if (bi.generic_mapping == GenericInputBinding::Unknown)
continue;
u32 mappings_added =
TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name, clear_existing_mappings);
// try to map to small motor if we tried big motor
if (mappings_added == 0 && bi.generic_mapping == GenericInputBinding::LargeMotor)
{
mappings_added +=
TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, bi.name, clear_existing_mappings);
}
num_mappings += mappings_added;
}
return (num_mappings > 0);
}
std::string InputManager::GetPhysicalDeviceForController(SettingsInterface& si, u32 controller)
{
std::string ret;
const std::string section = Controller::GetSettingsSection(controller);
const TinyString type = si.GetTinyStringValue(
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(controller)).name);
const Controller::ControllerInfo* info = Controller::GetControllerInfo(type);
if (info)
{
for (const Controller::ControllerBindingInfo& bi : info->bindings)
{
for (const std::string& binding : si.GetStringList(section.c_str(), bi.name))
{
std::string_view source, sub_binding;
if (!SplitBinding(binding, &source, &sub_binding))
continue;
if (ret.empty())
{
ret = source;
continue;
}
if (ret != source)
{
ret = TRANSLATE_STR("InputManager", "Multiple Devices");
return ret;
}
}
}
}
if (ret.empty())
ret = TRANSLATE_STR("InputManager", "None");
return ret;
}
std::vector<std::string> InputManager::GetInputProfileNames()
{
FileSystem::FindResultsArray results;
FileSystem::FindFiles(EmuFolders::InputProfiles.c_str(), "*.ini",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS |
FILESYSTEM_FIND_SORT_BY_NAME,
&results);
std::vector<std::string> ret;
ret.reserve(results.size());
for (FILESYSTEM_FIND_DATA& fd : results)
ret.emplace_back(Path::GetFileTitle(fd.FileName));
return ret;
}
void InputManager::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier,
std::string_view device_name)
{
INFO_LOG("Device '{}' connected: '{}'", identifier, device_name);
Host::OnInputDeviceConnected(key, identifier, device_name);
}
void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
{
INFO_LOG("Device '{}' disconnected", identifier);
Host::OnInputDeviceDisconnected(key, identifier);
}
std::unique_ptr<ForceFeedbackDevice> InputManager::CreateForceFeedbackDevice(const std::string_view device,
Error* error)
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i] && s_input_sources[i]->ContainsDevice(device))
return s_input_sources[i]->CreateForceFeedbackDevice(device, error);
}
Error::SetStringFmt(error, "No input source matched device '{}'", device);
return {};
}
// ------------------------------------------------------------------------
// Vibration
// ------------------------------------------------------------------------
void InputManager::SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensity,
float small_motor_intensity)
{
for (PadVibrationBinding& pad : s_pad_vibration_array)
{
if (pad.pad_index != pad_index)
continue;
PadVibrationBinding::Motor& large_motor = pad.motors[0];
PadVibrationBinding::Motor& small_motor = pad.motors[1];
if (large_motor.last_intensity == large_or_single_motor_intensity &&
small_motor.last_intensity == small_motor_intensity)
continue;
if (pad.AreMotorsCombined())
{
// if the motors are combined, we need to adjust to the maximum of both
const float report_intensity = std::max(large_or_single_motor_intensity, small_motor_intensity);
if (large_motor.source)
{
large_motor.last_update_time = Timer::GetCurrentValue();
large_motor.source->UpdateMotorState(large_motor.binding, report_intensity);
}
}
else if (large_motor.source == small_motor.source)
{
// both motors are bound to the same source, do an optimal update
large_motor.last_update_time = Timer::GetCurrentValue();
large_motor.source->UpdateMotorState(large_motor.binding, small_motor.binding, large_or_single_motor_intensity,
small_motor_intensity);
}
else
{
// update motors independently
if (large_motor.source && large_motor.last_intensity != large_or_single_motor_intensity)
{
large_motor.last_update_time = Timer::GetCurrentValue();
large_motor.source->UpdateMotorState(large_motor.binding, large_or_single_motor_intensity);
}
if (small_motor.source && small_motor.last_intensity != small_motor_intensity)
{
small_motor.last_update_time = Timer::GetCurrentValue();
small_motor.source->UpdateMotorState(small_motor.binding, small_motor_intensity);
}
}
large_motor.last_intensity = large_or_single_motor_intensity;
small_motor.last_intensity = small_motor_intensity;
}
}
void InputManager::PauseVibration()
{
for (PadVibrationBinding& binding : s_pad_vibration_array)
{
for (u32 motor_index = 0; motor_index < MAX_MOTORS_PER_PAD; motor_index++)
{
PadVibrationBinding::Motor& motor = binding.motors[motor_index];
if (!motor.source || motor.last_intensity == 0.0f)
continue;
// we deliberately don't zero the intensity here, so it can resume later
motor.last_update_time = 0;
motor.source->UpdateMotorState(motor.binding, 0.0f);
}
}
}
void InputManager::UpdateContinuedVibration()
{
// update vibration intensities, so if the game does a long effect, it continues
const u64 current_time = Timer::GetCurrentValue();
for (PadVibrationBinding& pad : s_pad_vibration_array)
{
if (pad.AreMotorsCombined())
{
// motors are combined
PadVibrationBinding::Motor& large_motor = pad.motors[0];
if (!large_motor.source)
continue;
// so only check the first one
const double dt = Timer::ConvertValueToSeconds(current_time - large_motor.last_update_time);
if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS)
continue;
// but take max of both motors for the intensity
const float intensity = pad.GetCombinedIntensity();
if (intensity == 0.0f)
continue;
large_motor.last_update_time = current_time;
large_motor.source->UpdateMotorState(large_motor.binding, intensity);
}
else
{
// independent motor control
for (u32 i = 0; i < MAX_MOTORS_PER_PAD; i++)
{
PadVibrationBinding::Motor& motor = pad.motors[i];
if (!motor.source || motor.last_intensity == 0.0f)
continue;
const double dt = Timer::ConvertValueToSeconds(current_time - motor.last_update_time);
if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS)
continue;
// re-notify the source of the continued effect
motor.last_update_time = current_time;
motor.source->UpdateMotorState(motor.binding, motor.last_intensity);
}
}
}
}
// ------------------------------------------------------------------------
// Macros
// ------------------------------------------------------------------------
void InputManager::LoadMacroButtonConfig(const SettingsInterface& si, const std::string& section, u32 pad,
const Controller::ControllerInfo& cinfo)
{
s_macro_buttons[pad] = {};
if (cinfo.bindings.empty())
return;
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
std::string binds_string;
if (!si.GetStringValue(section.c_str(), TinyString::from_format("Macro{}Binds", i + 1u), &binds_string))
continue;
const u32 frequency =
std::min<u32>(si.GetUIntValue(section.c_str(), TinyString::from_format("Macro{}Frequency", i + 1u), 0u),
std::numeric_limits<u16>::max());
const u8 pressure = static_cast<u8>(
std::clamp(si.GetFloatValue(section.c_str(), TinyString::from_format("Macro{}Pressure", i + 1u), 1.0f), 0.0f,
1.0f) *
255.0f);
const bool toggle = si.GetBoolValue(section.c_str(), TinyString::from_format("Macro{}Toggle", i + 1u), false);
// convert binds
std::vector<u32> bind_indices;
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
if (buttons_split.empty())
continue;
for (const std::string_view& button : buttons_split)
{
const Controller::ControllerBindingInfo* binding = nullptr;
for (const Controller::ControllerBindingInfo& bi : cinfo.bindings)
{
if (button == bi.name)
{
binding = &bi;
break;
}
}
if (!binding)
{
DEV_LOG("Invalid bind '{}' in macro button {} for pad {}", button, pad, i);
continue;
}
bind_indices.push_back(binding->bind_index);
}
if (bind_indices.empty())
continue;
MacroButton& macro = s_macro_buttons[pad][i];
macro.buttons = std::move(bind_indices);
macro.toggle_frequency = static_cast<u16>(frequency);
macro.trigger_toggle = toggle;
macro.trigger_pressure = pressure;
}
}
void InputManager::SetMacroButtonState(u32 pad, u32 index, bool state)
{
if (pad >= NUM_CONTROLLER_AND_CARD_PORTS || index >= NUM_MACRO_BUTTONS_PER_CONTROLLER)
return;
MacroButton& mb = s_macro_buttons[pad][index];
if (mb.buttons.empty())
return;
const bool trigger_state = (mb.trigger_toggle ? (state ? !mb.trigger_state : mb.trigger_state) : state);
if (mb.trigger_state == trigger_state)
return;
mb.toggle_counter = mb.toggle_frequency;
mb.trigger_state = trigger_state;
if (mb.toggle_state != trigger_state)
{
mb.toggle_state = trigger_state;
ApplyMacroButton(pad, mb);
}
}
void InputManager::ApplyMacroButton(u32 pad, const MacroButton& mb)
{
Controller* const controller = System::GetController(pad);
if (!controller)
return;
const float value = static_cast<float>(mb.toggle_state ? mb.trigger_pressure : 0) * (1.0f / 255.0f);
for (const u32 btn : mb.buttons)
controller->SetBindState(btn, value);
}
void InputManager::UpdateMacroButtons()
{
for (u32 pad = 0; pad < NUM_CONTROLLER_AND_CARD_PORTS; pad++)
{
for (u32 index = 0; index < NUM_MACRO_BUTTONS_PER_CONTROLLER; index++)
{
MacroButton& mb = s_macro_buttons[pad][index];
if (!mb.trigger_state || mb.toggle_frequency == 0)
continue;
mb.toggle_counter--;
if (mb.toggle_counter > 0)
continue;
mb.toggle_counter = mb.toggle_frequency;
mb.toggle_state = !mb.toggle_state;
ApplyMacroButton(pad, mb);
}
}
}
// ------------------------------------------------------------------------
// Hooks/Event Intercepting
// ------------------------------------------------------------------------
void InputManager::SetHook(InputInterceptHook::Callback callback)
{
std::unique_lock lock(s_mutex);
DebugAssert(!m_event_intercept_callback);
m_event_intercept_callback = std::move(callback);
}
void InputManager::RemoveHook()
{
std::unique_lock lock(s_mutex);
if (m_event_intercept_callback)
m_event_intercept_callback = {};
}
bool InputManager::HasHook()
{
std::unique_lock lock(s_mutex);
return (bool)m_event_intercept_callback;
}
bool InputManager::DoEventHook(InputBindingKey key, float value)
{
std::unique_lock lock(s_mutex);
if (!m_event_intercept_callback)
return false;
const InputInterceptHook::CallbackResult action = m_event_intercept_callback(key, value);
if (action >= InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent)
m_event_intercept_callback = {};
return (action == InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent ||
action == InputInterceptHook::CallbackResult::StopProcessingEvent);
}
// ------------------------------------------------------------------------
// Binding Updater
// ------------------------------------------------------------------------
void InputManager::ReloadBindings(const SettingsInterface& binding_si, const SettingsInterface& hotkey_binding_si)
{
PauseVibration();
std::unique_lock lock(s_mutex);
s_binding_map.clear();
s_pad_vibration_array.clear();
s_pointer_move_callbacks.clear();
Host::AddFixedInputBindings(binding_si);
// Hotkeys use the base configuration, except if the custom hotkeys option is enabled.
AddHotkeyBindings(hotkey_binding_si);
// If there's an input profile, we load pad bindings from it alone, rather than
// falling back to the base configuration.
for (u32 pad = 0; pad < NUM_CONTROLLER_AND_CARD_PORTS; pad++)
{
if (g_settings.controller_types[pad] == ControllerType::None)
continue;
const Controller::ControllerInfo& cinfo = Controller::GetControllerInfo(g_settings.controller_types[pad]);
const std::string section(Controller::GetSettingsSection(pad));
AddPadBindings(binding_si, section, pad, cinfo);
LoadMacroButtonConfig(binding_si, section, pad, cinfo);
}
for (u32 axis = 0; axis < static_cast<u32>(InputPointerAxis::Count); axis++)
{
// From lilypad: 1 mouse pixel = 1/8th way down.
const float default_scale = (axis <= static_cast<u32>(InputPointerAxis::Y)) ? 8.0f : 1.0f;
s_pointer_axis_scale[axis] =
1.0f / std::max(binding_si.GetFloatValue(
"ControllerPorts",
TinyString::from_format("Pointer{}Scale", s_pointer_axis_names[axis]).c_str(), default_scale),
1.0f);
}
UpdateRelativeMouseMode();
}
// ------------------------------------------------------------------------
// Source Management
// ------------------------------------------------------------------------
bool InputManager::ReloadDevices()
{
std::unique_lock lock(s_mutex);
bool changed = false;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
changed |= s_input_sources[i]->ReloadDevices();
}
UpdatePointerCount();
return changed;
}
void InputManager::CloseSources()
{
std::unique_lock lock(s_mutex);
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
s_input_sources[i]->Shutdown();
s_input_sources[i].reset();
}
}
}
void InputManager::PollSources()
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
s_input_sources[i]->PollEvents();
}
GenerateRelativeMouseEvents();
if (System::GetState() == System::State::Running)
{
UpdateMacroButtons();
if (!s_pad_vibration_array.empty())
UpdateContinuedVibration();
}
}
InputManager::DeviceList InputManager::EnumerateDevices()
{
std::unique_lock lock(s_mutex);
DeviceList ret;
InputBindingKey keyboard_key = {};
keyboard_key.source_type = InputSourceType::Keyboard;
InputBindingKey mouse_key = {};
mouse_key.source_type = InputSourceType::Pointer;
ret.emplace_back(keyboard_key, "Keyboard", TRANSLATE_STR("InputManager", "Keyboard"));
ret.emplace_back(mouse_key, GetPointerDeviceName(0), TRANSLATE_STR("InputManager", "Mouse"));
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
DeviceList devs = s_input_sources[i]->EnumerateDevices();
if (ret.empty())
ret = std::move(devs);
else
std::move(devs.begin(), devs.end(), std::back_inserter(ret));
}
}
return ret;
}
InputManager::VibrationMotorList InputManager::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
std::unique_lock lock(s_mutex);
VibrationMotorList ret;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
VibrationMotorList devs = s_input_sources[i]->EnumerateVibrationMotors(for_device);
if (ret.empty())
ret = std::move(devs);
else
std::move(devs.begin(), devs.end(), std::back_inserter(ret));
}
}
return ret;
}
static void GetKeyboardGenericBindingMapping(std::vector<std::pair<GenericInputBinding, std::string>>* mapping)
{
mapping->emplace_back(GenericInputBinding::DPadUp, "Keyboard/Up");
mapping->emplace_back(GenericInputBinding::DPadRight, "Keyboard/Right");
mapping->emplace_back(GenericInputBinding::DPadDown, "Keyboard/Down");
mapping->emplace_back(GenericInputBinding::DPadLeft, "Keyboard/Left");
mapping->emplace_back(GenericInputBinding::LeftStickUp, "Keyboard/W");
mapping->emplace_back(GenericInputBinding::LeftStickRight, "Keyboard/D");
mapping->emplace_back(GenericInputBinding::LeftStickDown, "Keyboard/S");
mapping->emplace_back(GenericInputBinding::LeftStickLeft, "Keyboard/A");
mapping->emplace_back(GenericInputBinding::RightStickUp, "Keyboard/T");
mapping->emplace_back(GenericInputBinding::RightStickRight, "Keyboard/H");
mapping->emplace_back(GenericInputBinding::RightStickDown, "Keyboard/G");
mapping->emplace_back(GenericInputBinding::RightStickLeft, "Keyboard/F");
mapping->emplace_back(GenericInputBinding::Start, "Keyboard/Return");
mapping->emplace_back(GenericInputBinding::Select, "Keyboard/Backspace");
mapping->emplace_back(GenericInputBinding::Triangle, "Keyboard/I");
mapping->emplace_back(GenericInputBinding::Circle, "Keyboard/L");
mapping->emplace_back(GenericInputBinding::Cross, "Keyboard/K");
mapping->emplace_back(GenericInputBinding::Square, "Keyboard/J");
mapping->emplace_back(GenericInputBinding::L1, "Keyboard/Q");
mapping->emplace_back(GenericInputBinding::L2, "Keyboard/1");
mapping->emplace_back(GenericInputBinding::L3, "Keyboard/2");
mapping->emplace_back(GenericInputBinding::R1, "Keyboard/E");
mapping->emplace_back(GenericInputBinding::R2, "Keyboard/3");
mapping->emplace_back(GenericInputBinding::R3, "Keyboard/4");
}
static bool GetInternalGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping)
{
if (device == "Keyboard")
{
GetKeyboardGenericBindingMapping(mapping);
return true;
}
return false;
}
GenericInputBindingMapping InputManager::GetGenericBindingMapping(std::string_view device)
{
GenericInputBindingMapping mapping;
if (!GetInternalGenericBindingMapping(device, &mapping))
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i] && s_input_sources[i]->GetGenericBindingMapping(device, &mapping))
break;
}
}
return mapping;
}
bool InputManager::IsInputSourceEnabled(const SettingsInterface& si, InputSourceType type)
{
#ifdef __ANDROID__
// Force Android source to always be enabled so nobody accidentally breaks it via ini.
if (type == InputSourceType::Android)
return true;
#endif
return si.GetBoolValue("InputSources", InputSourceToString(type), GetInputSourceDefaultEnabled(type));
}
void InputManager::UpdateInputSourceState(const SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock,
InputSourceType type, std::unique_ptr<InputSource> (*factory_function)())
{
const bool enabled = IsInputSourceEnabled(si, type);
if (enabled)
{
if (s_input_sources[static_cast<u32>(type)])
{
s_input_sources[static_cast<u32>(type)]->UpdateSettings(si, settings_lock);
}
else
{
std::unique_ptr<InputSource> source(factory_function());
if (!source->Initialize(si, settings_lock))
{
ERROR_LOG("Source '{}' failed to initialize.", InputSourceToString(type));
return;
}
s_input_sources[static_cast<u32>(type)] = std::move(source);
}
}
else
{
if (s_input_sources[static_cast<u32>(type)])
{
s_input_sources[static_cast<u32>(type)]->Shutdown();
s_input_sources[static_cast<u32>(type)].reset();
}
}
}
void InputManager::ReloadSources(const SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
std::unique_lock lock(s_mutex);
#ifdef _WIN32
UpdateInputSourceState(si, settings_lock, InputSourceType::DInput, &InputSource::CreateDInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
#endif
#ifdef ENABLE_SDL
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
#endif
#ifdef __ANDROID__
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
#endif
UpdatePointerCount();
}
ForceFeedbackDevice::~ForceFeedbackDevice()
{
}