InputManager: Support inverted full axis

i.e. pedals
This commit is contained in:
Connor McLaughlin 2023-01-15 14:00:51 +10:00
parent 01270bac35
commit 395e9a934b
39 changed files with 1022 additions and 366 deletions

View File

@ -72,6 +72,32 @@ UnorderedStringMapFind(UnorderedStringMap<ValueType>& map, const KeyType& key)
{ {
return map.find(key); return map.find(key);
} }
template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMultimap<ValueType>::const_iterator
UnorderedStringMultiMapFind(const UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.find(key);
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE std::pair<typename UnorderedStringMultimap<ValueType>::const_iterator,
typename UnorderedStringMultimap<ValueType>::const_iterator>
UnorderedStringMultiMapEqualRange(const UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.equal_range(key);
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMultimap<ValueType>::iterator
UnorderedStringMultiMapFind(UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.find(key);
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE std::pair<typename UnorderedStringMultimap<ValueType>::iterator,
typename UnorderedStringMultimap<ValueType>::iterator>
UnorderedStringMultiMapEqualRange(UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.equal_range(key);
}
#else #else
template<typename ValueType> template<typename ValueType>
using UnorderedStringMap = std::unordered_map<std::string, ValueType>; using UnorderedStringMap = std::unordered_map<std::string, ValueType>;
@ -81,15 +107,43 @@ using UnorderedStringSet = std::unordered_set<std::string>;
using UnorderedStringMultiSet = std::unordered_multiset<std::string>; using UnorderedStringMultiSet = std::unordered_multiset<std::string>;
template<typename KeyType, typename ValueType> template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMap<ValueType>::const_iterator UnorderedStringMapFind(const UnorderedStringMap<ValueType>& map, const KeyType& key) ALWAYS_INLINE typename UnorderedStringMap<ValueType>::const_iterator
UnorderedStringMapFind(const UnorderedStringMap<ValueType>& map, const KeyType& key)
{ {
return map.find(std::string(key)); return map.find(std::string(key));
} }
template<typename KeyType, typename ValueType> template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMap<ValueType>::iterator UnorderedStringMapFind(UnorderedStringMap<ValueType>& map, const KeyType& key) ALWAYS_INLINE typename UnorderedStringMap<ValueType>::iterator
UnorderedStringMapFind(UnorderedStringMap<ValueType>& map, const KeyType& key)
{ {
return map.find(std::string(key)); return map.find(std::string(key));
} }
template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMultimap<ValueType>::const_iterator
UnorderedStringMultiMapFind(const UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.find(std::string(key));
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE std::pair<typename UnorderedStringMultimap<ValueType>::const_iterator,
typename UnorderedStringMultimap<ValueType>::const_iterator>
UnorderedStringMultiMapEqualRange(const UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.equal_range(std::string(key));
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE typename UnorderedStringMultimap<ValueType>::iterator
UnorderedStringMultiMapFind(UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.find(std::string(key));
}
template<typename KeyType, typename ValueType>
ALWAYS_INLINE std::pair<typename UnorderedStringMultimap<ValueType>::iterator,
typename UnorderedStringMultimap<ValueType>::iterator>
UnorderedStringMultiMapEqualRange(UnorderedStringMultimap<ValueType>& map, const KeyType& key)
{
return map.equal_range(std::string(key));
}
#endif #endif
template<typename ValueType> template<typename ValueType>

View File

@ -3,6 +3,7 @@
#include "layered_settings_interface.h" #include "layered_settings_interface.h"
#include "common/assert.h" #include "common/assert.h"
#include <unordered_set>
LayeredSettingsInterface::LayeredSettingsInterface() = default; LayeredSettingsInterface::LayeredSettingsInterface() = default;
@ -190,3 +191,35 @@ bool LayeredSettingsInterface::AddToStringList(const char* section, const char*
Panic("Attempt to call AddToStringList() on layered settings interface"); Panic("Attempt to call AddToStringList() on layered settings interface");
return true; return true;
} }
std::vector<std::pair<std::string, std::string>> LayeredSettingsInterface::GetKeyValueList(const char* section) const
{
std::unordered_set<std::string_view> seen;
std::vector<std::pair<std::string, std::string>> ret;
for (u32 layer = FIRST_LAYER; layer <= LAST_LAYER; layer++)
{
if (SettingsInterface* sif = m_layers[layer])
{
const size_t newly_added_begin = ret.size();
std::vector<std::pair<std::string, std::string>> entries = sif->GetKeyValueList(section);
for (std::pair<std::string, std::string>& entry : entries)
{
if (seen.find(entry.first) != seen.end())
continue;
ret.push_back(std::move(entry));
}
// Mark keys as seen after processing all entries in case the layer has multiple entries for a specific key
for (auto cur = ret.begin() + newly_added_begin, end = ret.end(); cur < end; cur++)
seen.insert(cur->first);
}
}
return ret;
}
void LayeredSettingsInterface::SetKeyValueList(const char* section,
const std::vector<std::pair<std::string, std::string>>& items)
{
Panic("Attempt to call SetKeyValueList() on layered settings interface");
}

View File

@ -49,6 +49,9 @@ public:
bool RemoveFromStringList(const char* section, const char* key, const char* item) override; bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
bool AddToStringList(const char* section, const char* key, const char* item) override; bool AddToStringList(const char* section, const char* key, const char* item) override;
std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const override;
void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) override;
// default parameter overloads // default parameter overloads
using SettingsInterface::GetBoolValue; using SettingsInterface::GetBoolValue;
using SettingsInterface::GetDoubleValue; using SettingsInterface::GetDoubleValue;

View File

@ -21,7 +21,7 @@ void MemorySettingsInterface::Clear()
bool MemorySettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const bool MemorySettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
@ -39,11 +39,11 @@ bool MemorySettingsInterface::GetIntValue(const char* section, const char* key,
bool MemorySettingsInterface::GetUIntValue(const char* section, const char* key, u32* value) const bool MemorySettingsInterface::GetUIntValue(const char* section, const char* key, u32* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
const auto iter = sit->second.find(key); const auto iter = UnorderedStringMultiMapFind(sit->second, key);
if (iter == sit->second.end()) if (iter == sit->second.end())
return false; return false;
@ -57,11 +57,11 @@ bool MemorySettingsInterface::GetUIntValue(const char* section, const char* key,
bool MemorySettingsInterface::GetFloatValue(const char* section, const char* key, float* value) const bool MemorySettingsInterface::GetFloatValue(const char* section, const char* key, float* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
const auto iter = sit->second.find(key); const auto iter = UnorderedStringMultiMapFind(sit->second, key);
if (iter == sit->second.end()) if (iter == sit->second.end())
return false; return false;
@ -75,11 +75,11 @@ bool MemorySettingsInterface::GetFloatValue(const char* section, const char* key
bool MemorySettingsInterface::GetDoubleValue(const char* section, const char* key, double* value) const bool MemorySettingsInterface::GetDoubleValue(const char* section, const char* key, double* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
const auto iter = sit->second.find(key); const auto iter = UnorderedStringMultiMapFind(sit->second, key);
if (iter == sit->second.end()) if (iter == sit->second.end())
return false; return false;
@ -93,11 +93,11 @@ bool MemorySettingsInterface::GetDoubleValue(const char* section, const char* ke
bool MemorySettingsInterface::GetBoolValue(const char* section, const char* key, bool* value) const bool MemorySettingsInterface::GetBoolValue(const char* section, const char* key, bool* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
const auto iter = sit->second.find(key); const auto iter = UnorderedStringMultiMapFind(sit->second, key);
if (iter == sit->second.end()) if (iter == sit->second.end())
return false; return false;
@ -111,11 +111,11 @@ bool MemorySettingsInterface::GetBoolValue(const char* section, const char* key,
bool MemorySettingsInterface::GetStringValue(const char* section, const char* key, std::string* value) const bool MemorySettingsInterface::GetStringValue(const char* section, const char* key, std::string* value) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
const auto iter = sit->second.find(key); const auto iter = UnorderedStringMultiMapFind(sit->second, key);
if (iter == sit->second.end()) if (iter == sit->second.end())
return false; return false;
@ -153,13 +153,34 @@ void MemorySettingsInterface::SetStringValue(const char* section, const char* ke
SetValue(section, key, value); SetValue(section, key, value);
} }
std::vector<std::pair<std::string, std::string>> MemorySettingsInterface::GetKeyValueList(const char* section) const
{
std::vector<std::pair<std::string, std::string>> output;
auto sit = UnorderedStringMapFind(m_sections, section);
if (sit != m_sections.end())
{
for (const auto& it : sit->second)
output.emplace_back(it.first, it.second);
}
return output;
}
void MemorySettingsInterface::SetKeyValueList(const char* section,
const std::vector<std::pair<std::string, std::string>>& items)
{
auto sit = UnorderedStringMapFind(m_sections, section);
sit->second.clear();
for (const auto& [key, value] : items)
sit->second.emplace(key, value);
}
void MemorySettingsInterface::SetValue(const char* section, const char* key, std::string value) void MemorySettingsInterface::SetValue(const char* section, const char* key, std::string value)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first; sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
if (range.first == sit->second.end()) if (range.first == sit->second.end())
{ {
sit->second.emplace(std::string(key), std::move(value)); sit->second.emplace(std::string(key), std::move(value));
@ -182,10 +203,10 @@ std::vector<std::string> MemorySettingsInterface::GetStringList(const char* sect
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit != m_sections.end()) if (sit != m_sections.end())
{ {
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
for (auto iter = range.first; iter != range.second; ++iter) for (auto iter = range.first; iter != range.second; ++iter)
ret.emplace_back(iter->second); ret.emplace_back(iter->second);
} }
@ -195,11 +216,11 @@ std::vector<std::string> MemorySettingsInterface::GetStringList(const char* sect
void MemorySettingsInterface::SetStringList(const char* section, const char* key, const std::vector<std::string>& items) void MemorySettingsInterface::SetStringList(const char* section, const char* key, const std::vector<std::string>& items)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first; sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
for (auto iter = range.first; iter != range.second;) for (auto iter = range.first; iter != range.second;)
sit->second.erase(iter++); sit->second.erase(iter++);
@ -210,11 +231,11 @@ void MemorySettingsInterface::SetStringList(const char* section, const char* key
bool MemorySettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item) bool MemorySettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first; sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
bool result = false; bool result = false;
for (auto iter = range.first; iter != range.second;) for (auto iter = range.first; iter != range.second;)
{ {
@ -234,11 +255,11 @@ bool MemorySettingsInterface::RemoveFromStringList(const char* section, const ch
bool MemorySettingsInterface::AddToStringList(const char* section, const char* key, const char* item) bool MemorySettingsInterface::AddToStringList(const char* section, const char* key, const char* item)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first; sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
for (auto iter = range.first; iter != range.second; ++iter) for (auto iter = range.first; iter != range.second; ++iter)
{ {
if (iter->second == item) if (iter->second == item)
@ -251,7 +272,7 @@ bool MemorySettingsInterface::AddToStringList(const char* section, const char* k
bool MemorySettingsInterface::ContainsValue(const char* section, const char* key) const bool MemorySettingsInterface::ContainsValue(const char* section, const char* key) const
{ {
const auto sit = m_sections.find(section); const auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return false; return false;
@ -260,18 +281,18 @@ bool MemorySettingsInterface::ContainsValue(const char* section, const char* key
void MemorySettingsInterface::DeleteValue(const char* section, const char* key) void MemorySettingsInterface::DeleteValue(const char* section, const char* key)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return; return;
const auto range = sit->second.equal_range(key); const auto range = UnorderedStringMultiMapEqualRange(sit->second, key);
for (auto iter = range.first; iter != range.second;) for (auto iter = range.first; iter != range.second;)
sit->second.erase(iter++); sit->second.erase(iter++);
} }
void MemorySettingsInterface::ClearSection(const char* section) void MemorySettingsInterface::ClearSection(const char* section)
{ {
auto sit = m_sections.find(section); auto sit = UnorderedStringMapFind(m_sections, section);
if (sit == m_sections.end()) if (sit == m_sections.end())
return; return;

View File

@ -29,6 +29,10 @@ public:
void SetDoubleValue(const char* section, const char* key, double value) override; void SetDoubleValue(const char* section, const char* key, double value) override;
void SetBoolValue(const char* section, const char* key, bool value) override; void SetBoolValue(const char* section, const char* key, bool value) override;
void SetStringValue(const char* section, const char* key, const char* value) override; void SetStringValue(const char* section, const char* key, const char* value) override;
std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const override;
void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) override;
bool ContainsValue(const char* section, const char* key) const override; bool ContainsValue(const char* section, const char* key) const override;
void DeleteValue(const char* section, const char* key) override; void DeleteValue(const char* section, const char* key) override;
void ClearSection(const char* section) override; void ClearSection(const char* section) override;

View File

@ -35,6 +35,9 @@ public:
virtual bool RemoveFromStringList(const char* section, const char* key, const char* item) = 0; virtual bool RemoveFromStringList(const char* section, const char* key, const char* item) = 0;
virtual bool AddToStringList(const char* section, const char* key, const char* item) = 0; virtual bool AddToStringList(const char* section, const char* key, const char* item) = 0;
virtual std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const = 0;
virtual void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) = 0;
virtual bool ContainsValue(const char* section, const char* key) const = 0; virtual bool ContainsValue(const char* section, const char* key) const = 0;
virtual void DeleteValue(const char* section, const char* key) = 0; virtual void DeleteValue(const char* section, const char* key) = 0;
virtual void ClearSection(const char* section) = 0; virtual void ClearSection(const char* section) = 0;

View File

@ -59,6 +59,7 @@ add_library(core
host_interface_progress_callback.cpp host_interface_progress_callback.cpp
host_interface_progress_callback.h host_interface_progress_callback.h
host_settings.h host_settings.h
input_types.h
interrupt_controller.cpp interrupt_controller.cpp
interrupt_controller.h interrupt_controller.h
libcrypt_serials.cpp libcrypt_serials.cpp

View File

@ -789,12 +789,12 @@ std::unique_ptr<AnalogController> AnalogController::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
#define AXIS(name, display_name, halfaxis, genb) \ #define AXIS(name, display_name, halfaxis, genb) \
{ \ { \
name, display_name, static_cast<u32>(AnalogController::Button::Count) + static_cast<u32>(halfaxis), \ name, display_name, static_cast<u32>(AnalogController::Button::Count) + static_cast<u32>(halfaxis), \
Controller::ControllerBindingType::HalfAxis, genb \ InputBindingInfo::Type::HalfAxis, genb \
} }
BUTTON("Up", "D-Pad Up", AnalogController::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", "D-Pad Up", AnalogController::Button::Up, GenericInputBinding::DPadUp),
@ -862,11 +862,11 @@ static const SettingInfo s_settings[] = {
"functioning, try increasing this value."), "functioning, try increasing this value."),
"8", "0", "255", "1", "%d", nullptr, 1.0f}, "8", "0", "255", "1", "%d", nullptr, 1.0f},
{SettingInfo::Type::IntegerList, "InvertLeftStick", TRANSLATABLE("AnalogController", "Invert Left Stick"), {SettingInfo::Type::IntegerList, "InvertLeftStick", TRANSLATABLE("AnalogController", "Invert Left Stick"),
TRANSLATABLE("AnalogController", "Inverts the direction of the left analog stick."), TRANSLATABLE("AnalogController", "Inverts the direction of the left analog stick."), "0", "0", "3", nullptr, nullptr,
"0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, s_invert_settings, 0.0f},
{SettingInfo::Type::IntegerList, "InvertRightStick", TRANSLATABLE("AnalogController", "Invert Right Stick"), {SettingInfo::Type::IntegerList, "InvertRightStick", TRANSLATABLE("AnalogController", "Invert Right Stick"),
TRANSLATABLE("AnalogController", "Inverts the direction of the right analog stick."), TRANSLATABLE("AnalogController", "Inverts the direction of the right analog stick."), "0", "0", "3", nullptr,
"0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, nullptr, s_invert_settings, 0.0f},
}; };
const Controller::ControllerInfo AnalogController::INFO = {ControllerType::AnalogController, const Controller::ControllerInfo AnalogController::INFO = {ControllerType::AnalogController,

View File

@ -335,12 +335,12 @@ std::unique_ptr<AnalogJoystick> AnalogJoystick::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
#define AXIS(name, display_name, halfaxis, genb) \ #define AXIS(name, display_name, halfaxis, genb) \
{ \ { \
name, display_name, static_cast<u32>(AnalogJoystick::Button::Count) + static_cast<u32>(halfaxis), \ name, display_name, static_cast<u32>(AnalogJoystick::Button::Count) + static_cast<u32>(halfaxis), \
Controller::ControllerBindingType::HalfAxis, genb \ InputBindingInfo::Type::HalfAxis, genb \
} }
BUTTON("Up", "D-Pad Up", AnalogJoystick::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", "D-Pad Up", AnalogJoystick::Button::Up, GenericInputBinding::DPadUp),
@ -391,11 +391,11 @@ static const SettingInfo s_settings[] = {
"controllers, e.g. DualShock 4, Xbox One Controller."), "controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f}, "1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f},
{SettingInfo::Type::IntegerList, "InvertLeftStick", TRANSLATABLE("AnalogJoystick", "Invert Left Stick"), {SettingInfo::Type::IntegerList, "InvertLeftStick", TRANSLATABLE("AnalogJoystick", "Invert Left Stick"),
TRANSLATABLE("AnalogJoystick", "Inverts the direction of the left analog stick."), TRANSLATABLE("AnalogJoystick", "Inverts the direction of the left analog stick."), "0", "0", "3", nullptr, nullptr,
"0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, s_invert_settings, 0.0f},
{SettingInfo::Type::IntegerList, "InvertRightStick", TRANSLATABLE("AnalogJoystick", "Invert Right Stick"), {SettingInfo::Type::IntegerList, "InvertRightStick", TRANSLATABLE("AnalogJoystick", "Invert Right Stick"),
TRANSLATABLE("AnalogJoystick", "Inverts the direction of the right analog stick."), TRANSLATABLE("AnalogJoystick", "Inverts the direction of the right analog stick."), "0", "0", "3", nullptr, nullptr,
"0", "0", "3", nullptr, nullptr, s_invert_settings, 0.0f}, s_invert_settings, 0.0f},
}; };
const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJoystick, const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJoystick,

View File

@ -148,7 +148,7 @@ std::vector<std::string> Controller::GetControllerBinds(const std::string_view&
for (u32 i = 0; i < info->num_bindings; i++) for (u32 i = 0; i < info->num_bindings; i++)
{ {
const ControllerBindingInfo& bi = info->bindings[i]; const ControllerBindingInfo& bi = info->bindings[i];
if (bi.type == ControllerBindingType::Unknown || bi.type == ControllerBindingType::Motor) if (bi.type == InputBindingInfo::Type::Unknown || bi.type == InputBindingInfo::Type::Motor)
continue; continue;
ret.emplace_back(info->bindings[i].name); ret.emplace_back(info->bindings[i].name);
@ -168,7 +168,7 @@ std::vector<std::string> Controller::GetControllerBinds(ControllerType type)
for (u32 i = 0; i < info->num_bindings; i++) for (u32 i = 0; i < info->num_bindings; i++)
{ {
const ControllerBindingInfo& bi = info->bindings[i]; const ControllerBindingInfo& bi = info->bindings[i];
if (bi.type == ControllerBindingType::Unknown || bi.type == ControllerBindingType::Motor) if (bi.type == InputBindingInfo::Type::Unknown || bi.type == InputBindingInfo::Type::Motor)
continue; continue;
ret.emplace_back(info->bindings[i].name); ret.emplace_back(info->bindings[i].name);

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "common/image.h" #include "common/image.h"
#include "input_types.h"
#include "settings.h" #include "settings.h"
#include "types.h" #include "types.h"
#include <memory> #include <memory>
@ -16,21 +17,9 @@ class SettingsInterface;
class StateWrapper; class StateWrapper;
class HostInterface; class HostInterface;
enum class GenericInputBinding : u8;
class Controller class Controller
{ {
public: public:
enum class ControllerBindingType : u8
{
Unknown,
Button,
Axis,
HalfAxis,
Motor,
Macro
};
enum class VibrationCapabilities : u8 enum class VibrationCapabilities : u8
{ {
NoVibration, NoVibration,
@ -44,7 +33,7 @@ public:
const char* name; const char* name;
const char* display_name; const char* display_name;
u32 bind_index; u32 bind_index;
ControllerBindingType type; InputBindingInfo::Type type;
GenericInputBinding generic_mapping; GenericInputBinding generic_mapping;
}; };

View File

@ -128,6 +128,7 @@
<ClInclude Include="host_display.h" /> <ClInclude Include="host_display.h" />
<ClInclude Include="host_interface_progress_callback.h" /> <ClInclude Include="host_interface_progress_callback.h" />
<ClInclude Include="host_settings.h" /> <ClInclude Include="host_settings.h" />
<ClInclude Include="input_types.h" />
<ClInclude Include="interrupt_controller.h" /> <ClInclude Include="interrupt_controller.h" />
<ClInclude Include="libcrypt_serials.h" /> <ClInclude Include="libcrypt_serials.h" />
<ClInclude Include="mdec.h" /> <ClInclude Include="mdec.h" />

View File

@ -124,5 +124,6 @@
<ClInclude Include="host_settings.h" /> <ClInclude Include="host_settings.h" />
<ClInclude Include="achievements.h" /> <ClInclude Include="achievements.h" />
<ClInclude Include="game_database.h" /> <ClInclude Include="game_database.h" />
<ClInclude Include="input_types.h" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -146,7 +146,7 @@ std::unique_ptr<DigitalController> DigitalController::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
BUTTON("Up", "D-Pad Up", DigitalController::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", "D-Pad Up", DigitalController::Button::Up, GenericInputBinding::DPadUp),

View File

@ -208,7 +208,7 @@ std::unique_ptr<GunCon> GunCon::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
BUTTON("Trigger", "Trigger", GunCon::Button::Trigger, GenericInputBinding::R2), BUTTON("Trigger", "Trigger", GunCon::Button::Trigger, GenericInputBinding::R2),

View File

@ -23,49 +23,6 @@ class CDImage;
/// Marks a core string as being translatable. /// Marks a core string as being translatable.
#define TRANSLATABLE(context, str) str #define TRANSLATABLE(context, str) str
/// Generic input bindings. These roughly match a DualShock 4 or XBox One controller.
/// They are used for automatic binding to PS2 controller types, and for big picture mode navigation.
enum class GenericInputBinding : u8
{
Unknown,
DPadUp,
DPadRight,
DPadLeft,
DPadDown,
LeftStickUp,
LeftStickRight,
LeftStickDown,
LeftStickLeft,
L3,
RightStickUp,
RightStickRight,
RightStickDown,
RightStickLeft,
R3,
Triangle, // Y on XBox pads.
Circle, // B on XBox pads.
Cross, // A on XBox pads.
Square, // X on XBox pads.
Select, // Share on DS4, View on XBox pads.
Start, // Options on DS4, Menu on XBox pads.
System, // PS button on DS4, Guide button on XBox pads.
L1, // LB on Xbox pads.
L2, // Left trigger on XBox pads.
R1, // RB on XBox pads.
R2, // Right trigger on Xbox pads.
SmallMotor, // High frequency vibration.
LargeMotor, // Low frequency vibration.
Count,
};
namespace Host { namespace Host {
/// Reads a file from the resources directory of the application. /// Reads a file from the resources directory of the application.
/// This may be outside of the "normal" filesystem on platforms such as Mac. /// This may be outside of the "normal" filesystem on platforms such as Mac.

71
src/core/input_types.h Normal file
View File

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2022-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "types.h"
enum class GenericInputBinding : u8;
struct InputBindingInfo
{
enum class Type : u8
{
Unknown,
Button,
Axis,
HalfAxis,
Motor,
Pointer, // Receive relative mouse movement events, bind_index is offset by the axis.
Macro,
};
const char* name;
const char* display_name;
Type bind_type;
u16 bind_index;
GenericInputBinding generic_mapping;
};
/// Generic input bindings. These roughly match a DualShock 4 or XBox One controller.
/// They are used for automatic binding to PS2 controller types, and for big picture mode navigation.
enum class GenericInputBinding : u8
{
Unknown,
DPadUp,
DPadRight,
DPadLeft,
DPadDown,
LeftStickUp,
LeftStickRight,
LeftStickDown,
LeftStickLeft,
L3,
RightStickUp,
RightStickRight,
RightStickDown,
RightStickLeft,
R3,
Triangle, // Y on XBox pads.
Circle, // B on XBox pads.
Cross, // A on XBox pads.
Square, // X on XBox pads.
Select, // Share on DS4, View on XBox pads.
Start, // Options on DS4, Menu on XBox pads.
System, // PS button on DS4, Guide button on XBox pads.
L1, // LB on Xbox pads.
L2, // Left trigger on XBox pads.
R1, // RB on XBox pads.
R2, // Right trigger on Xbox pads.
SmallMotor, // High frequency vibration.
LargeMotor, // Low frequency vibration.
Count,
};

View File

@ -228,12 +228,12 @@ std::unique_ptr<NeGcon> NeGcon::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
#define AXIS(name, display_name, halfaxis, genb) \ #define AXIS(name, display_name, halfaxis, genb) \
{ \ { \
name, display_name, static_cast<u32>(NeGcon::Button::Count) + static_cast<u32>(halfaxis), \ name, display_name, static_cast<u32>(NeGcon::Button::Count) + static_cast<u32>(halfaxis), \
Controller::ControllerBindingType::HalfAxis, genb \ InputBindingInfo::Type::HalfAxis, genb \
} }
BUTTON("Up", "D-Pad Up", NeGcon::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", "D-Pad Up", NeGcon::Button::Up, GenericInputBinding::DPadUp),

View File

@ -179,7 +179,7 @@ std::unique_ptr<PlayStationMouse> PlayStationMouse::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, button, genb) \ #define BUTTON(name, display_name, button, genb) \
{ \ { \
name, display_name, static_cast<u32>(button), Controller::ControllerBindingType::Button, genb \ name, display_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
} }
BUTTON("Left", "Left Button", PlayStationMouse::Button::Left, GenericInputBinding::Cross), BUTTON("Left", "Left Button", PlayStationMouse::Button::Left, GenericInputBinding::Cross),

View File

@ -370,7 +370,7 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
for (u32 i = 0; i < cinfo->num_bindings; i++) for (u32 i = 0; i < cinfo->num_bindings; i++)
{ {
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i]; const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
if (bi.type == Controller::ControllerBindingType::Motor) if (bi.type == InputBindingInfo::Type::Motor)
continue; continue;
QListWidgetItem* item = new QListWidgetItem(); QListWidgetItem* item = new QListWidgetItem();
@ -383,7 +383,8 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0); m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0);
updateFrequencyText(); updateFrequencyText();
m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), section, fmt::format("Macro{}", index + 1u)); m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), InputBindingInfo::Type::Macro, section,
fmt::format("Macro{}", index + 1u));
connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); }); connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); });
connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); }); connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); });
@ -453,7 +454,7 @@ void ControllerMacroEditWidget::updateBinds()
for (u32 i = 0, bind_index = 0; i < cinfo->num_bindings; i++) for (u32 i = 0, bind_index = 0; i < cinfo->num_bindings; i++)
{ {
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i]; const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
if (bi.type == Controller::ControllerBindingType::Motor) if (bi.type == InputBindingInfo::Type::Motor)
continue; continue;
const QListWidgetItem* item = m_ui.bindList->item(static_cast<int>(bind_index)); const QListWidgetItem* item = m_ui.bindList->item(static_cast<int>(bind_index));
@ -740,9 +741,9 @@ void ControllerBindingWidget_Base::initBindingWidgets()
for (u32 i = 0; i < cinfo->num_bindings; i++) for (u32 i = 0; i < cinfo->num_bindings; i++)
{ {
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i]; const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
if (bi.type == Controller::ControllerBindingType::Unknown || bi.type == Controller::ControllerBindingType::Motor) if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
continue; bi.type == InputBindingInfo::Type::Button || bi.type == InputBindingInfo::Type::Pointer)
{
InputBindingWidget* widget = findChild<InputBindingWidget*>(QString::fromUtf8(bi.name)); InputBindingWidget* widget = findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
if (!widget) if (!widget)
{ {
@ -750,7 +751,8 @@ void ControllerBindingWidget_Base::initBindingWidgets()
continue; continue;
} }
widget->initialize(sif, config_section, bi.name); widget->initialize(sif, bi.type, config_section, bi.name);
}
} }
switch (cinfo->vibration_caps) switch (cinfo->vibration_caps)

View File

@ -237,7 +237,7 @@ void ControllerSettingsDialog::onVibrationMotorsEnumerated(const QList<InputBind
for (const InputBindingKey key : motors) for (const InputBindingKey key : motors)
{ {
const std::string key_str(InputManager::ConvertInputBindingKeyToString(key)); const std::string key_str(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, key));
if (!key_str.empty()) if (!key_str.empty())
m_vibration_motors.push_back(QString::fromStdString(key_str)); m_vibration_motors.push_back(QString::fromStdString(key_str));
} }

View File

@ -73,8 +73,8 @@ void HotkeySettingsWidget::createButtons()
QLabel* label = new QLabel(qApp->translate("Hotkeys", hotkey->display_name), m_container); QLabel* label = new QLabel(qApp->translate("Hotkeys", hotkey->display_name), m_container);
layout->addWidget(label, target_row, 0); layout->addWidget(label, target_row, 0);
InputBindingWidget* bind = InputBindingWidget* bind = new InputBindingWidget(m_container, m_dialog->getProfileSettingsInterface(),
new InputBindingWidget(m_container, m_dialog->getProfileSettingsInterface(), "Hotkeys", hotkey->name); InputBindingInfo::Type::Button, "Hotkeys", hotkey->name);
bind->setMinimumWidth(300); bind->setMinimumWidth(300);
layout->addWidget(bind, target_row, 1); layout->addWidget(bind, target_row, 1);
} }

View File

@ -11,10 +11,11 @@
#include <QtGui/QMouseEvent> #include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent> #include <QtGui/QWheelEvent>
InputBindingDialog::InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name, InputBindingDialog::InputBindingDialog(SettingsInterface* sif, InputBindingInfo::Type bind_type,
std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent) std::vector<std::string> bindings, QWidget* parent)
: QDialog(parent), m_sif(sif), m_section_name(std::move(section_name)), m_key_name(std::move(key_name)), : QDialog(parent), m_sif(sif), m_bind_type(bind_type), m_section_name(std::move(section_name)),
m_bindings(std::move(bindings)) m_key_name(std::move(key_name)), m_bindings(std::move(bindings))
{ {
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.title->setText( m_ui.title->setText(
@ -53,7 +54,8 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
{ {
// double clicks get triggered if we click bind, then click again quickly. // double clicks get triggered if we click bind, then click again quickly.
unsigned button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())); unsigned long button_index;
if (_BitScanForward(&button_index, static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())))
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index)); m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
return true; return true;
} }
@ -64,7 +66,7 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
if (dx != 0.0f) if (dx != 0.0f)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX));
key.negative = (dx < 0.0f); key.modifier = dx < 0.0f ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
} }
@ -72,7 +74,7 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
if (dy != 0.0f) if (dy != 0.0f)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY));
key.negative = (dy < 0.0f); key.modifier = dy < 0.0f ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
} }
@ -89,20 +91,20 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
// if we've moved more than a decent distance from the center of the widget, bind it. // if we've moved more than a decent distance from the center of the widget, bind it.
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad. // this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
static constexpr const s32 THRESHOLD = 50; static constexpr const s32 THRESHOLD = 50;
const QPointF diff(static_cast<QMouseEvent*>(event)->globalPosition() - m_input_listen_start_position); const QPoint diff(static_cast<QMouseEvent*>(event)->globalPosition().toPoint() - m_input_listen_start_position);
bool has_one = false; bool has_one = false;
if (std::abs(diff.x()) >= THRESHOLD) if (std::abs(diff.x()) >= THRESHOLD)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
key.negative = (diff.x() < 0); key.modifier = diff.x() < 0 ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
has_one = true; has_one = true;
} }
if (std::abs(diff.y()) >= THRESHOLD) if (std::abs(diff.y()) >= THRESHOLD)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
key.negative = (diff.y() < 0); key.modifier = diff.y() < 0 ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
has_one = true; has_one = true;
} }
@ -132,6 +134,7 @@ void InputBindingDialog::onInputListenTimerTimeout()
void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds) void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{ {
m_value_ranges.clear();
m_new_bindings.clear(); m_new_bindings.clear();
m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled(); m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled();
m_input_listen_start_position = QCursor::pos(); m_input_listen_start_position = QCursor::pos();
@ -179,7 +182,7 @@ void InputBindingDialog::addNewBinding()
return; return;
const std::string new_binding( const std::string new_binding(
InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size())); InputManager::ConvertInputBindingKeysToString(m_bind_type, m_new_bindings.data(), m_new_bindings.size()));
if (!new_binding.empty()) if (!new_binding.empty())
{ {
if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end()) if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end())
@ -248,14 +251,37 @@ void InputBindingDialog::saveListToSettings()
void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value) void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value)
{ {
const float abs_value = std::abs(value); if (!isListeningForInput())
return;
for (InputBindingKey other_key : m_new_bindings) float initial_value = value;
float min_value = value;
auto it = std::find_if(m_value_ranges.begin(), m_value_ranges.end(),
[key](const auto& it) { return it.first.bits == key.bits; });
if (it != m_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
m_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
const float abs_value = std::abs(value);
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
for (InputBindingKey& other_key : m_new_bindings)
{ {
if (other_key.MaskDirection() == key.MaskDirection()) if (other_key.MaskDirection() == key.MaskDirection())
{ {
if (abs_value < 0.5f) // for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{ {
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
// if this key is in our new binding list, it's a "release", and we're done // if this key is in our new binding list, it's a "release", and we're done
addNewBinding(); addNewBinding();
stopListeningForInput(); stopListeningForInput();
@ -268,10 +294,11 @@ void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float val
} }
// new binding, add it to the list, but wait for a decent distance first, and then wait for release // new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f) if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{ {
InputBindingKey key_to_add = key; InputBindingKey key_to_add = key;
key_to_add.negative = (value < 0.0f); key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
m_new_bindings.push_back(key_to_add); m_new_bindings.push_back(key_to_add);
} }
} }

View File

@ -17,8 +17,8 @@ class InputBindingDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name, InputBindingDialog(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
std::vector<std::string> bindings, QWidget* parent); std::string key_name, std::vector<std::string> bindings, QWidget* parent);
~InputBindingDialog(); ~InputBindingDialog();
protected Q_SLOTS: protected Q_SLOTS:
@ -51,13 +51,15 @@ protected:
Ui::InputBindingDialog m_ui; Ui::InputBindingDialog m_ui;
SettingsInterface* m_sif; SettingsInterface* m_sif;
InputBindingInfo::Type m_bind_type;
std::string m_section_name; std::string m_section_name;
std::string m_key_name; std::string m_key_name;
std::vector<std::string> m_bindings; std::vector<std::string> m_bindings;
std::vector<InputBindingKey> m_new_bindings; std::vector<InputBindingKey> m_new_bindings;
std::vector<std::pair<InputBindingKey, std::pair<float, float>>> m_value_ranges;
QTimer* m_input_listen_timer = nullptr; QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0; u32 m_input_listen_remaining_seconds = 0;
QPointF m_input_listen_start_position{}; QPoint m_input_listen_start_position{};
bool m_mouse_mapping_enabled = false; bool m_mouse_mapping_enabled = false;
}; };

