Add sdl controller mapping parser

This commit is contained in:
Benau
2020-04-21 09:07:29 +08:00
parent d3f45e84a3
commit 2405a3a00c
5 changed files with 334 additions and 154 deletions

View File

@@ -357,5 +357,25 @@ bool DeviceConfig::load(const XMLNode *config)
return !error;
} // load
// ---------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool DeviceConfig::hasBindingFor(const int button_id) const
{
for (int n=0; n<PA_COUNT; n++)
{
if (m_bindings[n].getId() == button_id) return true;
}
return false;
} // hasBindingFor
//-----------------------------------------------------------------------------
bool DeviceConfig::hasBindingFor(const int button_id, PlayerAction from,
PlayerAction to) const
{
for (int n=from; n<=to; n++)
{
if (m_bindings[n].getId() == button_id) return true;
}
return false;
} // hasBindingFor

View File

@@ -28,8 +28,31 @@
#include <assert.h>
using namespace irr;
#ifndef SERVER_ONLY
#include "input/sdl_controller.hpp"
#include <array>
#include <SDL.h>
static_assert(SDL_CONTROLLER_BUTTON_MAX - 1 == SDL_CONTROLLER_BUTTON_DPAD_RIGHT, "non continous name");
enum AxisWithDirection
{
SDL_CONTROLLER_AXIS_LEFTX_RIGHT = SDL_CONTROLLER_BUTTON_MAX,
SDL_CONTROLLER_AXIS_LEFTX_LEFT,
SDL_CONTROLLER_AXIS_LEFTY_DOWN,
SDL_CONTROLLER_AXIS_LEFTY_UP,
SDL_CONTROLLER_AXIS_RIGHTX_RIGHT,
SDL_CONTROLLER_AXIS_RIGHTX_LEFT,
SDL_CONTROLLER_AXIS_RIGHTY_DOWN,
SDL_CONTROLLER_AXIS_RIGHTY_UP,
// Triggers only have single direction
SDL_CONTROLLER_AXIS_TRIGGERLEFT_UP,
SDL_CONTROLLER_AXIS_TRIGGERRIGHT_UP,
SDL_CONTROLLER_AXIS_WITH_DIRECTION_AND_BUTTON_MAX,
};
#endif
using namespace irr;
GamepadConfig::GamepadConfig( const std::string &name,
const int axis_count,
@@ -39,11 +62,11 @@ GamepadConfig::GamepadConfig( const std::string &name,
setName(name);
m_axis_count = axis_count;
m_button_count = button_count;
m_hat_count = 0;
m_deadzone = 4096;
m_is_analog = true;
m_desensitize = false;
setDefaultBinds();
detectType();
} // GamepadConfig
//------------------------------------------------------------------------------
@@ -52,6 +75,7 @@ GamepadConfig::GamepadConfig() : DeviceConfig()
{
m_axis_count = 0;
m_button_count = 0;
m_hat_count = 0;
m_deadzone = 4096;
m_is_analog = true;
m_desensitize = false;
@@ -75,7 +99,6 @@ bool GamepadConfig::load(const XMLNode *config)
Log::error("DeviceConfig", "Unnamed joystick in config file.");
return false;
}
detectType();
return ok;
} // load
@@ -96,30 +119,6 @@ void GamepadConfig::save (std::ofstream& stream)
stream << "</gamepad>\n\n";
} // save
//-----------------------------------------------------------------------------
/** Try to identify a gamepad type (e.g. 'xbox'), so that better defaults
* and button names can be set. Atm the gamepad name is used.
*/
void GamepadConfig::detectType()
{
m_type = GP_UNIDENTIFIED;
std::string lower = StringUtils::toLowerCase(getName());
// xbox appears to be xbox 360
if(lower.find("xbox")!=std::string::npos)
{
m_type = GP_XBOX360;
return;
}
// The original xbox gamepad
if(lower.find("x-box")!=std::string::npos)
{
m_type = GP_XBOX_ORIGINAL;
return;
}
} // detectType
//------------------------------------------------------------------------------
void GamepadConfig::setDefaultBinds ()
@@ -146,120 +145,86 @@ void GamepadConfig::setDefaultBinds ()
//------------------------------------------------------------------------------
core::stringw GamepadConfig::getBindingAsString(const PlayerAction action) const
{
// Default names if gamepad is not identified
if(m_type==GP_UNIDENTIFIED) return DeviceConfig::getBindingAsString(action);
#ifndef SERVER_ONLY
std::array<core::stringw, SDL_CONTROLLER_AXIS_WITH_DIRECTION_AND_BUTTON_MAX> readable =
{{
"A", // SDL_CONTROLLER_BUTTON_A
"B", // SDL_CONTROLLER_BUTTON_B
"X", // SDL_CONTROLLER_BUTTON_X
"Y", // SDL_CONTROLLER_BUTTON_Y
// I18N: name of buttons on gamepads
_("Back"), // SDL_CONTROLLER_BUTTON_BACK
// I18N: name of buttons on gamepads
_("Guide"), // SDL_CONTROLLER_BUTTON_GUIDE
// I18N: name of buttons on gamepads
_("Start"), // SDL_CONTROLLER_BUTTON_START
// I18N: name of buttons on gamepads
_("Left stick"), // SDL_CONTROLLER_BUTTON_LEFTSTICK
// I18N: name of buttons on gamepads
_("Right stick"), // SDL_CONTROLLER_BUTTON_RIGHTSTICK
// I18N: name of buttons on gamepads
_("Left shoulder"), // SDL_CONTROLLER_BUTTON_LEFTSHOULDER
// I18N: name of buttons on gamepads
_("Right shoulder"), // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
// I18N: name of buttons on gamepads
_("DPad up"), // SDL_CONTROLLER_BUTTON_DPAD_UP
// I18N: name of buttons on gamepads
_("DPad down"), // SDL_CONTROLLER_BUTTON_DPAD_DOWN
// I18N: name of buttons on gamepads
_("DPad left"), // SDL_CONTROLLER_BUTTON_DPAD_LEFT
// I18N: name of buttons on gamepads
_("DPad right"), // SDL_CONTROLLER_BUTTON_DPAD_RIGHT
// Below are extensions after SDL2 header SDL_CONTROLLER_BUTTON_MAX
// I18N: name of buttons on gamepads
_("Left thumbstick right"), // SDL_CONTROLLER_AXIS_LEFTX_RIGHT
// I18N: name of buttons on gamepads
_("Left thumbstick left"), // SDL_CONTROLLER_AXIS_LEFTX_LEFT
// I18N: name of buttons on gamepads
_("Left thumbstick down"), // SDL_CONTROLLER_AXIS_LEFTY_DOWN
// I18N: name of buttons on gamepads
_("Left thumbstick up"), // SDL_CONTROLLER_AXIS_LEFTY_UP
// I18N: name of buttons on gamepads
_("Right thumbstick right"), // SDL_CONTROLLER_AXIS_RIGHTX_RIGHT
// I18N: name of buttons on gamepads
_("Right thumbstick left"), // SDL_CONTROLLER_AXIS_RIGHTX_LEFT
// I18N: name of buttons on gamepads
_("Right thumbstick down"), // SDL_CONTROLLER_AXIS_RIGHTY_DOWN
// I18N: name of buttons on gamepads
_("Right thumbstick up"), // SDL_CONTROLLER_AXIS_RIGHTY_UP
// I18N: name of buttons on gamepads
_("Left trigger"), // SDL_CONTROLLER_AXIS_TRIGGERLEFT_UP
// I18N: name of buttons on gamepads
_("Right trigger") // SDL_CONTROLLER_AXIS_TRIGGERRIGHT_UP
}};
const Binding &b = getBinding(action);
int id = b.getId();
Input::AxisDirection ad = b.getDirection();
Input::InputType it = b.getType();
// XBOX-360 controller
// -------------------
if(m_type==GP_XBOX_ORIGINAL)
if (it == Input::IT_STICKBUTTON)
{
// Handle only the differences to the xbox 360 controller, the rest
// will 'fall trough' to the xbox 360 code below
if(it==Input::IT_STICKBUTTON)
auto ret = m_sdl_mapping.find(std::make_tuple(id, ad));
if (ret != m_sdl_mapping.end())
return readable[ret->second];
}
else if (it == Input::IT_STICKMOTION)
{
auto ret = m_sdl_mapping.find(std::make_tuple(id, ad));
if (ret != m_sdl_mapping.end())
{
switch(id)
core::stringw name = readable[ret->second];
if (b.getRange() == Input::AR_FULL)
{
// I18N: Name of the black button on xbox controller
case 2: return _("Black");
case 3: return "X";
case 4: return "Y";
// I18N: Name of the white button on xbox controller
case 5: return _("White");
if (ad == Input::AD_POSITIVE)
name += L" (-+)";
else if (ad == Input::AD_NEGATIVE)
name += L" (+-)";
}
return name;
}
if(it==Input::IT_STICKMOTION)
{
switch(id)
{
// I18N: name of buttons on gamepads
case 2: return _("Left trigger");
// I18N: name of buttons on gamepads
case 3: return (ad == Input::AD_POSITIVE) ? _("Right thumb right")
// I18N: name of buttons on gamepads
: _("Right thumb left");
case // I18N: name of buttons on gamepads
4: return (ad == Input::AD_POSITIVE) ? _("Right thumb down")
// I18N: name of buttons on gamepads
: _("Right thumb up");
// I18N: name of buttons on gamepads
case 5: return _("Right trigger");
// I18N: name of buttons on gamepads
case 6: return (ad == Input::AD_POSITIVE) ? _("DPad right")
// I18N: name of buttons on gamepads
: _("DPad left");
// I18N: name of buttons on gamepads
case 7: return (ad == Input::AD_POSITIVE) ? _("DPad down")
// I18N: name of buttons on gamepads
: _("DPad up");
} // switch
} // stickmotion
} // xbox (original)
if(m_type==GP_XBOX360 || m_type==GP_XBOX_ORIGINAL)
{
if(it==Input::IT_STICKBUTTON)
{
switch(id)
{
case 0: return "A";
case 1: return "B";
case 2: return "X";
case 3: return "Y";
// I18N: name of buttons on gamepads
case 4: return _("Left bumper");
// I18N: name of buttons on gamepads
case 5: return _("Right bumper");
// I18N: name of buttons on gamepads
case 6: return _("Back");
// I18N: name of buttons on gamepads
case 7: return _("Start");
// I18N: name of buttons on gamepads
case 8: return _("Left thumb button");
// I18N: name of buttons on gamepads
case 9: return _("Right thumb button");
default: return DeviceConfig::getBindingAsString(action);
} // switch
} // if IT_STICKBUTTON
if(it==Input::IT_STICKMOTION)
{
switch(id)
{
// I18N: name of stick on gamepads
case 0: return (ad==Input::AD_POSITIVE) ? _("Left thumb right")
// I18N: name of stick on gamepads
: _("Left thumb left");
// I18N: name of stick on gamepads
case 1: return (ad==Input::AD_POSITIVE) ? _("Left thumb down")
// I18N: name of stick on gamepads
: _("Left thumb up");
// I18N: name of stick on gamepads
case 2: return _("Left trigger"); // I18N: name of trigger on gamepads
case 3: return (ad==Input::AD_POSITIVE) ? _("Right thumb down")
// I18N: name of stick on gamepads
: _("Right thumb up");
// I18N: name of stick on gamepads
case 4: return (ad==Input::AD_POSITIVE) ? _("Right thumb right")
// I18N: name of stick on gamepads
: _("Right thumb left");
// I18N: name of buttons on gamepads
case 5: return _("Right trigger"); // I18N: name of trigger on gamepads
case Input::HAT_H_ID: return (ad == Input::AD_POSITIVE) ? _("DPad up")
// I18N: name of buttons on gamepads
: _("DPad down");
// I18N: name of buttons on gamepads
case Input::HAT_V_ID: return (ad == Input::AD_POSITIVE) ? _("DPad right")
// I18N: name of buttons on gamepads
: _("DPad left");
} // switch
}
} // xbox
// Offer a fallback ... just in case
Log::warn("GamepadConfig", "Missing action string for pad '%s' action '%d'",
getName().c_str(), action);
}
#endif
return DeviceConfig::getBindingAsString(action);
} // getBindingAsString
@@ -275,25 +240,200 @@ irr::core::stringw GamepadConfig::toString()
return returnString;
} // toString
//------------------------------------------------------------------------------
bool DeviceConfig::hasBindingFor(const int button_id) const
// ----------------------------------------------------------------------------
bool GamepadConfig::getMappingTuple(const std::string& rhs,
std::tuple<int, Input::AxisDirection>& t)
{
for (int n=0; n<PA_COUNT; n++)
#ifndef SERVER_ONLY
if (rhs[0] == 'b')
{
if (m_bindings[n].getId() == button_id) return true;
int button = -1;
if (StringUtils::fromString(&rhs[1], button) && button >= 0)
{
std::get<0>(t) = button;
std::get<1>(t) = Input::AD_NEUTRAL;
return true;
}
}
if (rhs[0] == 'h')
{
int hat = -1;
if (StringUtils::fromString(&rhs[1], hat) && hat >= 0)
{
int direction = -1;
if (rhs.size() > 3 &&
StringUtils::fromString(&rhs[3], direction) && direction >= 0)
{
if (m_hat_count == 0)
return false;
int hat_start = m_button_count - (m_hat_count * 4);
switch (direction)
{
case 1:
{
std::get<0>(t) = hat_start + (hat * 4);
std::get<1>(t) = Input::AD_NEUTRAL;
return true;
}
case 2:
{
std::get<0>(t) = hat_start + (hat * 4) + 1;
std::get<1>(t) = Input::AD_NEUTRAL;
return true;
}
case 4:
{
std::get<0>(t) = hat_start + (hat * 4) + 2;
std::get<1>(t) = Input::AD_NEUTRAL;
return true;
}
case 8:
{
std::get<0>(t) = hat_start + (hat * 4) + 3;
std::get<1>(t) = Input::AD_NEUTRAL;
return true;
}
default:
break;
}
}
}
}
if ((rhs[0] == '+' || rhs[0] == '-') && rhs[1] == 'a' && rhs.size() > 2)
{
int axis = -1;
if (StringUtils::fromString(&rhs[2], axis) && axis >= 0)
{
std::get<0>(t) = axis;
bool positive = rhs[0] == '+';
// Inverted axis
if (rhs.back() == '~')
positive = !positive;
std::get<1>(t) = positive ?
Input::AD_POSITIVE : Input::AD_NEGATIVE;
return true;
}
}
#endif
return false;
} // hasBindingFor
} // getMappingTuple
//------------------------------------------------------------------------------
bool DeviceConfig::hasBindingFor(const int button_id, PlayerAction from,
PlayerAction to) const
// ----------------------------------------------------------------------------
void GamepadConfig::initSDLController(const std::string& mapping, int buttons,
int axes, int hats)
{
for (int n=from; n<=to; n++)
if (!m_sdl_mapping.empty() ||
m_axis_count > 0 || m_button_count > 0 || m_hat_count > 0)
return;
m_button_count = buttons;
m_axis_count = axes;
m_hat_count = hats;
#ifndef SERVER_ONLY
if (mapping.empty())
return;
// We need to maunally parse the mapping as API from SDL2 is not enough
std::map<std::string, int> lhs_mapping =
{
if (m_bindings[n].getId() == button_id) return true;
{ "a", SDL_CONTROLLER_BUTTON_A },
{ "b", SDL_CONTROLLER_BUTTON_B },
{ "x", SDL_CONTROLLER_BUTTON_X },
{ "y", SDL_CONTROLLER_BUTTON_Y },
{ "back", SDL_CONTROLLER_BUTTON_BACK },
{ "guide", SDL_CONTROLLER_BUTTON_GUIDE },
{ "start", SDL_CONTROLLER_BUTTON_START },
{ "leftstick", SDL_CONTROLLER_BUTTON_LEFTSTICK },
{ "rightstick", SDL_CONTROLLER_BUTTON_RIGHTSTICK },
{ "leftshoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER },
{ "rightshoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER },
{ "dpup", SDL_CONTROLLER_BUTTON_DPAD_UP },
{ "dpdown", SDL_CONTROLLER_BUTTON_DPAD_DOWN },
{ "dpleft", SDL_CONTROLLER_BUTTON_DPAD_LEFT },
{ "dpright", SDL_CONTROLLER_BUTTON_DPAD_RIGHT },
{ "+leftx", SDL_CONTROLLER_AXIS_LEFTX_RIGHT },
{ "-leftx", SDL_CONTROLLER_AXIS_LEFTX_LEFT },
{ "+lefty", SDL_CONTROLLER_AXIS_LEFTY_DOWN },
{ "-lefty", SDL_CONTROLLER_AXIS_LEFTY_UP },
{ "+rightx", SDL_CONTROLLER_AXIS_RIGHTX_RIGHT },
{ "-rightx", SDL_CONTROLLER_AXIS_RIGHTX_LEFT },
{ "+righty", SDL_CONTROLLER_AXIS_RIGHTY_DOWN },
{ "-righty", SDL_CONTROLLER_AXIS_RIGHTY_UP },
{ "lefttrigger", SDL_CONTROLLER_AXIS_TRIGGERLEFT_UP },
{ "righttrigger", SDL_CONTROLLER_AXIS_TRIGGERRIGHT_UP }
};
std::vector<std::string> m1 = StringUtils::split(mapping, ',');
for (unsigned i = 2; i < m1.size(); i++)
{
std::vector<std::string> m2 = StringUtils::split(m1[i], ':');
if (m2.size() != 2)
continue;
std::string& lhs = m2[0];
std::string& rhs = m2[1];
if (lhs.empty() || rhs.size() < 2)
continue;
std::tuple<int, Input::AxisDirection> t;
auto ret = lhs_mapping.find(lhs);
if (ret != lhs_mapping.end())
{
if ((lhs.compare("lefttrigger") == 0 ||
lhs.compare("righttrigger") == 0) && rhs[0] == 'a')
{
// If trigger direction is not specified use positive
rhs.insert(0, "+");
}
bool found = getMappingTuple(rhs, t);
if (found)
m_sdl_mapping[t] = ret->second;
continue;
}
// Combined axes handling, extract them manually
std::array<const char*, 4> axes =
{{
"leftx", "lefty", "rightx", "righty"
}};
for (const char* a : axes)
{
if (lhs.compare(a) == 0)
{
if ((rhs[0] == '+' || rhs[0] == '-') && rhs[1] == 'a')
{
// Single half axis
lhs.insert(0, rhs[0] == '+' ? "+" : "-");
auto ret = lhs_mapping.find(lhs);
if (ret != lhs_mapping.end())
{
bool found = getMappingTuple(rhs, t);
if (found)
m_sdl_mapping[t] = ret->second;
}
}
else if (rhs[0] == 'a')
{
auto ret = lhs_mapping.find(std::string("+") + a);
if (ret != lhs_mapping.end())
{
bool found = getMappingTuple(std::string("+") + rhs, t);
if (found)
m_sdl_mapping[t] = ret->second;
}
ret = lhs_mapping.find(std::string("-") + a);
if (ret != lhs_mapping.end())
{
bool found = getMappingTuple(std::string("-") + rhs, t);
if (found)
m_sdl_mapping[t] = ret->second;
}
}
}
}
}
return false;
} // hasBindingFor
#endif
} // initSDLController
// ----------------------------------------------------------------------------
void GamepadConfig::initSDLMapping()
{
} // initSDLMapping

View File

@@ -27,9 +27,12 @@
#include <iosfwd>
#include <irrString.h>
#include <map>
#include <string>
#include <tuple>
using namespace irr;
class SDLController;
/** \brief specialisation of DeviceConfig for gamepad type devices
* \ingroup config
@@ -41,6 +44,9 @@ private:
/** Number of axis this device has. */
int m_axis_count;
/** Number of hat this device has. */
int m_hat_count;
/** Number of buttons this device has. */
int m_button_count;
@@ -55,11 +61,10 @@ private:
* values close to 0 the joystick will react less sensitive. */
bool m_desensitize;
/** A type to keep track if the gamepad has been identified (which is
* used to display better button names and better defaults). */
enum {GP_UNIDENTIFIED, GP_XBOX360, GP_XBOX_ORIGINAL} m_type;
std::map<std::tuple<int, Input::AxisDirection>, int> m_sdl_mapping;
void detectType();
bool getMappingTuple(const std::string& rhs,
std::tuple<int, Input::AxisDirection>& t);
public:
GamepadConfig ();
@@ -108,7 +113,11 @@ public:
virtual bool isGamePadAndroid() const OVERRIDE { return false; }
// ------------------------------------------------------------------------
virtual bool isKeyboard() const OVERRIDE { return false; }
// ------------------------------------------------------------------------
void initSDLController(const std::string& mapping, int buttons, int axes,
int hats);
// ------------------------------------------------------------------------
void initSDLMapping();
}; // class GamepadConfig
#endif

View File

@@ -145,8 +145,17 @@ SDLController::SDLController(int device_id)
created = true;
}
cfg->setNumberOfButtons(m_buttons);
cfg->setNumberOfAxis(m_axes);
std::string mapping_string;
if (m_game_controller)
{
char* mapping = SDL_GameControllerMapping(m_game_controller);
if (mapping)
{
mapping_string = mapping;
SDL_free(mapping);
}
}
cfg->initSDLController(mapping_string, m_buttons, m_axes, m_hats);
cfg->setPlugged();
for (int i = 0; i < dm->getGamePadAmount(); i++)
{

View File

@@ -130,6 +130,8 @@ public:
m_irr_event.JoystickEvent.AxisChanged = 0;
return true;
} // handleButton
// ------------------------------------------------------------------------
SDL_GameController* getGameController() const { return m_game_controller; }
};
#endif