381 lines
11 KiB
C++
381 lines
11 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#include "InputBindings.h"
|
|
#include "Input.h"
|
|
#include "SDL_events.h"
|
|
#include "SDL_joystick.h"
|
|
#include "utils.h"
|
|
|
|
#include <regex>
|
|
|
|
using namespace InputBindings;
|
|
|
|
namespace Input {
|
|
int JoystickFromGUIDString(const std::string &guid);
|
|
std::string JoystickGUIDString(int joystickID);
|
|
} // namespace Input
|
|
|
|
// Helper function to early-out in Matches()
|
|
Response MatchType(const KeyBinding &k, const SDL_Event &ev)
|
|
{
|
|
using Type = KeyBinding::Type;
|
|
|
|
bool p = false;
|
|
bool r = false;
|
|
|
|
switch (k.type) {
|
|
case Type::Disabled:
|
|
return Response::Ignored;
|
|
case Type::KeyboardKey:
|
|
p = ev.type == SDL_KEYDOWN;
|
|
r = ev.type == SDL_KEYUP;
|
|
break;
|
|
case Type::JoystickButton:
|
|
p = ev.type == SDL_JOYBUTTONDOWN;
|
|
r = ev.type == SDL_JOYBUTTONUP;
|
|
break;
|
|
case Type::JoystickHat:
|
|
if (ev.type == SDL_JOYHATMOTION) {
|
|
p = (ev.jhat.value & k.joystick.button) == k.joystick.button;
|
|
r = !p;
|
|
}
|
|
break;
|
|
case Type::MouseButton:
|
|
p = ev.type == SDL_MOUSEBUTTONDOWN;
|
|
r = ev.type == SDL_MOUSEBUTTONUP;
|
|
break;
|
|
}
|
|
|
|
return p ? Response::Pressed : r ? Response::Released : Response::Ignored;
|
|
}
|
|
|
|
Response KeyBinding::Matches(const SDL_Event &ev) const
|
|
{
|
|
Response r = MatchType(*this, ev);
|
|
if (r == Response::Ignored)
|
|
return r;
|
|
|
|
if (type == Type::KeyboardKey) {
|
|
return (ev.key.keysym.sym == keycode) ? r : Response::Ignored;
|
|
} else if (type == Type::JoystickButton) {
|
|
bool cond = (joystick.id == ev.jbutton.which && joystick.button == ev.jbutton.button);
|
|
return cond ? r : Response::Ignored;
|
|
} else if (type == Type::JoystickHat) {
|
|
bool cond = (joystick.id == ev.jhat.which && joystick.hat == ev.jhat.hat);
|
|
return cond ? r : Response::Ignored;
|
|
} else if (type == Type::MouseButton) {
|
|
return (mouse.button == ev.button.button) ? r : Response::Ignored;
|
|
}
|
|
|
|
return Response::Ignored;
|
|
}
|
|
|
|
bool KeyBinding::operator==(const KeyBinding &rhs) const
|
|
{
|
|
if (type != rhs.type)
|
|
return false;
|
|
|
|
if (type == Type::KeyboardKey)
|
|
return keycode == rhs.keycode;
|
|
else if (type == Type::JoystickButton)
|
|
return joystick.id == rhs.joystick.id && joystick.button == rhs.joystick.button;
|
|
else if (type == Type::JoystickHat)
|
|
return joystick.id == rhs.joystick.id && joystick.hat == rhs.joystick.hat && joystick.button == rhs.joystick.button;
|
|
else if (type == Type::MouseButton)
|
|
return mouse.button == rhs.mouse.button;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
#define ret_if_different(val) \
|
|
if (val != rhs.val) return val < rhs.val
|
|
bool KeyBinding::operator<(const KeyBinding &rhs) const
|
|
{
|
|
ret_if_different(type);
|
|
|
|
switch (type) {
|
|
case Type::KeyboardKey:
|
|
ret_if_different(keycode);
|
|
break;
|
|
case Type::JoystickButton:
|
|
ret_if_different(joystick.id);
|
|
ret_if_different(joystick.button);
|
|
break;
|
|
case Type::JoystickHat:
|
|
ret_if_different(joystick.id);
|
|
ret_if_different(joystick.hat);
|
|
ret_if_different(joystick.button);
|
|
break;
|
|
case Type::MouseButton:
|
|
ret_if_different(mouse.button);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return this < &rhs;
|
|
}
|
|
#undef ret_if_different
|
|
|
|
Action &Action::operator=(const Action &rhs)
|
|
{
|
|
binding = rhs.binding;
|
|
binding2 = rhs.binding2;
|
|
return *this;
|
|
}
|
|
|
|
Axis &Axis::operator=(const Axis &rhs)
|
|
{
|
|
axis = rhs.axis;
|
|
positive = rhs.positive;
|
|
negative = rhs.negative;
|
|
return *this;
|
|
}
|
|
|
|
// ============================================================================
|
|
//
|
|
// Config Loading
|
|
//
|
|
// ============================================================================
|
|
|
|
using smatch = std::match_results<std::string_view::const_iterator>;
|
|
static std::regex disabled_matcher("^disabled", std::regex::icase);
|
|
|
|
// Handle the fiddly bits of matching a regex and advancing the beginning of a string
|
|
bool consumeMatch(std::string_view &str, smatch &match_results, std::regex ®)
|
|
{
|
|
if (!std::regex_search(str.cbegin(), str.cend(), match_results, reg))
|
|
return false;
|
|
|
|
str.remove_prefix(std::distance(str.cbegin(), match_results[0].second));
|
|
return true;
|
|
}
|
|
|
|
// Key54 | JoyGUID/B3 JoyGUID/H4/2 | Mouse5
|
|
// Less awful than iostreams, but still not elegant. That's C++ for you.
|
|
// TODO: save joystick id->GUID mapping separately in the config file and
|
|
// don't write them here to save space
|
|
std::string_view &InputBindings::operator>>(std::string_view &str, KeyBinding &out)
|
|
{
|
|
static std::regex key_matcher("^Key(\\d+)");
|
|
static std::regex joystick_matcher("^Joy([^/]{32})");
|
|
static std::regex joystick_button("^/B(\\d+)");
|
|
static std::regex joystick_hat("^/H(\\d+)/(\\d+)");
|
|
static std::regex mouse_matcher("^Mouse(\\d+)");
|
|
|
|
const auto begin = str.cbegin();
|
|
const auto end = str.cend();
|
|
|
|
// match_results[0].second should always point to the character after the
|
|
// parsed key binding unless the value present could not be parsed.
|
|
smatch match_results;
|
|
if (std::regex_search(begin, end, match_results, key_matcher)) {
|
|
out.type = KeyBinding::Type::KeyboardKey;
|
|
out.keycode = std::stoi(match_results[1]);
|
|
} else if (std::regex_search(begin, end, match_results, joystick_matcher)) {
|
|
out.joystick.id = Input::JoystickFromGUIDString(match_results[1]);
|
|
const auto start = match_results[0].second;
|
|
if (std::regex_search(start, end, match_results, joystick_button)) {
|
|
out.type = KeyBinding::Type::JoystickButton;
|
|
out.joystick.button = std::stoi(match_results[1]);
|
|
out.joystick.hat = 0;
|
|
} else if (std::regex_search(start, end, match_results, joystick_hat)) {
|
|
out.type = KeyBinding::Type::JoystickHat;
|
|
out.joystick.hat = std::stoi(match_results[1]);
|
|
out.joystick.button = std::stoi(match_results[2]);
|
|
}
|
|
} else if (std::regex_search(begin, end, match_results, mouse_matcher)) {
|
|
out.type = KeyBinding::Type::MouseButton;
|
|
out.mouse.button = std::stoi(match_results[1]);
|
|
} else {
|
|
out.type = KeyBinding::Type::Disabled;
|
|
// consume the disabled text if present.
|
|
std::regex_search(begin, end, match_results, disabled_matcher);
|
|
}
|
|
|
|
// return a string view containing the rest of the string
|
|
if (!match_results.empty())
|
|
str.remove_prefix(std::distance(begin, match_results[0].second));
|
|
|
|
return str;
|
|
}
|
|
|
|
// Serialize a KeyBinding into the output stream.
|
|
// Writes nothing if the binding is disabled
|
|
std::ostream &InputBindings::operator<<(std::ostream &str, const KeyBinding &in)
|
|
{
|
|
switch (in.type) {
|
|
case KeyBinding::Type::KeyboardKey:
|
|
return str << "Key" << in.keycode;
|
|
case KeyBinding::Type::JoystickButton:
|
|
return str << "Joy" << Input::JoystickGUIDString(in.joystick.id)
|
|
<< "/B" << int(in.joystick.button);
|
|
case KeyBinding::Type::JoystickHat:
|
|
return str << "Joy" << Input::JoystickGUIDString(in.joystick.id)
|
|
<< "/H" << int(in.joystick.hat) << "/" << int(in.joystick.button);
|
|
case KeyBinding::Type::MouseButton:
|
|
return str << "Mouse" << int(in.mouse.button);
|
|
default:
|
|
return str;
|
|
}
|
|
}
|
|
|
|
// Match [-]JoyGUID/A4
|
|
std::string_view &InputBindings::operator>>(std::string_view &str, JoyAxis &out)
|
|
{
|
|
static std::regex joy_matcher("^Joy([^/]{32})/A(\\d+)");
|
|
auto begin = str.cbegin();
|
|
|
|
bool reverse = !str.empty() && str[0] == '-';
|
|
if (reverse)
|
|
++begin;
|
|
|
|
smatch match_results;
|
|
if (std::regex_search(begin, str.cend(), match_results, joy_matcher)) {
|
|
out.joystickId = Input::JoystickFromGUIDString(match_results[1]);
|
|
out.axis = std::stoi(match_results[2]);
|
|
out.direction = reverse ? -1 : 1;
|
|
} else {
|
|
std::regex_search(str.cbegin(), str.cend(), match_results, std::regex("^disabled"));
|
|
out.direction = 0;
|
|
}
|
|
|
|
if (!match_results.empty())
|
|
str.remove_prefix(std::distance(str.cbegin(), match_results[0].second));
|
|
|
|
return str;
|
|
}
|
|
|
|
// [-]JoyGUID/A4
|
|
std::ostream &InputBindings::operator<<(std::ostream &str, const JoyAxis &in)
|
|
{
|
|
if (!in.Enabled())
|
|
return str << "disabled";
|
|
|
|
return str << (in.direction < 0.0 ? "-Joy" : "Joy")
|
|
<< Input::JoystickGUIDString(in.joystickId)
|
|
<< "/A" << int(in.axis);
|
|
}
|
|
|
|
// find a close paren, copy str into ret str, and return retstr
|
|
// (for one-line failure case returns)
|
|
std::string_view &findCloseParen(std::string_view &str, std::string_view &retstr, smatch &match_results)
|
|
{
|
|
if (std::regex_search(str.cbegin(), str.cend(), match_results, std::regex("\\)")))
|
|
str.remove_prefix(std::distance(str.cbegin(), match_results[0].second));
|
|
|
|
retstr = str;
|
|
return retstr;
|
|
}
|
|
|
|
// Parse KeyChord(Key53 + JoyGUID/B3 + Mouse1) | KeyChord(Mouse5)
|
|
std::string_view &InputBindings::operator>>(std::string_view &str, KeyChord &out)
|
|
{
|
|
static std::regex key_chord("^KeyChord\\(\\s*");
|
|
static std::regex plus_sign("^\\s*\\+\\s*");
|
|
smatch match_results;
|
|
|
|
// Early-out for disabled key chord
|
|
if (consumeMatch(str, match_results, disabled_matcher))
|
|
return str;
|
|
|
|
// make a copy of the string view so we can nondestructively consume matches.
|
|
std::string_view iterstr = str;
|
|
|
|
// ensure we read the KeyChord( opening
|
|
if (!consumeMatch(iterstr, match_results, key_chord))
|
|
return str;
|
|
|
|
// read the activator KeyBinding
|
|
iterstr >> out.activator;
|
|
|
|
// if the activator is disabled, early-out here.
|
|
// if we don't have a following plus sign, discard everything to the next close-paren
|
|
if (!out.activator.Enabled() || !consumeMatch(iterstr, match_results, plus_sign))
|
|
return findCloseParen(iterstr, str, match_results);
|
|
|
|
// read the first modifier
|
|
iterstr >> out.modifier1;
|
|
|
|
// ditto for the second modifier
|
|
if (!out.modifier1.Enabled() || !consumeMatch(iterstr, match_results, plus_sign))
|
|
return findCloseParen(iterstr, str, match_results);
|
|
|
|
iterstr >> out.modifier2;
|
|
|
|
return findCloseParen(iterstr, str, match_results);
|
|
}
|
|
|
|
// KeyChord(Key54 + JoyGUID/B4 + JoyGUID/H1/3)
|
|
std::ostream &InputBindings::operator<<(std::ostream &str, const KeyChord &in)
|
|
{
|
|
if (!in.Enabled())
|
|
return str << "disabled";
|
|
|
|
str << "KeyChord(" << in.activator;
|
|
if (in.modifier1.Enabled()) {
|
|
str << " + " << in.modifier1;
|
|
if (in.modifier2.Enabled())
|
|
str << " + " << in.modifier2;
|
|
}
|
|
str << ")";
|
|
|
|
return str;
|
|
}
|
|
|
|
std::string_view &InputBindings::operator>>(std::string_view &str, Axis &out)
|
|
{
|
|
static std::regex input_axis("^InputAxis\\(\\s*");
|
|
static std::regex comma_sep("^\\s*,\\s*");
|
|
smatch match_results;
|
|
|
|
auto iterstr = str;
|
|
if (!consumeMatch(iterstr, match_results, input_axis))
|
|
return str;
|
|
iterstr >> out.axis;
|
|
|
|
if (!consumeMatch(iterstr, match_results, comma_sep))
|
|
return findCloseParen(iterstr, str, match_results);
|
|
|
|
iterstr >> out.negative;
|
|
|
|
if (!consumeMatch(iterstr, match_results, comma_sep))
|
|
return findCloseParen(iterstr, str, match_results);
|
|
iterstr >> out.positive;
|
|
|
|
return findCloseParen(iterstr, str, match_results);
|
|
}
|
|
|
|
// InputAxis(-JoyGUID/A3, KeyChord(Key32), KeyChord(Mouse5 + JoyGUID/H1/1))
|
|
// InputAxis(disabled, disabled, disabled)
|
|
std::ostream &InputBindings::operator<<(std::ostream &str, const Axis &in)
|
|
{
|
|
return str << "InputAxis(" << in.axis << ", " << in.negative << ", " << in.positive << ")";
|
|
}
|
|
|
|
std::string_view &InputBindings::operator>>(std::string_view &str, Action &out)
|
|
{
|
|
static std::regex input_action("^InputAction\\(\\s*");
|
|
static std::regex comma_sep("^\\s*,\\s*");
|
|
smatch match_results;
|
|
|
|
auto iterstr = str;
|
|
if (!consumeMatch(iterstr, match_results, input_action))
|
|
return str;
|
|
iterstr >> out.binding;
|
|
|
|
if (!consumeMatch(iterstr, match_results, comma_sep))
|
|
return findCloseParen(iterstr, str, match_results);
|
|
iterstr >> out.binding2;
|
|
|
|
return findCloseParen(iterstr, str, match_results);
|
|
}
|
|
|
|
// InputAction(KeyChord(Key53 + Mouse4), KeyChord(Mouse5 + JoyGUID/H1/1))
|
|
// InputAction(disabled, disabled)
|
|
std::ostream &InputBindings::operator<<(std::ostream &str, const Action &in)
|
|
{
|
|
return str << "InputAction(" << in.binding << ", " << in.binding2 << ")";
|
|
}
|