View File

@ -22,8 +22,8 @@ InputBindingWidget::InputBindingWidget(QWidget* parent) : QPushButton(parent)
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
} }
InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif, InputBindingInfo::Type bind_type,
std::string key_name) std::string section_name, std::string key_name)
: QPushButton(parent) : QPushButton(parent)
{ {
setMinimumWidth(225); setMinimumWidth(225);
@ -31,7 +31,7 @@ InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif,
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
initialize(sif, std::move(section_name), std::move(key_name)); initialize(sif, bind_type, std::move(section_name), std::move(key_name));
} }
InputBindingWidget::~InputBindingWidget() InputBindingWidget::~InputBindingWidget()
@ -44,9 +44,11 @@ bool InputBindingWidget::isMouseMappingEnabled()
return Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false); return Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
} }
void InputBindingWidget::initialize(SettingsInterface* sif, std::string section_name, std::string key_name) void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
std::string key_name)
{ {
m_sif = sif; m_sif = sif;
m_bind_type = bind_type;
m_section_name = std::move(section_name); m_section_name = std::move(section_name);
m_key_name = std::move(key_name); m_key_name = std::move(key_name);
reloadBinding(); reloadBinding();
@ -109,7 +111,8 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
{ {
// double clicks get triggered if we click bind, then click again quickly. // double clicks get triggered if we click bind, then click again quickly.
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())); unsigned long button_index;
if (_BitScanForward(&button_index, static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())))
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index)); m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
return true; return true;
} }
@ -120,7 +123,7 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
if (dx != 0.0f) if (dx != 0.0f)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX));
key.negative = (dx < 0.0f); key.modifier = dx < 0.0f ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
} }
@ -128,7 +131,7 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
if (dy != 0.0f) if (dy != 0.0f)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY));
key.negative = (dy < 0.0f); key.modifier = dy < 0.0f ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
} }
@ -145,20 +148,20 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
// if we've moved more than a decent distance from the center of the widget, bind it. // if we've moved more than a decent distance from the center of the widget, bind it.
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad. // this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
static constexpr const s32 THRESHOLD = 50; static constexpr const s32 THRESHOLD = 50;
const QPointF diff(static_cast<QMouseEvent*>(event)->globalPosition() - m_input_listen_start_position); const QPoint diff(static_cast<QMouseEvent*>(event)->globalPosition().toPoint() - m_input_listen_start_position);
bool has_one = false; bool has_one = false;
if (std::abs(diff.x()) >= THRESHOLD) if (std::abs(diff.x()) >= THRESHOLD)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
key.negative = (diff.x() < 0); key.modifier = diff.x() < 0 ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
has_one = true; has_one = true;
} }
if (std::abs(diff.y()) >= THRESHOLD) if (std::abs(diff.y()) >= THRESHOLD)
{ {
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y)); InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
key.negative = (diff.y() < 0); key.modifier = diff.y() < 0 ? InputModifier::Negate : InputModifier::None;
m_new_bindings.push_back(key); m_new_bindings.push_back(key);
has_one = true; has_one = true;
} }
@ -205,8 +208,8 @@ void InputBindingWidget::setNewBinding()
if (m_new_bindings.empty()) if (m_new_bindings.empty())
return; return;
const std::string new_binding( std::string new_binding(
InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size())); InputManager::ConvertInputBindingKeysToString(m_bind_type, m_new_bindings.data(), m_new_bindings.size()));
if (!new_binding.empty()) if (!new_binding.empty())
{ {
if (m_sif) if (m_sif)
@ -280,6 +283,7 @@ void InputBindingWidget::onInputListenTimerTimeout()
void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds) void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
{ {
m_value_ranges.clear();
m_new_bindings.clear(); m_new_bindings.clear();
m_mouse_mapping_enabled = isMouseMappingEnabled(); m_mouse_mapping_enabled = isMouseMappingEnabled();
m_input_listen_start_position = QCursor::pos(); m_input_listen_start_position = QCursor::pos();
@ -315,14 +319,37 @@ void InputBindingWidget::stopListeningForInput()
void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float value) void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float value)
{ {
const float abs_value = std::abs(value); if (!isListeningForInput())
return;
for (InputBindingKey other_key : m_new_bindings) float initial_value = value;
float min_value = value;
auto it = std::find_if(m_value_ranges.begin(), m_value_ranges.end(),
[key](const auto& it) { return it.first.bits == key.bits; });
if (it != m_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
m_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
const float abs_value = std::abs(value);
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
for (InputBindingKey& other_key : m_new_bindings)
{ {
if (other_key.MaskDirection() == key.MaskDirection()) if (other_key.MaskDirection() == key.MaskDirection())
{ {
if (abs_value < 0.5f) // for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{ {
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
// if this key is in our new binding list, it's a "release", and we're done // if this key is in our new binding list, it's a "release", and we're done
setNewBinding(); setNewBinding();
stopListeningForInput(); stopListeningForInput();
@ -335,10 +362,11 @@ void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float val
} }
// new binding, add it to the list, but wait for a decent distance first, and then wait for release // new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f) if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{ {
InputBindingKey key_to_add = key; InputBindingKey key_to_add = key;
key_to_add.negative = (value < 0.0f); key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
m_new_bindings.push_back(key_to_add); m_new_bindings.push_back(key_to_add);
} }
} }
@ -359,7 +387,8 @@ void InputBindingWidget::unhookInputManager()
void InputBindingWidget::openDialog() void InputBindingWidget::openDialog()
{ {
InputBindingDialog binding_dialog(m_sif, m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this)); InputBindingDialog binding_dialog(m_sif, m_bind_type, m_section_name, m_key_name, m_bindings,
QtUtils::GetRootWidget(this));
binding_dialog.exec(); binding_dialog.exec();
reloadBinding(); reloadBinding();
} }

View File

@ -18,12 +18,14 @@ class InputBindingWidget : public QPushButton
public: public:
InputBindingWidget(QWidget* parent); InputBindingWidget(QWidget* parent);
InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, std::string key_name); InputBindingWidget(QWidget* parent, SettingsInterface* sif, InputBindingInfo::Type bind_type,
std::string section_name, std::string key_name);
~InputBindingWidget(); ~InputBindingWidget();
static bool isMouseMappingEnabled(); static bool isMouseMappingEnabled();
void initialize(SettingsInterface* sif, std::string section_name, std::string key_name); void initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
std::string key_name);
public Q_SLOTS: public Q_SLOTS:
void clearBinding(); void clearBinding();
@ -57,13 +59,15 @@ protected:
void unhookInputManager(); void unhookInputManager();
SettingsInterface* m_sif = nullptr; SettingsInterface* m_sif = nullptr;
InputBindingInfo::Type m_bind_type = InputBindingInfo::Type::Unknown;
std::string m_section_name; std::string m_section_name;
std::string m_key_name; std::string m_key_name;
std::vector<std::string> m_bindings; std::vector<std::string> m_bindings;
std::vector<InputBindingKey> m_new_bindings; std::vector<InputBindingKey> m_new_bindings;
std::vector<std::pair<InputBindingKey, std::pair<float, float>>> m_value_ranges;
QTimer* m_input_listen_timer = nullptr; QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0; u32 m_input_listen_remaining_seconds = 0;
QPointF m_input_listen_start_position{}; QPoint m_input_listen_start_position{};
bool m_mouse_mapping_enabled = false; bool m_mouse_mapping_enabled = false;
}; };

