input: refactor combos into a struct for future use

Also update some terminology in the code
This commit is contained in:
Megamouse 2026-03-17 21:30:18 +01:00
parent 6523afa69a
commit 704d8764af
9 changed files with 121 additions and 140 deletions

View File

@ -11,30 +11,15 @@ PadHandlerBase::PadHandlerBase(pad_handler type) : m_type(type)
{ {
} }
std::vector<std::set<u32>> PadHandlerBase::find_key_combos(const std::unordered_map<u32, std::string>& map, const std::string& cfg_string, const std::string& fallback) std::vector<std::set<u32>> PadHandlerBase::find_key_combos(const std::unordered_map<u32, std::string>& map, const std::string& cfg_string)
{ {
std::vector<std::set<u32>> key_codes; std::vector<std::set<u32>> key_codes;
const std::vector<std::vector<std::string>> combos = cfg_pad::get_buttons(cfg_string); const std::vector<pad::combo> combos = cfg_pad::get_combos(cfg_string);
u32 def_code = umax;
for (const std::vector<std::string>& names : combos) for (const pad::combo& combo : combos)
{ {
std::set<u32> keys; std::set<u32> keys = find_key_codes(map, combo);
for (const std::string& nam : names)
{
for (const auto& [code, name] : map)
{
if (name == nam)
{
keys.insert(code);
}
if (!fallback.empty() && name == fallback)
def_code = code;
}
}
if (!keys.empty()) if (!keys.empty())
{ {
@ -42,39 +27,18 @@ std::vector<std::set<u32>> PadHandlerBase::find_key_combos(const std::unordered_
} }
} }
if (!key_codes.empty()) return key_codes;
{
return key_codes;
}
if (!fallback.empty())
{
if (!combos.empty())
input_log.error("FindKeyCode for [name = %s] returned with [def_code = %d] for [fallback = %s]", cfg_string, def_code, fallback);
if (def_code != umax)
{
return {{ def_code }};
}
}
return {};
} }
std::vector<std::set<u32>> PadHandlerBase::find_key_combos(const std::unordered_map<u32, std::string>& map, const cfg::string& cfg_string, bool fallback) std::set<u32> PadHandlerBase::find_key_codes(const std::unordered_map<u32, std::string>& map, const pad::combo& combo)
{
return find_key_combos(map, cfg_string.to_string(), fallback ? cfg_string.def : "");
}
std::set<u32> PadHandlerBase::find_key_codes(const std::unordered_map<u32, std::string>& map, const std::vector<std::string>& names)
{ {
std::set<u32> key_codes; std::set<u32> key_codes;
for (const std::string& name : names) for (const std::string& button_name : combo.buttons())
{ {
for (const auto& [code, nam] : map) for (const auto& [code, name] : map)
{ {
if (nam == name) if (button_name == name)
{ {
key_codes.insert(code); key_codes.insert(code);
break; break;
@ -82,12 +46,7 @@ std::set<u32> PadHandlerBase::find_key_codes(const std::unordered_map<u32, std::
} }
} }
if (!key_codes.empty()) return key_codes;
{
return key_codes;
}
return {};
} }
s32 PadHandlerBase::MultipliedInput(s32 raw_value, s32 multiplier) s32 PadHandlerBase::MultipliedInput(s32 raw_value, s32 multiplier)

View File

@ -192,14 +192,11 @@ protected:
std::shared_ptr<Pad> m_pad_for_pad_settings; std::shared_ptr<Pad> m_pad_for_pad_settings;
// Search an unordered map for a string value and return the found combo // Search an unordered map for a string value and return the found combos
static std::vector<std::set<u32>> find_key_combos(const std::unordered_map<u32, std::string>& map, const std::string& cfg_string, const std::string& fallback); static std::vector<std::set<u32>> find_key_combos(const std::unordered_map<u32, std::string>& map, const std::string& cfg_string);
// Search an unordered map for a string value and return the found combo // Search an unordered map for a combo and return the found key codes
static std::vector<std::set<u32>> find_key_combos(const std::unordered_map<u32, std::string>& map, const cfg::string& cfg_string, bool fallback = true); static std::set<u32> find_key_codes(const std::unordered_map<u32, std::string>& map, const pad::combo& combo);
// Search an unordered map for string values and return the found key codes
static std::set<u32> find_key_codes(const std::unordered_map<u32, std::string>& map, const std::vector<std::string>& names);
// Get normalized trigger value based on the range defined by a threshold // Get normalized trigger value based on the range defined by a threshold
u16 NormalizeTriggerInput(u16 value, u32 threshold) const; u16 NormalizeTriggerInput(u16 value, u32 threshold) const;

View File

@ -5,15 +5,15 @@
extern std::string g_input_config_override; extern std::string g_input_config_override;
std::vector<std::vector<std::string>> cfg_pad::get_buttons(std::string_view str) std::vector<pad::combo> cfg_pad::get_combos(std::string_view button_string)
{ {
if (str.empty()) if (button_string.empty())
return {}; return {};
// Handle special case: string contains separator itself as configured value (it's why I don't use fmt::split here) // Handle special case: string contains separator itself as configured value (it's why I don't use fmt::split here)
const auto split = [](std::string_view str, char sep) const auto split = [](std::string_view str, char sep)
{ {
std::vector<std::string> vec; std::set<std::string> buttons;
bool was_sep = true; bool was_sep = true;
usz btn_start = 0ULL; usz btn_start = 0ULL;
usz i = 0ULL; usz i = 0ULL;
@ -27,7 +27,7 @@ std::vector<std::vector<std::string>> cfg_pad::get_buttons(std::string_view str)
if (!was_sep) if (!was_sep)
{ {
was_sep = true; was_sep = true;
vec.push_back(std::string(str.substr(btn_start, i - btn_start))); buttons.insert(std::string(str.substr(btn_start, i - btn_start)));
continue; continue;
} }
} }
@ -40,54 +40,47 @@ std::vector<std::vector<std::string>> cfg_pad::get_buttons(std::string_view str)
if (i == (str.size() - 1)) if (i == (str.size() - 1))
{ {
vec.push_back(std::string(str.substr(btn_start, i - btn_start + 1))); buttons.insert(std::string(str.substr(btn_start, i - btn_start + 1)));
} }
} }
// Remove duplicates return buttons;
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
return vec;
}; };
std::vector<std::vector<std::string>> res; std::vector<pad::combo> combos;
// Get all combos (seperated by ',') // Get all combos (seperated by ',')
const std::vector<std::string> vec = split(str, ','); const std::set<std::string> combo_strings = split(button_string, ',');
for (const std::string& combo : vec) for (const std::string& combo_string : combo_strings)
{ {
// Get all keys for this combo (seperated by '&') // Get all keys for this combo (seperated by '&')
std::vector<std::string> keys = split(combo, '&'); std::set<std::string> combo = split(combo_string, '&');
if (!keys.empty()) if (!combo.empty())
{ {
res.push_back(std::move(keys)); combos.push_back(pad::combo{std::move(combo)});
} }
} }
return res; return combos;
} }
std::string cfg_pad::get_buttons(std::vector<std::vector<std::string>> vec) std::string cfg_pad::get_button_string(std::vector<pad::combo>& combos)
{ {
std::vector<std::string> combos; std::vector<std::string> combo_strings;
// Remove duplicates // Remove duplicates
std::sort(vec.begin(), vec.end()); std::sort(combos.begin(), combos.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); combos.erase(std::unique(combos.begin(), combos.end()), combos.end());
for (std::vector<std::string>& combo : vec) for (const pad::combo& combo : combos)
{ {
std::sort(combo.begin(), combo.end());
combo.erase(std::unique(combo.begin(), combo.end()), combo.end());
// Merge all keys for this combo (seperated by '&') // Merge all keys for this combo (seperated by '&')
combos.push_back(fmt::merge(combo, "&")); combo_strings.push_back(fmt::merge(combo.buttons(), "&"));
} }
// Merge combos (seperated by ',') // Merge combos (seperated by ',')
return fmt::merge(combos, ","); return fmt::merge(combo_strings, ",");
} }
u8 cfg_pad::get_motor_speed(VibrateMotor& motor, f32 multiplier) const u8 cfg_pad::get_motor_speed(VibrateMotor& motor, f32 multiplier) const

