rawinput: modifiers (key combinations) (#813)

## Link to GitHub Issue or related Pull Request, if one exists
Fixes #635

## Description of change
Adds optional **button modifiers**, allowing a binding to require one or
more "modifier" buttons to be held before it activates.

- New "Modifiers" controller page in the overlay config to bind the
Modifier 1–4 source buttons.
- Each binding gains a 4-bit modifier_mask (Modifier 1-4). It is
persisted as an optional modifiers XML attribute on button nodes and
controller-preset entries. The attribute is optional and defaults to 0,
so existing config files and presets load unchanged and the feature is
off unless the user opts in.
- The button Edit properties popup gains a "Modifiers" dropdown to pick
which modifiers a binding requires. Doesn't apply to MIDI though.
- Input evaluation skips a binding whose required modifiers are not
held, falling through to its alternatives; velocity reporting uses the
same gated path.
- Controller-preset templates gain a "Modifiers" group for importing and
exporting.

## Testing
This commit is contained in:
bicarus
2026-07-21 00:26:36 -07:00
committed by GitHub
parent 623e1e3998
commit ed7318c270
11 changed files with 393 additions and 48 deletions
+32
View File
@@ -44,6 +44,7 @@ namespace games {
static bool IO_INITIALIZED = false;
static std::vector<std::string> games;
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_modifiers;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
static robin_hood::unordered_map<std::string, std::string> buttons_help;
@@ -365,6 +366,37 @@ namespace games {
return it->second;
}
static std::vector<Button> gen_buttons_modifiers(const std::string &game) {
auto modifier_buttons = GameAPI::Buttons::getButtons(game);
const std::vector<std::string> names {
"Modifier 1",
"Modifier 2",
"Modifier 3",
"Modifier 4",
};
modifier_buttons = GameAPI::Buttons::sortButtons(modifier_buttons, names);
for (auto &modifier : modifier_buttons) {
modifier.setModifierMask(0);
for (auto &alternative : modifier.getAlternatives()) {
alternative.setModifierMask(0);
}
}
return modifier_buttons;
}
std::vector<Button> *get_buttons_modifiers(const std::string &game) {
initialize();
auto it = buttons_modifiers.find(game);
if (it == buttons_modifiers.end()) {
if (game.empty()) {
return nullptr;
}
buttons_modifiers[game] = gen_buttons_modifiers(game);
return &buttons_modifiers[game];
}
return &it->second;
}
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names;
+11
View File
@@ -5,6 +5,16 @@
namespace games {
namespace ModifierButtons {
enum {
Modifier1,
Modifier2,
Modifier3,
Modifier4,
Size,
};
}
namespace OverlayButtons {
enum {
Screenshot,
@@ -59,6 +69,7 @@ namespace games {
const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(const std::string &game);
std::vector<Button> *get_buttons_modifiers(const std::string &game);
std::string get_buttons_help(const std::string &game);
std::string get_analogs_help(const std::string &game);
std::vector<Button> *get_buttons_keypads(const std::string &game);