View File

@ -56,7 +56,7 @@ std::string DInputSource::GetDeviceIdentifier(u32 index)
} }
static constexpr std::array<const char*, DInputSource::NUM_HAT_DIRECTIONS> s_hat_directions = { static constexpr std::array<const char*, DInputSource::NUM_HAT_DIRECTIONS> s_hat_directions = {
{"Up", "Right", "Down", "Left"}}; {"Up", "Down", "Left", "Right"}};
bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{ {
@ -89,12 +89,13 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
// need to release the lock while we're enumerating, because we call winId(). // need to release the lock while we're enumerating, because we call winId().
settings_lock.unlock(); settings_lock.unlock();
const std::optional<WindowInfo> toplevel_wi(Host::GetTopLevelWindowInfo()); const std::optional<WindowInfo> toplevel_wi(Host::GetTopLevelWindowInfo());
settings_lock.lock();
if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32) if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32)
{ {
Log_ErrorPrintf("Missing top level window, cannot add DInput devices."); Log_ErrorPrintf("Missing top level window, cannot add DInput devices.");
return false; return false;
} }
settings_lock.lock();
m_toplevel_window = static_cast<HWND>(toplevel_wi->window_handle); m_toplevel_window = static_cast<HWND>(toplevel_wi->window_handle);
ReloadDevices(); ReloadDevices();
@ -106,15 +107,6 @@ void DInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::m
// noop // noop
} }
void DInputSource::Shutdown()
{
while (!m_controllers.empty())
{
Host::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1)));
m_controllers.pop_back();
}
}
static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef)
{ {
static_cast<std::vector<DIDEVICEINSTANCEW>*>(pvRef)->push_back(*lpddi); static_cast<std::vector<DIDEVICEINSTANCEW>*>(pvRef)->push_back(*lpddi);
@ -126,6 +118,7 @@ bool DInputSource::ReloadDevices()
// detect any removals // detect any removals
PollEvents(); PollEvents();
// look for new devices
std::vector<DIDEVICEINSTANCEW> devices; std::vector<DIDEVICEINSTANCEW> devices;
m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY); m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY);
@ -156,7 +149,7 @@ bool DInputSource::ReloadDevices()
{ {
const u32 index = static_cast<u32>(m_controllers.size()); const u32 index = static_cast<u32>(m_controllers.size());
m_controllers.push_back(std::move(cd)); m_controllers.push_back(std::move(cd));
Host::OnInputDeviceConnected(GetDeviceIdentifier(index), name); InputManager::OnInputDeviceConnected(GetDeviceIdentifier(index), name);
changed = true; changed = true;
} }
} }
@ -164,6 +157,15 @@ bool DInputSource::ReloadDevices()
return changed; return changed;
} }
void DInputSource::Shutdown()
{
while (!m_controllers.empty())
{
InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1)));
m_controllers.pop_back();
}
}
bool DInputSource::AddDevice(ControllerData& cd, const std::string& name) bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
{ {
HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE); HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
@ -204,11 +206,11 @@ bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
cd.num_buttons = caps.dwButtons; cd.num_buttons = caps.dwButtons;
static constexpr auto axis_offsets = static constexpr const u32 axis_offsets[] = {offsetof(DIJOYSTATE, lX), offsetof(DIJOYSTATE, lY),
make_array(offsetof(DIJOYSTATE, lX), offsetof(DIJOYSTATE, lY), offsetof(DIJOYSTATE, lZ), offsetof(DIJOYSTATE, lRz), offsetof(DIJOYSTATE, lZ), offsetof(DIJOYSTATE, lRz),
offsetof(DIJOYSTATE, lRx), offsetof(DIJOYSTATE, lRy), offsetof(DIJOYSTATE, rglSlider[0]), offsetof(DIJOYSTATE, lRx), offsetof(DIJOYSTATE, lRy),
offsetof(DIJOYSTATE, rglSlider[1])); offsetof(DIJOYSTATE, rglSlider[0]), offsetof(DIJOYSTATE, rglSlider[1])};
for (const auto offset : axis_offsets) for (const u32 offset : axis_offsets)
{ {
// ask for 16 bits of axis range // ask for 16 bits of axis range
DIPROPRANGE range = {}; DIPROPRANGE range = {};
@ -222,9 +224,7 @@ bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
// did it apply? // did it apply?
if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph))) if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph)))
{ cd.axis_offsets.push_back(offset);
cd.axis_offsets.push_back(static_cast<u32>(offset));
}
} }
cd.num_hats = caps.dwPOVs; cd.num_hats = caps.dwPOVs;
@ -263,7 +263,7 @@ void DInputSource::PollEvents()
if (hr != DI_OK) if (hr != DI_OK)
{ {
Host::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(i))); InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(i)));
m_controllers.erase(m_controllers.begin() + i); m_controllers.erase(m_controllers.begin() + i);
continue; continue;
} }
@ -336,13 +336,28 @@ std::optional<InputBindingKey> DInputSource::ParseKeyString(const std::string_vi
if (StringUtil::StartsWith(binding, "+Axis") || StringUtil::StartsWith(binding, "-Axis")) if (StringUtil::StartsWith(binding, "+Axis") || StringUtil::StartsWith(binding, "-Axis"))
{ {
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5)); std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5), 10, &end);
if (!axis_index.has_value()) if (!axis_index.has_value())
return std::nullopt; return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis; key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value(); key.data = axis_index.value();
key.negative = (binding[0] == '-'); key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
else if (StringUtil::StartsWith(binding, "FullAxis"))
{
std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(8), 10, &end);
if (!axis_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value();
key.modifier = InputModifier::FullAxis;
key.invert = (end == "~");
return key; return key;
} }
else if (StringUtil::StartsWith(binding, "Hat")) else if (StringUtil::StartsWith(binding, "Hat"))
@ -388,7 +403,9 @@ std::string DInputSource::ConvertKeyToString(InputBindingKey key)
{ {
if (key.source_subtype == InputSubclass::ControllerAxis) if (key.source_subtype == InputSubclass::ControllerAxis)
{ {
ret = fmt::format("DInput-{}/{}Axis{}", u32(key.source_index), key.negative ? '-' : '+', u32(key.data)); const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
ret = fmt::format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), key.invert ? "~" : "");
} }
else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS) else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS)
{ {

View File

@ -4,8 +4,8 @@
#pragma once #pragma once
#define DIRECTINPUT_VERSION 0x0800 #define DIRECTINPUT_VERSION 0x0800
#include "common/windows_headers.h" #include "common/windows_headers.h"
#include "input_source.h"
#include "core/types.h" #include "core/types.h"
#include "input_source.h"
#include <array> #include <array>
#include <dinput.h> #include <dinput.h>
#include <functional> #include <functional>
@ -80,7 +80,7 @@ private:
ControllerDataArray m_controllers; ControllerDataArray m_controllers;
HMODULE m_dinput_module{}; HMODULE m_dinput_module{};
LPCDIDATAFORMAT m_joystick_data_format{};
ComPtr<IDirectInput8W> m_dinput; ComPtr<IDirectInput8W> m_dinput;
LPCDIDATAFORMAT m_joystick_data_format{};
HWND m_toplevel_window = NULL; HWND m_toplevel_window = NULL;
}; };