View File

@ -5,10 +5,42 @@
#include "Utilities/Config.h" #include "Utilities/Config.h"
#include <array> #include <array>
#include <vector>
namespace pad namespace pad
{ {
constexpr static std::string_view keyboard_device_name = "Keyboard"; constexpr static std::string_view keyboard_device_name = "Keyboard";
struct combo
{
public:
combo() = default;
combo(std::set<std::string> buttons) : m_buttons(std::move(buttons)) {}
const std::set<std::string>& buttons() const
{
return m_buttons;
}
void add_button(const std::string& button)
{
if (button.empty()) return;
m_buttons.insert(button);
}
bool operator==(const combo& other) const
{
return m_buttons == other.m_buttons;
}
bool operator<(const combo& other) const
{
return m_buttons < other.m_buttons;
}
private:
std::set<std::string> m_buttons;
};
} }
struct cfg_sensor final : cfg::node struct cfg_sensor final : cfg::node
@ -25,8 +57,8 @@ struct cfg_pad final : cfg::node
cfg_pad() {}; cfg_pad() {};
cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {} cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {}
static std::vector<std::vector<std::string>> get_buttons(std::string_view str); static std::vector<pad::combo> get_combos(std::string_view button_string);
static std::string get_buttons(std::vector<std::vector<std::string>> vec); static std::string get_button_string(std::vector<pad::combo>& combos);
u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const;
u8 get_large_motor_speed(std::array<VibrateMotor, 2>& motors) const; u8 get_large_motor_speed(std::array<VibrateMotor, 2>& motors) const;

