436 lines
13 KiB
C++
436 lines
13 KiB
C++
#include "input/gamepad_device.h"
|
|
#include "input/mouse.h"
|
|
#include "oslib/oslib.h"
|
|
#include "sdl.h"
|
|
|
|
template<bool Arcade = false, bool Gamepad = false>
|
|
class DefaultInputMapping : public InputMapping
|
|
{
|
|
public:
|
|
DefaultInputMapping()
|
|
{
|
|
name = "Default";
|
|
set_button(DC_BTN_Y, 0);
|
|
set_button(DC_BTN_B, 1);
|
|
set_button(DC_BTN_A, 2);
|
|
set_button(DC_BTN_X, 3);
|
|
set_button(DC_BTN_START, 9);
|
|
|
|
set_axis(0, DC_AXIS_LEFT, 0, false);
|
|
set_axis(0, DC_AXIS_RIGHT, 0, true);
|
|
set_axis(0, DC_AXIS_UP, 1, false);
|
|
set_axis(0, DC_AXIS_DOWN, 1, true);
|
|
set_axis(0, DC_AXIS2_LEFT, 2, false);
|
|
set_axis(0, DC_AXIS2_RIGHT, 2, true);
|
|
set_axis(0, DC_AXIS2_UP, 3, false);
|
|
set_axis(0, DC_AXIS2_DOWN, 3, true);
|
|
dirty = false;
|
|
}
|
|
|
|
DefaultInputMapping(SDL_GameController *sdlController) : DefaultInputMapping()
|
|
{
|
|
if (sdlController != nullptr)
|
|
{
|
|
name = SDL_GameControllerName(sdlController);
|
|
INFO_LOG(INPUT, "SDL: using SDL game controller mappings for '%s'", name.c_str());
|
|
|
|
auto map_button = [&](SDL_GameControllerButton sdl_btn, DreamcastKey dc_btn) {
|
|
SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForButton(sdlController, sdl_btn);
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON)
|
|
set_button(dc_btn, bind.value.button);
|
|
else if (bind.bindType == SDL_CONTROLLER_BINDTYPE_HAT)
|
|
{
|
|
int dir;
|
|
switch (bind.value.hat.hat_mask)
|
|
{
|
|
case SDL_HAT_UP:
|
|
dir = 0;
|
|
break;
|
|
case SDL_HAT_DOWN:
|
|
dir = 1;
|
|
break;
|
|
case SDL_HAT_LEFT:
|
|
dir = 2;
|
|
break;
|
|
case SDL_HAT_RIGHT:
|
|
dir = 3;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
set_button(dc_btn, ((bind.value.hat.hat + 1) << 8) | dir);
|
|
}
|
|
};
|
|
auto map_axis = [&](SDL_GameControllerAxis sdl_axis, DreamcastKey dc_axis, bool positive) {
|
|
SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForAxis(sdlController, sdl_axis);
|
|
if (bind.bindType != SDL_CONTROLLER_BINDTYPE_AXIS)
|
|
return false;
|
|
|
|
bool invert_axis = false;
|
|
char *gcdb = SDL_GameControllerMapping(sdlController);
|
|
const char *axisName = SDL_GameControllerGetStringForAxis(sdl_axis);
|
|
if (gcdb != nullptr && axisName != nullptr)
|
|
{
|
|
const char *p = strstr(gcdb, axisName);
|
|
if (p != nullptr)
|
|
{
|
|
const char *pend = strchr(p, ',');
|
|
if (pend == nullptr)
|
|
pend = p + strlen(p);
|
|
invert_axis = pend[-1] == '~';
|
|
}
|
|
}
|
|
set_axis(dc_axis, bind.value.axis, invert_axis ^ positive);
|
|
SDL_free(gcdb);
|
|
|
|
return true;
|
|
};
|
|
|
|
if (Arcade)
|
|
{
|
|
if (Gamepad)
|
|
{
|
|
// 1 2 3 4 5 6
|
|
// A B X Y R L
|
|
map_button(SDL_CONTROLLER_BUTTON_A, DC_BTN_A);
|
|
map_button(SDL_CONTROLLER_BUTTON_B, DC_BTN_B);
|
|
map_button(SDL_CONTROLLER_BUTTON_X, DC_BTN_C);
|
|
map_button(SDL_CONTROLLER_BUTTON_Y, DC_BTN_X);
|
|
|
|
if (!map_axis(SDL_CONTROLLER_AXIS_TRIGGERLEFT, DC_AXIS_LT, true))
|
|
map_button(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DC_AXIS_LT);
|
|
else
|
|
map_button(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DC_BTN_Z);
|
|
if (!map_axis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, DC_AXIS_RT, true))
|
|
map_button(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DC_AXIS_RT);
|
|
else
|
|
map_button(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DC_BTN_Y);
|
|
}
|
|
else
|
|
{
|
|
// Hitbox
|
|
// 1 2 3 4 5 6 7 8
|
|
// X Y R1 A B R2 L1 L2
|
|
map_button(SDL_CONTROLLER_BUTTON_X, DC_BTN_A);
|
|
map_button(SDL_CONTROLLER_BUTTON_Y, DC_BTN_B);
|
|
map_button(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DC_BTN_C); // R1
|
|
map_button(SDL_CONTROLLER_BUTTON_A, DC_BTN_X);
|
|
map_button(SDL_CONTROLLER_BUTTON_B, DC_BTN_Y);
|
|
map_axis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, DC_BTN_Z, true); // R2
|
|
map_button(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DC_DPAD2_LEFT); // L1 (Naomi button 7)
|
|
map_axis(SDL_CONTROLLER_AXIS_TRIGGERLEFT, DC_DPAD2_RIGHT, true); // L2 (Naomi button 8)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
map_button(SDL_CONTROLLER_BUTTON_A, DC_BTN_A);
|
|
map_button(SDL_CONTROLLER_BUTTON_B, DC_BTN_B);
|
|
map_button(SDL_CONTROLLER_BUTTON_X, DC_BTN_X);
|
|
map_button(SDL_CONTROLLER_BUTTON_Y, DC_BTN_Y);
|
|
if (!map_axis(SDL_CONTROLLER_AXIS_TRIGGERLEFT, DC_AXIS_LT, true))
|
|
map_button(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DC_AXIS_LT);
|
|
else
|
|
map_button(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DC_BTN_Z);
|
|
if (!map_axis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, DC_AXIS_RT, true))
|
|
map_button(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DC_AXIS_RT);
|
|
else
|
|
map_button(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DC_BTN_C);
|
|
}
|
|
map_button(SDL_CONTROLLER_BUTTON_START, DC_BTN_START);
|
|
map_button(SDL_CONTROLLER_BUTTON_DPAD_UP, DC_DPAD_UP);
|
|
map_button(SDL_CONTROLLER_BUTTON_DPAD_DOWN, DC_DPAD_DOWN);
|
|
map_button(SDL_CONTROLLER_BUTTON_DPAD_LEFT, DC_DPAD_LEFT);
|
|
map_button(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, DC_DPAD_RIGHT);
|
|
map_button(SDL_CONTROLLER_BUTTON_GUIDE, DC_DPAD2_UP); // service
|
|
map_button(SDL_CONTROLLER_BUTTON_BACK, EMU_BTN_MENU);
|
|
|
|
map_axis(SDL_CONTROLLER_AXIS_LEFTX, DC_AXIS_LEFT, false);
|
|
map_axis(SDL_CONTROLLER_AXIS_LEFTX, DC_AXIS_RIGHT, true);
|
|
map_axis(SDL_CONTROLLER_AXIS_LEFTY, DC_AXIS_UP, false);
|
|
map_axis(SDL_CONTROLLER_AXIS_LEFTY, DC_AXIS_DOWN, true);
|
|
map_axis(SDL_CONTROLLER_AXIS_RIGHTX, DC_AXIS2_LEFT, false);
|
|
map_axis(SDL_CONTROLLER_AXIS_RIGHTX, DC_AXIS2_RIGHT, true);
|
|
map_axis(SDL_CONTROLLER_AXIS_RIGHTY, DC_AXIS2_UP, false);
|
|
map_axis(SDL_CONTROLLER_AXIS_RIGHTY, DC_AXIS2_DOWN, true);
|
|
|
|
dirty = false;
|
|
}
|
|
else
|
|
INFO_LOG(INPUT, "using default mapping");
|
|
}
|
|
};
|
|
|
|
class SDLGamepad : public GamepadDevice
|
|
{
|
|
public:
|
|
SDLGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
|
|
: GamepadDevice(maple_port, "SDL"), sdl_joystick(sdl_joystick)
|
|
{
|
|
const char *joyName = SDL_JoystickName(sdl_joystick);
|
|
if (joyName == nullptr)
|
|
{
|
|
WARN_LOG(INPUT, "Can't get joystick %d name: %s", joystick_idx, SDL_GetError());
|
|
throw FlycastException("joystick failure");
|
|
}
|
|
_name = joyName;
|
|
sdl_joystick_instance = SDL_JoystickInstanceID(sdl_joystick);
|
|
_unique_id = "sdl_joystick_" + std::to_string(sdl_joystick_instance);
|
|
INFO_LOG(INPUT, "SDL: Opened joystick %d on port %d: '%s' unique_id=%s", sdl_joystick_instance, maple_port, _name.c_str(), _unique_id.c_str());
|
|
|
|
if (SDL_IsGameController(joystick_idx))
|
|
{
|
|
sdl_controller = SDL_GameControllerOpen(joystick_idx);
|
|
if (sdl_controller == nullptr)
|
|
{
|
|
WARN_LOG(INPUT, "Can't open game controller %d: %s", joystick_idx, SDL_GetError());
|
|
}
|
|
else
|
|
{
|
|
SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForAxis(sdl_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS)
|
|
leftTrigger = bind.value.axis;
|
|
bind = SDL_GameControllerGetBindForAxis(sdl_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS)
|
|
rightTrigger = bind.value.axis;
|
|
}
|
|
}
|
|
|
|
if (!find_mapping())
|
|
input_mapper = std::make_shared<DefaultInputMapping<>>(sdl_controller);
|
|
else
|
|
INFO_LOG(INPUT, "using custom mapping '%s'", input_mapper->name.c_str());
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
|
rumbleEnabled = SDL_JoystickHasRumble(sdl_joystick);
|
|
#else
|
|
rumbleEnabled = (SDL_JoystickRumble(sdl_joystick, 1, 1, 1) != -1);
|
|
#endif
|
|
}
|
|
|
|
bool gamepad_axis_input(u32 code, int value) override
|
|
{
|
|
if (code == leftTrigger || code == rightTrigger)
|
|
value = (u16)(value + 32768) / 2;
|
|
return GamepadDevice::gamepad_axis_input(code, value);
|
|
}
|
|
|
|
u16 getRumbleIntensity(float power) {
|
|
return (u16)std::min(power * 65535.f / std::pow(1.06f, 100.f - rumblePower), 65535.f);
|
|
}
|
|
|
|
void rumble(float power, float inclination, u32 duration_ms) override
|
|
{
|
|
if (rumbleEnabled)
|
|
{
|
|
vib_inclination = inclination * power;
|
|
vib_stop_time = os_GetSeconds() + duration_ms / 1000.0;
|
|
|
|
u16 intensity = getRumbleIntensity(power);
|
|
SDL_JoystickRumble(sdl_joystick, intensity, intensity, duration_ms);
|
|
}
|
|
}
|
|
void update_rumble() override
|
|
{
|
|
if (!rumbleEnabled)
|
|
return;
|
|
if (vib_inclination > 0)
|
|
{
|
|
int rem_time = (vib_stop_time - os_GetSeconds()) * 1000;
|
|
if (rem_time <= 0)
|
|
vib_inclination = 0;
|
|
else
|
|
{
|
|
u16 intensity = getRumbleIntensity(vib_inclination * rem_time);
|
|
SDL_JoystickRumble(sdl_joystick, intensity, intensity, rem_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
void close()
|
|
{
|
|
INFO_LOG(INPUT, "SDL: Joystick '%s' on port %d disconnected", _name.c_str(), maple_port());
|
|
if (sdl_controller != nullptr)
|
|
SDL_GameControllerClose(sdl_controller);
|
|
SDL_JoystickClose(sdl_joystick);
|
|
GamepadDevice::Unregister(sdl_gamepads[sdl_joystick_instance]);
|
|
sdl_gamepads.erase(sdl_joystick_instance);
|
|
}
|
|
|
|
const char *get_button_name(u32 code) override
|
|
{
|
|
static struct
|
|
{
|
|
const char *sdlButton;
|
|
const char *label;
|
|
} buttonsTable[] =
|
|
{
|
|
{ "a", "A" },
|
|
{ "b", "B" },
|
|
{ "x", "X" },
|
|
{ "y", "Y" },
|
|
{ "back", "Back" },
|
|
{ "guide", "Guide" },
|
|
{ "start", "Start" },
|
|
{ "leftstick", "L3" },
|
|
{ "rightstick", "R3" },
|
|
{ "leftshoulder", "L1" },
|
|
{ "rightshoulder", "R1" },
|
|
{ "dpup", "DPad Up" },
|
|
{ "dpdown", "DPad Down" },
|
|
{ "dpleft", "DPad Left" },
|
|
{ "dpright", "DPad Right" },
|
|
{ "misc1", "Misc" },
|
|
{ "paddle1", "Paddle 1" },
|
|
{ "paddle2", "Paddle 2" },
|
|
{ "paddle3", "Paddle 3" },
|
|
{ "paddle4", "Paddle 4" },
|
|
{ "touchpad", "Touchpad" },
|
|
};
|
|
if (sdl_controller != nullptr)
|
|
for (SDL_GameControllerButton button = SDL_CONTROLLER_BUTTON_A; button < SDL_CONTROLLER_BUTTON_MAX; button = (SDL_GameControllerButton)(button + 1))
|
|
{
|
|
SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForButton(sdl_controller, button);
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button == (int)code)
|
|
{
|
|
const char *sdlButton = SDL_GameControllerGetStringForButton(button);
|
|
if (sdlButton == nullptr)
|
|
return nullptr;
|
|
for (const auto& button : buttonsTable)
|
|
if (!strcmp(button.sdlButton, sdlButton))
|
|
return button.label;
|
|
return sdlButton;
|
|
}
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_HAT && (code >> 8) - 1 == (u32)bind.value.hat.hat)
|
|
{
|
|
int hat;
|
|
const char *name;
|
|
switch (code & 0xff)
|
|
{
|
|
case 0:
|
|
hat = SDL_HAT_UP;
|
|
name = "DPad Up";
|
|
break;
|
|
case 1:
|
|
hat = SDL_HAT_DOWN;
|
|
name = "DPad Down";
|
|
break;
|
|
case 2:
|
|
hat = SDL_HAT_LEFT;
|
|
name = "DPad Left";
|
|
break;
|
|
case 3:
|
|
hat = SDL_HAT_RIGHT;
|
|
name = "DPad Right";
|
|
break;
|
|
default:
|
|
hat = 0;
|
|
name = nullptr;
|
|
break;
|
|
}
|
|
if (hat == bind.value.hat.hat_mask)
|
|
return name;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const char *get_axis_name(u32 code) override
|
|
{
|
|
static struct
|
|
{
|
|
const char *sdlAxis;
|
|
const char *label;
|
|
} axesTable[] =
|
|
{
|
|
{ "leftx", "Left Stick X" },
|
|
{ "lefty", "Left Stick Y" },
|
|
{ "rightx", "Right Stick X" },
|
|
{ "righty", "Right Stick Y" },
|
|
{ "lefttrigger", "L2" },
|
|
{ "righttrigger", "R2" },
|
|
};
|
|
|
|
if (sdl_controller != nullptr)
|
|
for (SDL_GameControllerAxis axis = SDL_CONTROLLER_AXIS_LEFTX; axis < SDL_CONTROLLER_AXIS_MAX; axis = (SDL_GameControllerAxis)(axis + 1))
|
|
{
|
|
SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForAxis(sdl_controller, axis);
|
|
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis == (int)code)
|
|
{
|
|
const char *sdlAxis = SDL_GameControllerGetStringForAxis(axis);
|
|
if (sdlAxis == nullptr)
|
|
return nullptr;
|
|
for (const auto& axis : axesTable)
|
|
if (!strcmp(axis.sdlAxis, sdlAxis))
|
|
return axis.label;
|
|
return sdlAxis;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void resetMappingToDefault(bool arcade, bool gamepad) override
|
|
{
|
|
NOTICE_LOG(INPUT, "Resetting SDL gamepad to default: %d %d", arcade, gamepad);
|
|
if (arcade)
|
|
{
|
|
if (gamepad)
|
|
input_mapper = std::make_shared<DefaultInputMapping<true, true>>(sdl_controller);
|
|
else
|
|
input_mapper = std::make_shared<DefaultInputMapping<true, false>>(sdl_controller);
|
|
}
|
|
else
|
|
input_mapper = std::make_shared<DefaultInputMapping<false, false>>(sdl_controller);
|
|
}
|
|
|
|
|
|
static void AddSDLGamepad(std::shared_ptr<SDLGamepad> gamepad)
|
|
{
|
|
sdl_gamepads[gamepad->sdl_joystick_instance] = gamepad;
|
|
GamepadDevice::Register(gamepad);
|
|
}
|
|
static std::shared_ptr<SDLGamepad> GetSDLGamepad(SDL_JoystickID id)
|
|
{
|
|
auto it = sdl_gamepads.find(id);
|
|
if (it != sdl_gamepads.end())
|
|
return it->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
static void closeAllGamepads()
|
|
{
|
|
while (!sdl_gamepads.empty())
|
|
sdl_gamepads.begin()->second->close();
|
|
}
|
|
static void UpdateRumble()
|
|
{
|
|
for (auto& pair : sdl_gamepads)
|
|
pair.second->update_rumble();
|
|
}
|
|
|
|
private:
|
|
SDL_Joystick* sdl_joystick;
|
|
SDL_JoystickID sdl_joystick_instance;
|
|
float vib_inclination = 0;
|
|
double vib_stop_time = 0;
|
|
SDL_GameController *sdl_controller = nullptr;
|
|
u32 leftTrigger = ~0;
|
|
u32 rightTrigger = ~0;
|
|
static std::map<SDL_JoystickID, std::shared_ptr<SDLGamepad>> sdl_gamepads;
|
|
};
|
|
|
|
std::map<SDL_JoystickID, std::shared_ptr<SDLGamepad>> SDLGamepad::sdl_gamepads;
|
|
|
|
class SDLMouse : public Mouse
|
|
{
|
|
public:
|
|
SDLMouse() : Mouse("SDL")
|
|
{
|
|
this->_name = "Default Mouse";
|
|
this->_unique_id = "sdl_mouse";
|
|
loadMapping();
|
|
}
|
|
|
|
void setAbsPos(int x, int y);
|
|
};
|
|
|