View File

@ -44,6 +44,8 @@
#include <bitset> #include <bitset>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <utility>
#include <vector>
Log_SetChannel(FullscreenUI); Log_SetChannel(FullscreenUI);
#ifdef WITH_CHEEVOS #ifdef WITH_CHEEVOS
@ -362,11 +364,10 @@ static void PopulateGraphicsAdapterList();
static void PopulateGameListDirectoryCache(SettingsInterface* si); static void PopulateGameListDirectoryCache(SettingsInterface* si);
static void PopulatePostProcessingChain(); static void PopulatePostProcessingChain();
static void SavePostProcessingChain(); static void SavePostProcessingChain();
static void BeginInputBinding(SettingsInterface* bsi, Controller::ControllerBindingType type, static void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const std::string_view& section,
const std::string_view& section, const std::string_view& key, const std::string_view& key, const std::string_view& display_name);
const std::string_view& display_name);
static void DrawInputBindingWindow(); static void DrawInputBindingWindow();
static void DrawInputBindingButton(SettingsInterface* bsi, Controller::ControllerBindingType type, const char* section, static void DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* name, const char* display_name, bool show_type = true); const char* name, const char* display_name, bool show_type = true);
static void ClearInputBindingVariables(); static void ClearInputBindingVariables();
static void StartAutomaticBinding(u32 port); static void StartAutomaticBinding(u32 port);
@ -381,11 +382,12 @@ static FrontendCommon::PostProcessingChain s_postprocessing_chain;
static std::vector<const HotkeyInfo*> s_hotkey_list_cache; static std::vector<const HotkeyInfo*> s_hotkey_list_cache;
static std::atomic_bool s_settings_changed{false}; static std::atomic_bool s_settings_changed{false};
static std::atomic_bool s_game_settings_changed{false}; static std::atomic_bool s_game_settings_changed{false};
static Controller::ControllerBindingType s_input_binding_type = Controller::ControllerBindingType::Unknown; static InputBindingInfo::Type s_input_binding_type = InputBindingInfo::Type::Unknown;
static std::string s_input_binding_section; static std::string s_input_binding_section;
static std::string s_input_binding_key; static std::string s_input_binding_key;
static std::string s_input_binding_display_name; static std::string s_input_binding_display_name;
static std::vector<InputBindingKey> s_input_binding_new_bindings; static std::vector<InputBindingKey> s_input_binding_new_bindings;
static std::vector<std::pair<InputBindingKey, std::pair<float, float>>> s_input_binding_value_ranges;
static Common::Timer s_input_binding_timer; static Common::Timer s_input_binding_timer;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -788,7 +790,7 @@ void FullscreenUI::Render()
if (s_about_window_open) if (s_about_window_open)
DrawAboutWindow(); DrawAboutWindow();
if (s_input_binding_type != Controller::ControllerBindingType::Unknown) if (s_input_binding_type != InputBindingInfo::Type::Unknown)
DrawInputBindingWindow(); DrawInputBindingWindow();
ImGuiFullscreen::EndLayout(); ImGuiFullscreen::EndLayout();
@ -1277,9 +1279,8 @@ std::string FullscreenUI::GetEffectiveStringSetting(SettingsInterface* bsi, cons
return ret; return ret;
} }
void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, Controller::ControllerBindingType type, void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
const char* section, const char* name, const char* display_name, const char* name, const char* display_name, bool show_type)
bool show_type)
{ {
TinyString title; TinyString title;
title.Fmt("{}/{}", section, name); title.Fmt("{}/{}", section, name);
@ -1299,17 +1300,17 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, Controller::Co
{ {
switch (type) switch (type)
{ {
case Controller::ControllerBindingType::Button: case InputBindingInfo::Type::Button:
title = fmt::format(ICON_FA_DOT_CIRCLE " {}", display_name); title = fmt::format(ICON_FA_DOT_CIRCLE " {}", display_name);
break; break;
case Controller::ControllerBindingType::Axis: case InputBindingInfo::Type::Axis:
case Controller::ControllerBindingType::HalfAxis: case InputBindingInfo::Type::HalfAxis:
title = fmt::format(ICON_FA_BULLSEYE " {}", display_name); title = fmt::format(ICON_FA_BULLSEYE " {}", display_name);
break; break;
case Controller::ControllerBindingType::Motor: case InputBindingInfo::Type::Motor:
title = fmt::format(ICON_FA_BELL " {}", display_name); title = fmt::format(ICON_FA_BELL " {}", display_name);
break; break;
case Controller::ControllerBindingType::Macro: case InputBindingInfo::Type::Macro:
title = fmt::format(ICON_FA_PIZZA_SLICE " {}", display_name); title = fmt::format(ICON_FA_PIZZA_SLICE " {}", display_name);
break; break;
default: default:
@ -1343,18 +1344,19 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, Controller::Co
void FullscreenUI::ClearInputBindingVariables() void FullscreenUI::ClearInputBindingVariables()
{ {
s_input_binding_type = Controller::ControllerBindingType::Unknown; s_input_binding_type = InputBindingInfo::Type::Unknown;
s_input_binding_section = {}; s_input_binding_section = {};
s_input_binding_key = {}; s_input_binding_key = {};
s_input_binding_display_name = {}; s_input_binding_display_name = {};
s_input_binding_new_bindings = {}; s_input_binding_new_bindings = {};
s_input_binding_value_ranges = {};
} }
void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, Controller::ControllerBindingType type, void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type,
const std::string_view& section, const std::string_view& key, const std::string_view& section, const std::string_view& key,
const std::string_view& display_name) const std::string_view& display_name)
{ {
if (s_input_binding_type != Controller::ControllerBindingType::Unknown) if (s_input_binding_type != InputBindingInfo::Type::Unknown)
{ {
InputManager::RemoveHook(); InputManager::RemoveHook();
ClearInputBindingVariables(); ClearInputBindingVariables();
@ -1365,25 +1367,50 @@ void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, Controller::Control
s_input_binding_key = key; s_input_binding_key = key;
s_input_binding_display_name = display_name; s_input_binding_display_name = display_name;
s_input_binding_new_bindings = {}; s_input_binding_new_bindings = {};
s_input_binding_value_ranges = {};
s_input_binding_timer.Reset(); s_input_binding_timer.Reset();
InputManager::SetHook([game_settings = IsEditingGameSettings(bsi)]( const bool game_settings = IsEditingGameSettings(bsi);
InputBindingKey key, float value) -> InputInterceptHook::CallbackResult {
InputManager::SetHook([game_settings](InputBindingKey key, float value) -> InputInterceptHook::CallbackResult {
if (s_input_binding_type == InputBindingInfo::Type::Unknown)
return InputInterceptHook::CallbackResult::StopProcessingEvent;
// holding the settings lock here will protect the input binding list // holding the settings lock here will protect the input binding list
auto lock = Host::GetSettingsLock(); auto lock = Host::GetSettingsLock();
const float abs_value = std::abs(value); float initial_value = value;
float min_value = value;
auto it = std::find_if(s_input_binding_value_ranges.begin(), s_input_binding_value_ranges.end(),
[key](const auto& it) { return it.first.bits == key.bits; });
if (it != s_input_binding_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
s_input_binding_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
for (InputBindingKey other_key : s_input_binding_new_bindings) const float abs_value = std::abs(value);
{ const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
if (other_key.MaskDirection() == key.MaskDirection())
{ for (InputBindingKey& other_key : s_input_binding_new_bindings)
if (abs_value < 0.5f)
{ {
// if this key is in our new binding list, it's a "release", and we're done // if this key is in our new binding list, it's a "release", and we're done
if (other_key.MaskDirection() == key.MaskDirection())
{
// for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
const std::string new_binding(InputManager::ConvertInputBindingKeysToString( const std::string new_binding(InputManager::ConvertInputBindingKeysToString(
s_input_binding_new_bindings.data(), s_input_binding_new_bindings.size())); s_input_binding_type, s_input_binding_new_bindings.data(), s_input_binding_new_bindings.size()));
bsi->SetStringValue(s_input_binding_section.c_str(), s_input_binding_key.c_str(), new_binding.c_str()); bsi->SetStringValue(s_input_binding_section.c_str(), s_input_binding_key.c_str(), new_binding.c_str());
SetSettingsChanged(bsi); SetSettingsChanged(bsi);
ClearInputBindingVariables(); ClearInputBindingVariables();
@ -1396,10 +1423,11 @@ void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, Controller::Control
} }
// new binding, add it to the list, but wait for a decent distance first, and then wait for release // new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f) if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{ {
InputBindingKey key_to_add = key; InputBindingKey key_to_add = key;
key_to_add.negative = (value < 0.0f); key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
s_input_binding_new_bindings.push_back(key_to_add); s_input_binding_new_bindings.push_back(key_to_add);
} }
@ -1409,7 +1437,7 @@ void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, Controller::Control
void FullscreenUI::DrawInputBindingWindow() void FullscreenUI::DrawInputBindingWindow()
{ {
DebugAssert(s_input_binding_type != Controller::ControllerBindingType::Unknown); DebugAssert(s_input_binding_type != InputBindingInfo::Type::Unknown);
const double time_remaining = INPUT_BINDING_TIMEOUT_SECONDS - s_input_binding_timer.GetTimeSeconds(); const double time_remaining = INPUT_BINDING_TIMEOUT_SECONDS - s_input_binding_timer.GetTimeSeconds();
if (time_remaining <= 0.0) if (time_remaining <= 0.0)
@ -3180,7 +3208,7 @@ void FullscreenUI::DrawControllerSettingsPage()
for (u32 macro_index = 0; macro_index < InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++) for (u32 macro_index = 0; macro_index < InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++)
{ {
DrawInputBindingButton(bsi, Controller::ControllerBindingType::Macro, section.c_str(), DrawInputBindingButton(bsi, InputBindingInfo::Type::Macro, section.c_str(),
fmt::format("Macro{}", macro_index + 1).c_str(), fmt::format("Macro{}", macro_index + 1).c_str(),
fmt::format("Macro {} Trigger", macro_index + 1).c_str()); fmt::format("Macro {} Trigger", macro_index + 1).c_str());
@ -3194,9 +3222,8 @@ void FullscreenUI::DrawControllerSettingsPage()
for (u32 i = 0; i < ci->num_bindings; i++) for (u32 i = 0; i < ci->num_bindings; i++)
{ {
const Controller::ControllerBindingInfo& bi = ci->bindings[i]; const Controller::ControllerBindingInfo& bi = ci->bindings[i];
if (bi.type != Controller::ControllerBindingType::Button && if (bi.type != InputBindingInfo::Type::Button && bi.type != InputBindingInfo::Type::Axis &&
bi.type != Controller::ControllerBindingType::Axis && bi.type != InputBindingInfo::Type::HalfAxis)
bi.type != Controller::ControllerBindingType::HalfAxis)
{ {
continue; continue;
} }
@ -3350,8 +3377,7 @@ void FullscreenUI::DrawHotkeySettingsPage()
last_category = hotkey; last_category = hotkey;
} }
DrawInputBindingButton(bsi, Controller::ControllerBindingType::Button, "Hotkeys", hotkey->name, DrawInputBindingButton(bsi, InputBindingInfo::Type::Button, "Hotkeys", hotkey->name, hotkey->display_name, false);
hotkey->display_name, false);
} }
EndMenuButtons(); EndMenuButtons();
@ -5831,9 +5857,12 @@ void FullscreenUI::HandleGameListActivate(const GameList::Entry* entry)
void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry)
{ {
ImGuiFullscreen::ChoiceDialogOptions options = { ImGuiFullscreen::ChoiceDialogOptions options = {
{ICON_FA_WRENCH " Game Properties", false}, {ICON_FA_PLAY " Resume Game", false}, {ICON_FA_WRENCH " Game Properties", false},
{ICON_FA_UNDO " Load State", false}, {ICON_FA_COMPACT_DISC " Default Boot", false}, {ICON_FA_PLAY " Resume Game", false},
{ICON_FA_LIGHTBULB " Fast Boot", false}, {ICON_FA_MAGIC " Slow Boot", false}, {ICON_FA_UNDO " Load State", false},
{ICON_FA_COMPACT_DISC " Default Boot", false},
{ICON_FA_LIGHTBULB " Fast Boot", false},
{ICON_FA_MAGIC " Slow Boot", false},
{ICON_FA_FOLDER_MINUS " Reset Play Time", false}, {ICON_FA_FOLDER_MINUS " Reset Play Time", false},
{ICON_FA_WINDOW_CLOSE " Close Menu", false}, {ICON_FA_WINDOW_CLOSE " Close Menu", false},
}; };

View File

@ -523,8 +523,8 @@ void ImGuiManager::DrawInputsOverlay()
const Controller::ControllerBindingInfo& bi = cinfo->bindings[bind]; const Controller::ControllerBindingInfo& bi = cinfo->bindings[bind];
switch (bi.type) switch (bi.type)
{ {
case Controller::ControllerBindingType::Axis: case InputBindingInfo::Type::Axis:
case Controller::ControllerBindingType::HalfAxis: case InputBindingInfo::Type::HalfAxis:
{ {
// axes are always shown // axes are always shown
const float value = controller->GetBindState(bi.bind_index); const float value = controller->GetBindState(bi.bind_index);
@ -535,7 +535,7 @@ void ImGuiManager::DrawInputsOverlay()
} }
break; break;
case Controller::ControllerBindingType::Button: case InputBindingInfo::Type::Button:
{ {
// buttons only shown when active // buttons only shown when active
const float value = controller->GetBindState(bi.bind_index); const float value = controller->GetBindState(bi.bind_index);
@ -544,9 +544,10 @@ void ImGuiManager::DrawInputsOverlay()
} }
break; break;
case Controller::ControllerBindingType::Motor: case InputBindingInfo::Type::Motor:
case Controller::ControllerBindingType::Macro: case InputBindingInfo::Type::Macro:
case Controller::ControllerBindingType::Unknown: case InputBindingInfo::Type::Unknown:
case InputBindingInfo::Type::Pointer:
default: default:
break; break;
} }

View File

@ -281,8 +281,27 @@ bool InputManager::ParseBindingAndGetSource(const std::string_view& binding, Inp
return false; return false;
} }
std::string InputManager::ConvertInputBindingKeyToString(InputBindingKey key) std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key)
{ {
if (binding_type == InputBindingInfo::Type::Pointer)
{
// pointer and device bindings don't have a data part
if (key.source_type == InputSourceType::Pointer)
{
return GetPointerDeviceName(key.data);
}
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) if (key.source_type == InputSourceType::Keyboard)
{ {
const std::optional<std::string> str(ConvertHostKeyboardCodeToString(key.data)); const std::optional<std::string> str(ConvertHostKeyboardCodeToString(key.data));
@ -301,28 +320,33 @@ std::string InputManager::ConvertInputBindingKeyToString(InputBindingKey key)
else if (key.source_subtype == InputSubclass::PointerAxis) else if (key.source_subtype == InputSubclass::PointerAxis)
{ {
return fmt::format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_names[key.data], return fmt::format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_names[key.data],
key.negative ? '-' : '+'); key.modifier == InputModifier::Negate ? '-' : '+');
} }
} }
else if (key.source_type == InputSourceType::Sensor)
{
if (key.source_subtype == InputSubclass::SensorAccelerometer && key.data < s_sensor_accelerometer_names.size())
return fmt::format("Sensor/{}", s_sensor_accelerometer_names[key.data]);
}
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)]) else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
{ {
return s_input_sources[static_cast<u32>(key.source_type)]->ConvertKeyToString(key); return s_input_sources[static_cast<u32>(key.source_type)]->ConvertKeyToString(key);
} }
}
return {}; return {};
} }
std::string InputManager::ConvertInputBindingKeysToString(const InputBindingKey* keys, size_t num_keys) 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)
{
// so only take the first
if (num_keys > 0)
return ConvertInputBindingKeyToString(binding_type, keys[0]);
}
std::stringstream ss; std::stringstream ss;
for (size_t i = 0; i < num_keys; i++) for (size_t i = 0; i < num_keys; i++)
{ {
const std::string keystr(ConvertInputBindingKeyToString(keys[i])); const std::string keystr(ConvertInputBindingKeyToString(binding_type, keys[i]));
if (keystr.empty()) if (keystr.empty())
return std::string(); return std::string();
@ -574,9 +598,9 @@ std::optional<InputBindingKey> InputManager::ParsePointerKey(const std::string_v
const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_names[i]))); const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_names[i])));
if (dir_part == "+") if (dir_part == "+")
key.negative = false; key.modifier = InputModifier::None;
else if (dir_part == "-") else if (dir_part == "-")
key.negative = true; key.modifier = InputModifier::Negate;
else else
return std::nullopt; return std::nullopt;
@ -597,6 +621,23 @@ std::optional<InputBindingKey> InputManager::ParsePointerKey(const std::string_v
return std::nullopt; return std::nullopt;
} }
std::optional<u32> InputManager::GetIndexFromPointerBinding(const std::string_view& source)
{
if (!StringUtil::StartsWith(source, "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(const std::string_view& source, std::optional<InputBindingKey> InputManager::ParseSensorKey(const std::string_view& source,
const std::string_view& sub_binding) const std::string_view& sub_binding)
{ {
@ -616,9 +657,9 @@ std::optional<InputBindingKey> InputManager::ParseSensorKey(const std::string_vi
const std::string_view dir_part(sub_binding.substr(std::strlen(s_sensor_accelerometer_names[i]))); const std::string_view dir_part(sub_binding.substr(std::strlen(s_sensor_accelerometer_names[i])));
if (dir_part == "+") if (dir_part == "+")
key.negative = false; key.modifier = InputModifier::None;
else if (dir_part == "-") else if (dir_part == "-")
key.negative = true; key.modifier = InputModifier::Negate;
else else
return std::nullopt; return std::nullopt;
@ -777,7 +818,6 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi
for (auto it = range.first; it != range.second; ++it) for (auto it = range.first; it != range.second; ++it)
{ {
InputBinding* binding = it->second.get(); InputBinding* binding = it->second.get();
// find the key which matches us // find the key which matches us
for (u32 i = 0; i < binding->num_keys; i++) for (u32 i = 0; i < binding->num_keys; i++)
{ {
@ -785,11 +825,26 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi
continue; continue;
const u8 bit = static_cast<u8>(1) << i; const u8 bit = static_cast<u8>(1) << i;
const bool negative = binding->keys[i].negative; const bool negative = binding->keys[i].modifier == InputModifier::Negate;
const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f)); 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;
}
// invert if we're negative, since the handler expects 0..1 // handle inverting, needed for some wheels.
const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value : 0.0f); 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 // 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) // (but going from not-zero to zero will still fire, because of the full state)
@ -1130,6 +1185,16 @@ std::vector<std::string> InputManager::GetInputProfileNames()
return ret; return ret;
} }
void InputManager::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name)
{
Host::OnInputDeviceConnected(identifier, device_name);
}
void InputManager::OnInputDeviceDisconnected(const std::string_view& identifier)
{
Host::OnInputDeviceDisconnected(identifier);
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Vibration // Vibration
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -14,6 +14,8 @@
#include "common/types.h" #include "common/types.h"
#include "common/window_info.h" #include "common/window_info.h"
#include "core/input_types.h"
/// Class, or source of an input event. /// Class, or source of an input event.
enum class InputSourceType : u32 enum class InputSourceType : u32
{ {
@ -47,12 +49,20 @@ enum class InputSubclass : u32
ControllerButton = 0, ControllerButton = 0,
ControllerAxis = 1, ControllerAxis = 1,
ControllerMotor = 2, ControllerHat = 2,
ControllerHaptic = 3, ControllerMotor = 3,
ControllerHaptic = 4,
SensorAccelerometer = 0, SensorAccelerometer = 0,
}; };
enum class InputModifier : u32
{
None = 0,
Negate, ///< Input * -1, gets the negative side of the axis
FullAxis, ///< (Input * 0.5) + 0.5, uses both the negative and positive side of the axis together
};
/// A composite type representing a full input key which is part of an event. /// A composite type representing a full input key which is part of an event.
union InputBindingKey union InputBindingKey
{ {
@ -60,9 +70,10 @@ union InputBindingKey
{ {
InputSourceType source_type : 4; InputSourceType source_type : 4;
u32 source_index : 8; ///< controller number u32 source_index : 8; ///< controller number
InputSubclass source_subtype : 2; ///< if 1, binding is for an axis and not a button (used for controllers) InputSubclass source_subtype : 3; ///< if 1, binding is for an axis and not a button (used for controllers)
u32 negative : 1; ///< if 1, binding is for the negative side of the axis InputModifier modifier : 2;
u32 unused : 17; u32 invert : 1; ///< if 1, value is inverted prior to being sent to the sink
u32 unused : 14;
u32 data; u32 data;
}; };
@ -77,7 +88,8 @@ union InputBindingKey
{ {
InputBindingKey r; InputBindingKey r;
r.bits = bits; r.bits = bits;
r.negative = false; r.modifier = InputModifier::None;
r.invert = 0;
return r; return r;
} }
}; };
@ -181,6 +193,12 @@ bool GetInputSourceDefaultEnabled(InputSourceType type);
/// Parses an input class string. /// Parses an input class string.
std::optional<InputSourceType> ParseInputSourceString(const std::string_view& str); std::optional<InputSourceType> ParseInputSourceString(const std::string_view& str);
/// Parses a pointer device string, i.e. tells you which pointer is specified.
std::optional<u32> GetIndexFromPointerBinding(const std::string_view& str);
/// Returns the device name for a pointer index (e.g. Pointer-0).
std::string GetPointerDeviceName(u32 pointer_index);
/// Converts a key code from a human-readable string to an identifier. /// Converts a key code from a human-readable string to an identifier.
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str); std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str);
@ -204,10 +222,11 @@ InputBindingKey MakeSensorAxisKey(InputSubclass sensor, u32 axis);
std::optional<InputBindingKey> ParseInputBindingKey(const std::string_view& binding); std::optional<InputBindingKey> ParseInputBindingKey(const std::string_view& binding);
/// Converts a input key to a string. /// Converts a input key to a string.
std::string ConvertInputBindingKeyToString(InputBindingKey key); std::string ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key);
/// Converts a chord of binding keys to a string. /// Converts a chord of binding keys to a string.
std::string ConvertInputBindingKeysToString(const InputBindingKey* keys, size_t num_keys); std::string ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys,
size_t num_keys);
/// Returns a list of all hotkeys. /// Returns a list of all hotkeys.
std::vector<const HotkeyInfo*> GetHotkeyList(); std::vector<const HotkeyInfo*> GetHotkeyList();
@ -263,7 +282,7 @@ void AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding,
/// Updates internal state for any binds for this key, and fires callbacks as needed. /// Updates internal state for any binds for this key, and fires callbacks as needed.
/// Returns true if anything was bound to this key, otherwise false. /// Returns true if anything was bound to this key, otherwise false.
bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key); bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown);
/// Sets a hook which can be used to intercept events before they're processed by the normal bindings. /// Sets a hook which can be used to intercept events before they're processed by the normal bindings.
/// This is typically used when binding new controls to detect what gets pressed. /// This is typically used when binding new controls to detect what gets pressed.
@ -315,6 +334,12 @@ bool MapController(SettingsInterface& si, u32 controller,
/// Returns a list of input profiles available. /// Returns a list of input profiles available.
std::vector<std::string> GetInputProfileNames(); std::vector<std::string> GetInputProfileNames();
/// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name);
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier);
} // namespace InputManager } // namespace InputManager
namespace Host { namespace Host {

View File

@ -38,6 +38,17 @@ InputBindingKey InputSource::MakeGenericControllerButtonKey(InputSourceType claz
return key; return key;
} }
InputBindingKey InputSource::MakeGenericControllerHatKey(InputSourceType clazz, u32 controller_index, s32 hat_index,
u8 hat_direction, u32 num_directions)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::ControllerHat;
key.data = static_cast<u32>(hat_index) * num_directions + hat_direction;
return key;
}
InputBindingKey InputSource::MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index) InputBindingKey InputSource::MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index)
{ {
InputBindingKey key = {}; InputBindingKey key = {};
@ -81,12 +92,21 @@ std::optional<InputBindingKey> InputSource::ParseGenericControllerKey(InputSourc
key.data = static_cast<u32>(axis_number.value()); key.data = static_cast<u32>(axis_number.value());
if (sub_binding[0] == '+') if (sub_binding[0] == '+')
key.negative = false; key.modifier = InputModifier::None;
else if (sub_binding[0] == '-') else if (sub_binding[0] == '-')
key.negative = true; key.modifier = InputModifier::Negate;
else else
return std::nullopt; return std::nullopt;
} }
else if (StringUtil::StartsWith(sub_binding, "FullAxis"))
{
const std::optional<s32> axis_number = StringUtil::FromChars<s32>(sub_binding.substr(8));
if (!axis_number.has_value() || axis_number.value() < 0)
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = static_cast<u32>(axis_number.value());
key.modifier = InputModifier::FullAxis;
}
else if (StringUtil::StartsWith(sub_binding, "Button")) else if (StringUtil::StartsWith(sub_binding, "Button"))
{ {
const std::optional<s32> button_number = StringUtil::FromChars<s32>(sub_binding.substr(6)); const std::optional<s32> button_number = StringUtil::FromChars<s32>(sub_binding.substr(6));
@ -108,8 +128,21 @@ std::string InputSource::ConvertGenericControllerKeyToString(InputBindingKey key
{ {
if (key.source_subtype == InputSubclass::ControllerAxis) if (key.source_subtype == InputSubclass::ControllerAxis)
{ {
return StringUtil::StdStringFromFormat("%s-%u/%cAxis%u", InputManager::InputSourceToString(key.source_type), const char* modifier = "";
key.source_index, key.negative ? '+' : '-', key.data); switch (key.modifier)
{
case InputModifier::None:
modifier = "+";
break;
case InputModifier::Negate:
modifier = "-";
break;
case InputModifier::FullAxis:
modifier = "Full";
break;
}
return StringUtil::StdStringFromFormat("%s-%u/%sAxis%u", InputManager::InputSourceToString(key.source_type),
key.source_index, modifier, key.data);
} }
else if (key.source_subtype == InputSubclass::ControllerButton) else if (key.source_subtype == InputSubclass::ControllerButton)
{ {

View File

@ -55,6 +55,10 @@ public:
/// Creates a key for a generic controller button event. /// Creates a key for a generic controller button event.
static InputBindingKey MakeGenericControllerButtonKey(InputSourceType clazz, u32 controller_index, s32 button_index); static InputBindingKey MakeGenericControllerButtonKey(InputSourceType clazz, u32 controller_index, s32 button_index);
/// Creates a key for a generic controller hat event.
static InputBindingKey MakeGenericControllerHatKey(InputSourceType clazz, u32 controller_index, s32 hat_index,
u8 hat_direction, u32 num_directions);
/// Creates a key for a generic controller motor event. /// Creates a key for a generic controller motor event.
static InputBindingKey MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index); static InputBindingKey MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index);

View File

@ -14,7 +14,7 @@
#endif #endif
Log_SetChannel(SDLInputSource); Log_SetChannel(SDLInputSource);
static const char* s_sdl_axis_names[] = { static constexpr const char* s_sdl_axis_names[] = {
"LeftX", // SDL_CONTROLLER_AXIS_LEFTX "LeftX", // SDL_CONTROLLER_AXIS_LEFTX
"LeftY", // SDL_CONTROLLER_AXIS_LEFTY "LeftY", // SDL_CONTROLLER_AXIS_LEFTY
"RightX", // SDL_CONTROLLER_AXIS_RIGHTX "RightX", // SDL_CONTROLLER_AXIS_RIGHTX
@ -22,7 +22,7 @@ static const char* s_sdl_axis_names[] = {
"LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT "LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT
"RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT "RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT
}; };
static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = {
{GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX
{GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY
{GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX
@ -31,7 +31,7 @@ static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = {
{GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT
}; };
static const char* s_sdl_button_names[] = { static constexpr const char* s_sdl_button_names[] = {
"A", // SDL_CONTROLLER_BUTTON_A "A", // SDL_CONTROLLER_BUTTON_A
"B", // SDL_CONTROLLER_BUTTON_B "B", // SDL_CONTROLLER_BUTTON_B
"X", // SDL_CONTROLLER_BUTTON_X "X", // SDL_CONTROLLER_BUTTON_X
@ -54,7 +54,7 @@ static const char* s_sdl_button_names[] = {
"Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4 "Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4
"Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD "Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD
}; };
static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[] = {
GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A
GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B
GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X
@ -78,11 +78,20 @@ static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = {
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD
}; };
static constexpr const char* s_sdl_hat_direction_names[] = {
// clang-format off
"North",
"East",
"South",
"West",
// clang-format on
};
SDLInputSource::SDLInputSource() = default; SDLInputSource::SDLInputSource() = default;
SDLInputSource::~SDLInputSource() SDLInputSource::~SDLInputSource()
{ {
DebugAssert(m_controllers.empty()); Assert(m_controllers.empty());
} }
bool SDLInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) bool SDLInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
@ -123,6 +132,13 @@ void SDLInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std:
} }
} }
bool SDLInputSource::ReloadDevices()
{
// We'll get a GC added/removed event here.
PollEvents();
return false;
}
void SDLInputSource::Shutdown() void SDLInputSource::Shutdown()
{ {
ShutdownSubsystem(); ShutdownSubsystem();
@ -131,36 +147,32 @@ void SDLInputSource::Shutdown()
void SDLInputSource::LoadSettings(SettingsInterface& si) void SDLInputSource::LoadSettings(SettingsInterface& si)
{ {
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false); m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
} m_sdl_hints = si.GetKeyValueList("SDLHints");
bool SDLInputSource::ReloadDevices()
{
// We'll get a GC added/removed event here.
PollEvents();
return false;
} }
void SDLInputSource::SetHints() void SDLInputSource::SetHints()
{ {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, m_controller_enhanced_mode ? "1" : "0");
// Enable Wii U Pro Controller support
// New as of SDL 2.26, so use string
SDL_SetHint("SDL_JOYSTICK_HIDAPI_WII", "1");
#ifndef _WIN32
// Gets us pressure sensitive button support on Linux
// Apparently doesn't work on Windows, so leave it off there
// New as of SDL 2.26, so use string
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS3", "1");
#endif
for (const std::pair<std::string, std::string>& hint : m_sdl_hints)
SDL_SetHint(hint.first.c_str(), hint.second.c_str());
} }
bool SDLInputSource::InitializeSubsystem() bool SDLInputSource::InitializeSubsystem()
{ {
int result; if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
#ifdef __APPLE__
// On macOS, SDL_InitSubSystem runs a main-thread-only call to some GameController framework method
// So send this to be run on the main thread
dispatch_sync_f(dispatch_get_main_queue(), &result, [](void* ctx) {
*static_cast<int*>(ctx) = SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);
});
#else
result = SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);
#endif
if (result < 0)
{ {
Log_ErrorPrintf("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed"); Log_ErrorPrint("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
return false; return false;
} }
@ -172,16 +184,11 @@ bool SDLInputSource::InitializeSubsystem()
void SDLInputSource::ShutdownSubsystem() void SDLInputSource::ShutdownSubsystem()
{ {
while (!m_controllers.empty()) while (!m_controllers.empty())
CloseGameController(m_controllers.begin()->joystick_id); CloseDevice(m_controllers.begin()->joystick_id);
if (m_sdl_subsystem_initialized) if (m_sdl_subsystem_initialized)
{ {
#ifdef __APPLE__
dispatch_sync_f(dispatch_get_main_queue(), nullptr,
[](void*) { SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); });
#else
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);
#endif
m_sdl_subsystem_initialized = false; m_sdl_subsystem_initialized = false;
} }
} }
@ -206,7 +213,7 @@ std::vector<std::pair<std::string, std::string>> SDLInputSource::EnumerateDevice
{ {
std::string id(StringUtil::StdStringFromFormat("SDL-%d", cd.player_id)); std::string id(StringUtil::StdStringFromFormat("SDL-%d", cd.player_id));
const char* name = SDL_GameControllerName(cd.game_controller); const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick);
if (name) if (name)
ret.emplace_back(std::move(id), name); ret.emplace_back(std::move(id), name);
else else
@ -258,6 +265,19 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_
{ {
// likely an axis // likely an axis
const std::string_view axis_name(binding.substr(1)); const std::string_view axis_name(binding.substr(1));
if (StringUtil::StartsWith(axis_name, "Axis"))
{
std::string_view end;
if (auto value = StringUtil::FromChars<u32>(axis_name.substr(4), 10, &end))
{
key.source_subtype = InputSubclass::ControllerAxis;
key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names));
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
}
for (u32 i = 0; i < std::size(s_sdl_axis_names); i++) for (u32 i = 0; i < std::size(s_sdl_axis_names); i++)
{ {
if (axis_name == s_sdl_axis_names[i]) if (axis_name == s_sdl_axis_names[i])
@ -265,14 +285,51 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_
// found an axis! // found an axis!
key.source_subtype = InputSubclass::ControllerAxis; key.source_subtype = InputSubclass::ControllerAxis;
key.data = i; key.data = i;
key.negative = (binding[0] == '-'); key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
return key; return key;
} }
} }
} }
else if (StringUtil::StartsWith(binding, "FullAxis"))
{
std::string_view end;
if (auto value = StringUtil::FromChars<u32>(binding.substr(8), 10, &end))
{
key.source_subtype = InputSubclass::ControllerAxis;
key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names));
key.modifier = InputModifier::FullAxis;
key.invert = (end == "~");
return key;
}
}
else if (StringUtil::StartsWith(binding, "Hat"))
{
std::string_view hat_dir;
if (auto value = StringUtil::FromChars<u32>(binding.substr(3), 10, &hat_dir); value.has_value() && !hat_dir.empty())
{
for (u8 dir = 0; dir < static_cast<u8>(std::size(s_sdl_hat_direction_names)); dir++)
{
if (hat_dir == s_sdl_hat_direction_names[dir])
{
key.source_subtype = InputSubclass::ControllerHat;
key.data = value.value() * static_cast<u32>(std::size(s_sdl_hat_direction_names)) + dir;
return key;
}
}
}
}
else else
{ {
// must be a button // must be a button
if (StringUtil::StartsWith(binding, "Button"))
{
if (auto value = StringUtil::FromChars<u32>(binding.substr(6)))
{
key.source_subtype = InputSubclass::ControllerButton;
key.data = *value + static_cast<u32>(std::size(s_sdl_button_names));
return key;
}
}
for (u32 i = 0; i < std::size(s_sdl_button_names); i++) for (u32 i = 0; i < std::size(s_sdl_button_names); i++)
{ {
if (binding == s_sdl_button_names[i]) if (binding == s_sdl_button_names[i])
@ -294,15 +351,40 @@ std::string SDLInputSource::ConvertKeyToString(InputBindingKey key)
if (key.source_type == InputSourceType::SDL) if (key.source_type == InputSourceType::SDL)
{ {
if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_sdl_axis_names)) if (key.source_subtype == InputSubclass::ControllerAxis)
{ {
ret = StringUtil::StdStringFromFormat("SDL-%u/%c%s", key.source_index, key.negative ? '-' : '+', const char* modifier =
s_sdl_axis_names[key.data]); (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
if (key.data < std::size(s_sdl_axis_names))
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%s%s", key.source_index, modifier, s_sdl_axis_names[key.data]);
} }
else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_sdl_button_names)) else
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%sAxis%u%s", key.source_index, modifier,
key.data - static_cast<u32>(std::size(s_sdl_axis_names)),
key.invert ? "~" : "");
}
}
else if (key.source_subtype == InputSubclass::ControllerButton)
{
if (key.data < std::size(s_sdl_button_names))
{ {
ret = StringUtil::StdStringFromFormat("SDL-%u/%s", key.source_index, s_sdl_button_names[key.data]); ret = StringUtil::StdStringFromFormat("SDL-%u/%s", key.source_index, s_sdl_button_names[key.data]);
} }
else
{
ret = StringUtil::StdStringFromFormat("SDL-%u/Button%u", key.source_index,
key.data - static_cast<u32>(std::size(s_sdl_button_names)));
}
}
else if (key.source_subtype == InputSubclass::ControllerHat)
{
const u32 hat_index = key.data / static_cast<u32>(std::size(s_sdl_hat_direction_names));
const u32 hat_direction = key.data % static_cast<u32>(std::size(s_sdl_hat_direction_names));
ret = StringUtil::StdStringFromFormat("SDL-%u/Hat%u%s", key.source_index, hat_index,
s_sdl_hat_direction_names[hat_direction]);
}
else if (key.source_subtype == InputSubclass::ControllerMotor) else if (key.source_subtype == InputSubclass::ControllerMotor)
{ {
ret = StringUtil::StdStringFromFormat("SDL-%u/%sMotor", key.source_index, key.data ? "Large" : "Small"); ret = StringUtil::StdStringFromFormat("SDL-%u/%sMotor", key.source_index, key.data ? "Large" : "Small");
@ -323,14 +405,37 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEADDED:
{ {
Log_InfoPrintf("(SDLInputSource) Controller %d inserted", event->cdevice.which); Log_InfoPrintf("(SDLInputSource) Controller %d inserted", event->cdevice.which);
OpenGameController(event->cdevice.which); OpenDevice(event->cdevice.which, true);
return true; return true;
} }
case SDL_CONTROLLERDEVICEREMOVED: case SDL_CONTROLLERDEVICEREMOVED:
{ {
Log_InfoPrintf("(SDLInputSource) Controller %d removed", event->cdevice.which); Log_InfoPrintf("(SDLInputSource) Controller %d removed", event->cdevice.which);
CloseGameController(event->cdevice.which); CloseDevice(event->cdevice.which);
return true;
}
case SDL_JOYDEVICEADDED:
{
// Let game controller handle.. well.. game controllers.
if (SDL_IsGameController(event->jdevice.which))
return false;
Log_InfoPrintf("(SDLInputSource) Joystick %d inserted", event->jdevice.which);
OpenDevice(event->cdevice.which, false);
return true;
}
break;
case SDL_JOYDEVICEREMOVED:
{
if (auto it = GetControllerDataForJoystickId(event->cdevice.which);
it != m_controllers.end() && it->game_controller)
return false;
Log_InfoPrintf("(SDLInputSource) Joystick %d removed", event->jdevice.which);
CloseDevice(event->cdevice.which);
return true; return true;
} }
@ -341,11 +446,37 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONUP:
return HandleControllerButtonEvent(&event->cbutton); return HandleControllerButtonEvent(&event->cbutton);
case SDL_JOYAXISMOTION:
return HandleJoystickAxisEvent(&event->jaxis);
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
return HandleJoystickButtonEvent(&event->jbutton);
case SDL_JOYHATMOTION:
return HandleJoystickHatEvent(&event->jhat);
default: default:
return false; return false;
} }
} }
SDL_Joystick* SDLInputSource::GetJoystickForDevice(const std::string_view& device)
{
if (!StringUtil::StartsWith(device, "SDL-"))
return nullptr;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return nullptr;
auto it = GetControllerDataForPlayerId(player_id.value());
if (it == m_controllers.end())
return nullptr;
return it->joystick;
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(int id) SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(int id)
{ {
return std::find_if(m_controllers.begin(), m_controllers.end(), return std::find_if(m_controllers.begin(), m_controllers.end(),
@ -375,11 +506,23 @@ int SDLInputSource::GetFreePlayerId() const
return 0; return 0;
} }
bool SDLInputSource::OpenGameController(int index) bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
{ {
SDL_GameController* gcontroller = SDL_GameControllerOpen(index); SDL_GameController* gcontroller;
SDL_Joystick* joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr; SDL_Joystick* joystick;
if (!gcontroller || !joystick)
if (is_gamecontroller)
{
gcontroller = SDL_GameControllerOpen(index);
joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr;
}
else
{
gcontroller = nullptr;
joystick = SDL_JoystickOpen(index);
}
if (!gcontroller && !joystick)
{ {
Log_ErrorPrintf("(SDLInputSource) Failed to open controller %d", index); Log_ErrorPrintf("(SDLInputSource) Failed to open controller %d", index);
if (gcontroller) if (gcontroller)
@ -389,7 +532,7 @@ bool SDLInputSource::OpenGameController(int index)
} }
const int joystick_id = SDL_JoystickInstanceID(joystick); const int joystick_id = SDL_JoystickInstanceID(joystick);
int player_id = SDL_GameControllerGetPlayerIndex(gcontroller); int player_id = gcontroller ? SDL_GameControllerGetPlayerIndex(gcontroller) : SDL_JoystickGetPlayerIndex(joystick);
if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end()) if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end())
{ {
const int free_player_id = GetFreePlayerId(); const int free_player_id = GetFreePlayerId();
@ -399,20 +542,49 @@ bool SDLInputSource::OpenGameController(int index)
player_id = free_player_id; player_id = free_player_id;
} }
Log_InfoPrintf("(SDLInputSource) Opened controller %d (instance id %d, player id %d): %s", index, joystick_id, const char* name = gcontroller ? SDL_GameControllerName(gcontroller) : SDL_JoystickName(joystick);
player_id, SDL_GameControllerName(gcontroller)); if (!name)
name = "Unknown Device";
Log_VerbosePrintf("(SDLInputSource) Opened %s %d (instance id %d, player id %d): %s",
is_gamecontroller ? "game controller" : "joystick", index, joystick_id, player_id, name);
ControllerData cd = {}; ControllerData cd = {};
cd.player_id = player_id; cd.player_id = player_id;
cd.joystick_id = joystick_id; cd.joystick_id = joystick_id;
cd.haptic_left_right_effect = -1; cd.haptic_left_right_effect = -1;
cd.game_controller = gcontroller; cd.game_controller = gcontroller;
cd.joystick = joystick;
cd.use_game_controller_rumble = (SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); if (gcontroller)
{
const int num_axes = SDL_JoystickNumAxes(joystick);
const int num_buttons = SDL_JoystickNumButtons(joystick);
cd.joy_axis_used_in_gc.resize(num_axes, false);
cd.joy_button_used_in_gc.resize(num_buttons, false);
auto mark_bind = [&](SDL_GameControllerButtonBind bind) {
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes)
cd.joy_axis_used_in_gc[bind.value.axis] = true;
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons)
cd.joy_button_used_in_gc[bind.value.button] = true;
};
for (size_t i = 0; i < std::size(s_sdl_axis_names); i++)
mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast<SDL_GameControllerAxis>(i)));
for (size_t i = 0; i < std::size(s_sdl_button_names); i++)
mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i)));
}
else
{
// GC doesn't have the concept of hats, so we only need to do this for joysticks.
const int num_hats = SDL_JoystickNumHats(joystick);
if (num_hats > 0)
cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0));
}
cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0);
if (cd.use_game_controller_rumble) if (cd.use_game_controller_rumble)
{ {
Log_DevPrintf("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", Log_VerbosePrintf("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", name);
SDL_GameControllerName(gcontroller));
} }
else else
{ {
@ -431,7 +603,7 @@ bool SDLInputSource::OpenGameController(int index)
} }
else else
{ {
Log_WarningPrintf("(SDLInputSource) Failed to create haptic left/right effect: %s", SDL_GetError()); Log_ErrorPrintf("(SDLInputSource) Failed to create haptic left/right effect: %s", SDL_GetError());
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0) if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0)
{ {
cd.haptic = haptic; cd.haptic = haptic;
@ -445,37 +617,43 @@ bool SDLInputSource::OpenGameController(int index)
} }
if (cd.haptic) if (cd.haptic)
Log_DevPrintf("(SDLInputSource) Rumble is supported on '%s' via haptic", SDL_GameControllerName(gcontroller)); Log_VerbosePrintf("(SDLInputSource) Rumble is supported on '%s' via haptic", name);
} }
if (!cd.haptic && !cd.use_game_controller_rumble) if (!cd.haptic && !cd.use_game_controller_rumble)
Log_WarningPrintf("(SDLInputSource) Rumble is not supported on '%s'", SDL_GameControllerName(gcontroller)); Log_VerbosePrintf("(SDLInputSource) Rumble is not supported on '%s'", name);
m_controllers.push_back(std::move(cd)); m_controllers.push_back(std::move(cd));
const char* name = SDL_GameControllerName(cd.game_controller); InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name);
Host::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name ? name : "Unknown Device");
return true; return true;
} }
bool SDLInputSource::CloseGameController(int joystick_index) bool SDLInputSource::CloseDevice(int joystick_index)
{ {
auto it = GetControllerDataForJoystickId(joystick_index); auto it = GetControllerDataForJoystickId(joystick_index);
if (it == m_controllers.end()) if (it == m_controllers.end())
return false; return false;
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", it->player_id));
if (it->haptic) if (it->haptic)
SDL_HapticClose(static_cast<SDL_Haptic*>(it->haptic)); SDL_HapticClose(it->haptic);
SDL_GameControllerClose(static_cast<SDL_GameController*>(it->game_controller)); if (it->game_controller)
SDL_GameControllerClose(it->game_controller);
else
SDL_JoystickClose(it->joystick);
const int player_id = it->player_id;
m_controllers.erase(it); m_controllers.erase(it);
Host::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", player_id));
return true; return true;
} }
static float NormalizeS16(s16 value)
{
return static_cast<float>(value) / (value < 0 ? 32768.0f : 32767.0f);
}
bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev) bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev)
{ {
auto it = GetControllerDataForJoystickId(ev->which); auto it = GetControllerDataForJoystickId(ev->which);
@ -483,8 +661,8 @@ bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev
return false; return false;
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis)); const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis));
const float value = static_cast<float>(ev->value) / (ev->value < 0 ? 32768.0f : 32767.0f); InputManager::InvokeEvents(key, NormalizeS16(ev->value));
return InputManager::InvokeEvents(key, value, GenericInputBinding::Unknown); return true;
} }
bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev) bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev)
@ -497,7 +675,62 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent
const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ? const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ?
s_sdl_generic_binding_button_mapping[ev->button] : s_sdl_generic_binding_button_mapping[ev->button] :
GenericInputBinding::Unknown; GenericInputBinding::Unknown;
return InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key);
return true;
}
bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
if (ev->axis < it->joy_axis_used_in_gc.size() && it->joy_axis_used_in_gc[ev->axis])
return false; // Will get handled by GC event
const u32 axis = ev->axis + static_cast<u32>(std::size(s_sdl_axis_names)); // Ensure we don't conflict with GC axes
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, axis));
InputManager::InvokeEvents(key, NormalizeS16(ev->value));
return true;
}
bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button])
return false; // Will get handled by GC event
const u32 button =
ev->button + static_cast<u32>(std::size(s_sdl_button_names)); // Ensure we don't conflict with GC buttons
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button));
InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f);
return true;
}
bool SDLInputSource::HandleJoystickHatEvent(const SDL_JoyHatEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end() || ev->hat >= it->last_hat_state.size())
return false;
const unsigned long last_direction = it->last_hat_state[ev->hat];
it->last_hat_state[ev->hat] = ev->value;
unsigned long changed_direction = last_direction ^ ev->value;
while (changed_direction != 0)
{
unsigned long pos;
_BitScanForward(&pos, changed_direction);
const unsigned long mask = (1u << pos);
changed_direction &= ~mask;
const InputBindingKey key(MakeGenericControllerHatKey(InputSourceType::SDL, it->player_id, ev->hat,
static_cast<u8>(pos),
static_cast<u32>(std::size(s_sdl_hat_direction_names))));
InputManager::InvokeEvents(key, (last_direction & mask) ? 0.0f : 1.0f);
}
return true;
} }
std::vector<InputBindingKey> SDLInputSource::EnumerateMotors() std::vector<InputBindingKey> SDLInputSource::EnumerateMotors()
@ -581,7 +814,7 @@ bool SDLInputSource::GetGenericBindingMapping(const std::string_view& device, Ge
} }
else else
{ {
// joysticks, which we haven't implemented yet anyway. // joysticks have arbitrary axis numbers, so automapping isn't going to work here.
return false; return false;
} }
} }

