mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-04-27 07:05:42 -04:00
InputManager: Support inverted full axis
i.e. pedals
This commit is contained in:
parent
01270bac35
commit
395e9a934b
@ -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>
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
@ -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),
|
||||||
|
@ -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),
|
||||||
|
@ -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
71
src/core/input_types.h
Normal 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,
|
||||||
|
};
|
||||||
|
|
@ -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),
|
||||||
|
@ -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),
|
||||||
|
@ -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)
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,26 @@ 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)
|
||||||
{
|
{
|
||||||
@ -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
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user