View File

@ -373,7 +373,7 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s
const auto find_value = [&, this](const std::string& str) const auto find_value = [&, this](const std::string& str)
{ {
const std::vector<std::vector<std::string>> combos = cfg_pad::get_buttons(str); const std::vector<pad::combo> combos = cfg_pad::get_combos(str);
u16 value{}; u16 value{};
@ -385,19 +385,19 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s
} }
}; };
for (const std::vector<std::string>& names : combos) for (const pad::combo& combo : combos)
{ {
for (const u32 code : find_key_codes(rev_axis_list, names)) for (const u32 code : find_key_codes(rev_axis_list, combo))
{ {
set_value(code, true); set_value(code, true);
} }
for (const u32 code : find_key_codes(axis_list, names)) for (const u32 code : find_key_codes(axis_list, combo))
{ {
set_value(code, false); set_value(code, false);
} }
for (const u32 code : find_key_codes(button_list, names)) for (const u32 code : find_key_codes(button_list, combo))
{ {
set_value(code, false); set_value(code, false);
} }
@ -1379,37 +1379,37 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
const auto find_buttons = [&](const cfg::string& name) -> std::vector<std::set<u32>> const auto find_buttons = [&](const cfg::string& name) -> std::vector<std::set<u32>>
{ {
const std::vector<std::vector<std::string>> str_combos = cfg_pad::get_buttons(name.to_string()); const std::vector<pad::combo> combos = cfg_pad::get_combos(name.to_string());
// In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes. // In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes.
std::vector<std::set<u32>> combos; std::vector<std::set<u32>> index_combos;
for (const std::vector<std::string>& names : str_combos) for (const pad::combo& combo : combos)
{ {
std::set<u32> indices; std::set<u32> indices;
for (const u32 code : find_key_codes(axis_list, names)) for (const u32 code : find_key_codes(axis_list, combo))
{ {
indices.insert(register_evdevbutton(code, true, false)); indices.insert(register_evdevbutton(code, true, false));
} }
for (const u32 code : find_key_codes(rev_axis_list, names)) for (const u32 code : find_key_codes(rev_axis_list, combo))
{ {
indices.insert(register_evdevbutton(code, true, true)); indices.insert(register_evdevbutton(code, true, true));
} }
for (const u32 code : find_key_codes(button_list, names)) for (const u32 code : find_key_codes(button_list, combo))
{ {
indices.insert(register_evdevbutton(code, false, false)); indices.insert(register_evdevbutton(code, false, false));
} }
if (!indices.empty()) if (!indices.empty())
{ {
combos.push_back(std::move(indices)); index_combos.push_back(std::move(indices));
} }
} }
return combos; return index_combos;
}; };
const auto find_motion_button = [&](const cfg_sensor& sensor) -> evdev_sensor const auto find_motion_button = [&](const cfg_sensor& sensor) -> evdev_sensor

View File

@ -895,13 +895,13 @@ std::vector<std::set<u32>> keyboard_pad_handler::GetKeyCombos(const cfg::string&
{ {
std::vector<std::set<u32>> res; std::vector<std::set<u32>> res;
for (const std::vector<std::string>& combo : cfg_pad::get_buttons(cfg_string.to_string())) for (const pad::combo& combo : cfg_pad::get_combos(cfg_string.to_string()))
{ {
std::set<u32> key_codes; std::set<u32> key_codes;
for (const std::string& key_name : combo) for (const std::string& button : combo.buttons())
{ {
if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton) if (u32 code = GetKeyCode(QString::fromStdString(button)); code != Qt::NoButton)
{ {
key_codes.insert(code); key_codes.insert(code);
} }
@ -1051,7 +1051,7 @@ bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
const auto find_combos = [this](const cfg::string& name) const auto find_combos = [this](const cfg::string& name)
{ {
std::vector<std::set<u32>> combos = find_key_combos(mouse_list, name, false); std::vector<std::set<u32>> combos = find_key_combos(mouse_list, name);
for (const std::set<u32>& combo : GetKeyCombos(name)) combos.push_back(combo); for (const std::set<u32>& combo : GetKeyCombos(name)) combos.push_back(combo);
if (!combos.empty()) if (!combos.empty())

View File

@ -232,7 +232,7 @@ pad_preview_values mm_joystick_handler::get_preview_values(const std::unordered_
u16 value{}; u16 value{};
// The DS3 Button is considered pressed if any configured button combination is pressed // The DS3 Button is considered pressed if any configured button combination is pressed
for (const std::set<u32>& codes : find_key_combos(button_list, str, std::string())) for (const std::set<u32>& codes : find_key_combos(button_list, str))
{ {
bool combo_pressed = !codes.empty(); bool combo_pressed = !codes.empty();
u16 combo_val = 0; u16 combo_val = 0;

View File

@ -51,30 +51,30 @@ inline bool CreateConfigFile(const QString& dir, const QString& name)
return true; return true;
} }
void pad_settings_dialog::pad_button::insert_key(const std::string& key, binding_mode mode) void pad_settings_dialog::pad_button::insert_button(const std::string& button, binding_mode mode)
{ {
std::vector<std::vector<std::string>> combos; std::vector<pad::combo> combos;
if (mode != binding_mode::single) if (mode != binding_mode::single)
{ {
combos = cfg_pad::get_buttons(m_keys); combos = cfg_pad::get_combos(m_button_string);
} }
if (combos.empty() || mode != binding_mode::combo) if (combos.empty() || mode != binding_mode::combo)
{ {
combos.push_back({key}); combos.push_back(pad::combo({button}));
} }
else if (mode == binding_mode::combo) else if (mode == binding_mode::combo)
{ {
combos.back().push_back(key); combos.back().add_button(button);
} }
update(cfg_pad::get_buttons(combos)); update(cfg_pad::get_button_string(combos));
} }
void pad_settings_dialog::pad_button::update(const std::string& keys) void pad_settings_dialog::pad_button::update(const std::string& button_string)
{ {
m_keys = keys; m_button_string = button_string;
QString new_text = QString::fromStdString(keys); QString new_text = QString::fromStdString(button_string);
m_text = new_text.replace(",", ", ").replace("&", " + "); m_text = new_text.replace(",", ", ").replace("&", " + ");
} }
@ -622,7 +622,7 @@ void pad_settings_dialog::InitButtons()
{ {
if (value == 0) continue; if (value == 0) continue;
m_cfg_entries[m_button_id].insert_key(key, mode); m_cfg_entries[m_button_id].insert_button(key, mode);
// Switch to combo mode for all further keys // Switch to combo mode for all further keys
mode = binding_mode::combo; mode = binding_mode::combo;
@ -632,7 +632,7 @@ void pad_settings_dialog::InitButtons()
{ {
if (value == 0) continue; if (value == 0) continue;
m_cfg_entries[m_button_id].insert_key(key, mode); m_cfg_entries[m_button_id].insert_button(key, mode);
// Switch to combo mode for all further keys // Switch to combo mode for all further keys
mode = binding_mode::combo; mode = binding_mode::combo;
@ -674,16 +674,16 @@ void pad_settings_dialog::InitButtons()
const std::vector<std::string> buttons = const std::vector<std::string> buttons =
{ {
m_cfg_entries[button_ids::id_pad_l2].keys(), m_cfg_entries[button_ids::id_pad_l2].button_string(),
m_cfg_entries[button_ids::id_pad_r2].keys(), m_cfg_entries[button_ids::id_pad_r2].button_string(),
m_cfg_entries[button_ids::id_pad_lstick_left].keys(), m_cfg_entries[button_ids::id_pad_lstick_left].button_string(),
m_cfg_entries[button_ids::id_pad_lstick_right].keys(), m_cfg_entries[button_ids::id_pad_lstick_right].button_string(),
m_cfg_entries[button_ids::id_pad_lstick_down].keys(), m_cfg_entries[button_ids::id_pad_lstick_down].button_string(),
m_cfg_entries[button_ids::id_pad_lstick_up].keys(), m_cfg_entries[button_ids::id_pad_lstick_up].button_string(),
m_cfg_entries[button_ids::id_pad_rstick_left].keys(), m_cfg_entries[button_ids::id_pad_rstick_left].button_string(),
m_cfg_entries[button_ids::id_pad_rstick_right].keys(), m_cfg_entries[button_ids::id_pad_rstick_right].button_string(),
m_cfg_entries[button_ids::id_pad_rstick_down].keys(), m_cfg_entries[button_ids::id_pad_rstick_down].button_string(),
m_cfg_entries[button_ids::id_pad_rstick_up].keys() m_cfg_entries[button_ids::id_pad_rstick_up].button_string()
}; };
// Check if this is the first call during a remap // Check if this is the first call during a remap
@ -1023,7 +1023,7 @@ void pad_settings_dialog::keyPressEvent(QKeyEvent* keyEvent)
} }
else else
{ {
m_cfg_entries[m_button_id].insert_key(keyboard_pad_handler::GetKeyName(keyEvent, false), m_binding_mode); m_cfg_entries[m_button_id].insert_button(keyboard_pad_handler::GetKeyName(keyEvent, false), m_binding_mode);
} }
ReactivateButtons(); ReactivateButtons();
@ -1050,7 +1050,7 @@ void pad_settings_dialog::mouseReleaseEvent(QMouseEvent* event)
} }
else else
{ {
m_cfg_entries[m_button_id].insert_key((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(event), m_binding_mode); m_cfg_entries[m_button_id].insert_button((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(event), m_binding_mode);
} }
ReactivateButtons(); ReactivateButtons();
@ -1112,7 +1112,7 @@ void pad_settings_dialog::wheelEvent(QWheelEvent* event)
} }
} }
m_cfg_entries[m_button_id].insert_key((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key), m_binding_mode); m_cfg_entries[m_button_id].insert_button((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key), m_binding_mode);
ReactivateButtons(); ReactivateButtons();
} }
@ -1163,7 +1163,7 @@ void pad_settings_dialog::mouseMoveEvent(QMouseEvent* event)
if (key != 0) if (key != 0)
{ {
m_cfg_entries[m_button_id].insert_key((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key), m_binding_mode); m_cfg_entries[m_button_id].insert_button((static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key), m_binding_mode);
ReactivateButtons(); ReactivateButtons();
} }
} }
@ -2110,7 +2110,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
// Check for duplicate button choices // Check for duplicate button choices
if (m_handler->m_type != pad_handler::null) if (m_handler->m_type != pad_handler::null)
{ {
std::set<std::string> unique_keys; std::set<std::string> unique_button_strings;
for (const auto& [id, button] : m_cfg_entries) for (const auto& [id, button] : m_cfg_entries)
{ {
// Let's ignore special keys, unless we're using a keyboard // Let's ignore special keys, unless we're using a keyboard
@ -2120,13 +2120,13 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
continue; continue;
} }
for (const std::vector<std::string>& combo : cfg_pad::get_buttons(button.keys())) for (const pad::combo& combo : cfg_pad::get_combos(button.button_string()))
{ {
for (const std::string& key : combo) for (const std::string& button_string : combo.buttons())
{ {
if (const auto& [it, ok] = unique_keys.insert(key); !ok) if (const auto& [it, ok] = unique_button_strings.insert(button_string); !ok)
{ {
m_duplicate_buttons[m_last_player_id] = key; m_duplicate_buttons[m_last_player_id] = button_string;
break; break;
} }
} }
@ -2137,7 +2137,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
// Apply buttons // Apply buttons
for (const auto& entry : m_cfg_entries) for (const auto& entry : m_cfg_entries)
{ {
entry.second.cfg_text()->from_string(entry.second.keys()); entry.second.cfg_text()->from_string(entry.second.button_string());
} }
// Apply rest of config // Apply rest of config

View File

@ -93,16 +93,16 @@ class pad_settings_dialog : public QDialog
update(*cfg_text); update(*cfg_text);
} }
void insert_key(const std::string& key, binding_mode mode); void insert_button(const std::string& button, binding_mode mode);
void update(const std::string& keys); void update(const std::string& button_string);
cfg::string* cfg_text() const { return m_cfg_text; } cfg::string* cfg_text() const { return m_cfg_text; }
const std::string& keys() const { return m_keys; } const std::string& button_string() const { return m_button_string; }
const QString& text() const { return m_text; } const QString& text() const { return m_text; }
private: private:
cfg::string* m_cfg_text = nullptr; cfg::string* m_cfg_text = nullptr;
std::string m_keys; std::string m_button_string;
QString m_text; QString m_text;
}; };