View File

@ -36,22 +36,26 @@ public:
bool ProcessSDLEvent(const SDL_Event* event); bool ProcessSDLEvent(const SDL_Event* event);
private: SDL_Joystick* GetJoystickForDevice(const std::string_view& device);
enum : int
{
MAX_NUM_AXES = 7,
MAX_NUM_BUTTONS = 16,
};
private:
struct ControllerData struct ControllerData
{ {
SDL_Haptic* haptic; SDL_Haptic* haptic;
SDL_GameController* game_controller; SDL_GameController* game_controller;
SDL_Joystick* joystick;
u16 rumble_intensity[2]; u16 rumble_intensity[2];
int haptic_left_right_effect; int haptic_left_right_effect;
int joystick_id; int joystick_id;
int player_id; int player_id;
bool use_game_controller_rumble; bool use_game_controller_rumble;
// Used to disable Joystick controls that are used in GameController inputs so we don't get double events
std::vector<bool> joy_button_used_in_gc;
std::vector<bool> joy_axis_used_in_gc;
// Track last hat state so we can send "unpressed" events.
std::vector<u8> last_hat_state;
}; };
using ControllerDataVector = std::vector<ControllerData>; using ControllerDataVector = std::vector<ControllerData>;
@ -65,14 +69,18 @@ private:
ControllerDataVector::iterator GetControllerDataForPlayerId(int id); ControllerDataVector::iterator GetControllerDataForPlayerId(int id);
int GetFreePlayerId() const; int GetFreePlayerId() const;
bool OpenGameController(int index); bool OpenDevice(int index, bool is_gamecontroller);
bool CloseGameController(int joystick_index); bool CloseDevice(int joystick_index);
bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* event); bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev);
bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* event); bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev);
bool HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev);
bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev);
bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev);
void SendRumbleUpdate(ControllerData* cd); void SendRumbleUpdate(ControllerData* cd);
ControllerDataVector m_controllers; ControllerDataVector m_controllers;
bool m_sdl_subsystem_initialized = false; bool m_sdl_subsystem_initialized = false;
bool m_controller_enhanced_mode = false; bool m_controller_enhanced_mode = false;
std::vector<std::pair<std::string, std::string>> m_sdl_hints;
}; };

View File

@ -260,7 +260,7 @@ std::optional<InputBindingKey> XInputSource::ParseKeyString(const std::string_vi
// found an axis! // found an axis!
key.source_subtype = InputSubclass::ControllerAxis; key.source_subtype = InputSubclass::ControllerAxis;
key.data = i; key.data = i;
key.negative = (binding[0] == '-'); key.modifier = binding[0] == '-' ? InputModifier::Negate : InputModifier::None;
return key; return key;
} }
} }
@ -291,8 +291,8 @@ std::string XInputSource::ConvertKeyToString(InputBindingKey key)
{ {
if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_names)) if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_names))
{ {
ret = StringUtil::StdStringFromFormat("XInput-%u/%c%s", key.source_index, key.negative ? '-' : '+', const char modifier = key.modifier == InputModifier::Negate ? '-' : '+';
s_axis_names[key.data]); ret = StringUtil::StdStringFromFormat("XInput-%u/%c%s", key.source_index, modifier, s_axis_names[key.data]);
} }
else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_names)) else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_names))
{ {
@ -382,16 +382,15 @@ void XInputSource::HandleControllerConnection(u32 index)
cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0; cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0;
cd.last_state = {}; cd.last_state = {};
Host::OnInputDeviceConnected(StringUtil::StdStringFromFormat("XInput-%u", index), InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("XInput-%u", index),
StringUtil::StdStringFromFormat("XInput Controller %u", index)); StringUtil::StdStringFromFormat("XInput Controller %u", index));
} }
void XInputSource::HandleControllerDisconnection(u32 index) void XInputSource::HandleControllerDisconnection(u32 index)
{ {
Log_InfoPrintf("XInput controller %u disconnected.", index); Log_InfoPrintf("XInput controller %u disconnected.", index);
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("XInput-%u", index));
m_controllers[index] = {}; m_controllers[index] = {};
Host::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("XInput-%u", index));
} }
void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state) void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state)

View File

@ -186,28 +186,28 @@ bool INISettingsInterface::GetStringValue(const char* section, const char* key,
return true; return true;
} }
void INISettingsInterface::SetIntValue(const char* section, const char* key, int value) void INISettingsInterface::SetIntValue(const char* section, const char* key, s32 value)
{ {
m_dirty = true; m_dirty = true;
m_ini.SetLongValue(section, key, static_cast<long>(value), nullptr, false, true); m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true);
} }
void INISettingsInterface::SetUIntValue(const char* section, const char* key, u32 value) void INISettingsInterface::SetUIntValue(const char* section, const char* key, u32 value)
{ {
m_dirty = true; m_dirty = true;
m_ini.SetLongValue(section, key, static_cast<long>(value), nullptr, false, true); m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true);
} }
void INISettingsInterface::SetFloatValue(const char* section, const char* key, float value) void INISettingsInterface::SetFloatValue(const char* section, const char* key, float value)
{ {
m_dirty = true; m_dirty = true;
m_ini.SetDoubleValue(section, key, static_cast<double>(value), nullptr, true); m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true);
} }
void INISettingsInterface::SetDoubleValue(const char* section, const char* key, double value) void INISettingsInterface::SetDoubleValue(const char* section, const char* key, double value)
{ {
m_dirty = true; m_dirty = true;
m_ini.SetDoubleValue(section, key, value, nullptr, true); m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true);
} }
void INISettingsInterface::SetBoolValue(const char* section, const char* key, bool value) void INISettingsInterface::SetBoolValue(const char* section, const char* key, bool value)
@ -282,3 +282,40 @@ bool INISettingsInterface::AddToStringList(const char* section, const char* key,
m_ini.SetValue(section, key, item, nullptr, false); m_ini.SetValue(section, key, item, nullptr, false);
return true; return true;
} }
std::vector<std::pair<std::string, std::string>> INISettingsInterface::GetKeyValueList(const char* section) const
{
using Entry = CSimpleIniA::Entry;
using KVEntry = std::pair<const char*, Entry>;
std::vector<KVEntry> entries;
std::vector<std::pair<std::string, std::string>> output;
std::list<Entry> keys, values;
if (m_ini.GetAllKeys(section, keys))
{
for (Entry& key : keys)
{
if (!m_ini.GetAllValues(section, key.pItem, values)) // [[unlikely]]
{
Log_ErrorPrintf("Got no values for a key returned from GetAllKeys!");
continue;
}
for (const Entry& value : values)
entries.emplace_back(key.pItem, value);
}
}
std::sort(entries.begin(), entries.end(),
[](const KVEntry& a, const KVEntry& b) { return a.second.nOrder < b.second.nOrder; });
for (const KVEntry& entry : entries)
output.emplace_back(entry.first, entry.second.pItem);
return output;
}
void INISettingsInterface::SetKeyValueList(const char* section,
const std::vector<std::pair<std::string, std::string>>& items)
{
m_ini.Delete(section, nullptr);
for (const std::pair<std::string, std::string>& item : items)
m_ini.SetValue(section, item.first.c_str(), item.second.c_str(), nullptr, false);
}

View File

@ -45,6 +45,9 @@ public:
bool RemoveFromStringList(const char* section, const char* key, const char* item) override; bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
bool AddToStringList(const char* section, const char* key, const char* item) override; bool AddToStringList(const char* section, const char* key, const char* item) override;
std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const override;
void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) override;
// default parameter overloads // default parameter overloads
using SettingsInterface::GetBoolValue; using SettingsInterface::GetBoolValue;
using SettingsInterface::GetDoubleValue; using SettingsInterface::GetDoubleValue;