2020-02-15 15:14:53 +00:00
|
|
|
#include "sdl_controller_interface.h"
|
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/log.h"
|
|
|
|
#include "core/controller.h"
|
|
|
|
#include "core/host_interface.h"
|
|
|
|
#include "core/system.h"
|
2020-02-15 15:15:05 +00:00
|
|
|
#include "sdl_initializer.h"
|
2020-02-15 15:14:53 +00:00
|
|
|
#include <SDL.h>
|
2020-02-17 15:06:11 +00:00
|
|
|
#include <cmath>
|
2020-02-15 15:14:53 +00:00
|
|
|
Log_SetChannel(SDLControllerInterface);
|
|
|
|
|
|
|
|
SDLControllerInterface::SDLControllerInterface() = default;
|
|
|
|
|
|
|
|
SDLControllerInterface::~SDLControllerInterface()
|
|
|
|
{
|
|
|
|
Assert(m_controllers.empty());
|
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
bool SDLControllerInterface::Initialize(CommonHostInterface* host_interface)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
if (!ControllerInterface::Initialize(host_interface))
|
|
|
|
return false;
|
|
|
|
|
2020-02-15 15:15:05 +00:00
|
|
|
FrontendCommon::EnsureSDLInitialized();
|
|
|
|
|
|
|
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-02-15 15:15:05 +00:00
|
|
|
Log_ErrorPrintf("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
|
|
|
|
return false;
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// we should open the controllers as the connected events come in, so no need to do any more here
|
2020-03-21 14:49:46 +00:00
|
|
|
m_sdl_subsystem_initialized = true;
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SDLControllerInterface::Shutdown()
|
|
|
|
{
|
|
|
|
while (!m_controllers.empty())
|
2020-03-22 02:37:11 +00:00
|
|
|
CloseGameController(m_controllers.begin()->joystick_id, false);
|
2020-02-15 15:14:53 +00:00
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
if (m_sdl_subsystem_initialized)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-02-15 15:15:05 +00:00
|
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);
|
2020-03-21 14:49:46 +00:00
|
|
|
m_sdl_subsystem_initialized = false;
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
2020-03-22 02:37:11 +00:00
|
|
|
|
|
|
|
ControllerInterface::Shutdown();
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
void SDLControllerInterface::PollEvents()
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
SDL_Event ev;
|
|
|
|
if (SDL_PollEvent(&ev))
|
|
|
|
ProcessSDLEvent(&ev);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::ProcessSDLEvent(const SDL_Event* event)
|
|
|
|
{
|
|
|
|
switch (event->type)
|
|
|
|
{
|
|
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
|
|
{
|
|
|
|
Log_InfoPrintf("Controller %d inserted", event->cdevice.which);
|
|
|
|
OpenGameController(event->cdevice.which);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
|
|
{
|
|
|
|
Log_InfoPrintf("Controller %d removed", event->cdevice.which);
|
2020-03-22 02:37:11 +00:00
|
|
|
CloseGameController(event->cdevice.which, true);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SDL_CONTROLLERAXISMOTION:
|
|
|
|
return HandleControllerAxisEvent(event);
|
|
|
|
|
|
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
|
|
return HandleControllerButtonEvent(event);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
SDLControllerInterface::ControllerDataVector::iterator
|
|
|
|
SDLControllerInterface::GetControllerDataForController(void* controller)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
return std::find_if(m_controllers.begin(), m_controllers.end(),
|
|
|
|
[controller](const ControllerData& cd) { return cd.controller == controller; });
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
SDLControllerInterface::ControllerDataVector::iterator SDLControllerInterface::GetControllerDataForJoystickId(int id)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
return std::find_if(m_controllers.begin(), m_controllers.end(),
|
|
|
|
[id](const ControllerData& cd) { return cd.joystick_id == id; });
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
SDLControllerInterface::ControllerDataVector::iterator SDLControllerInterface::GetControllerDataForPlayerId(int id)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
return std::find_if(m_controllers.begin(), m_controllers.end(),
|
|
|
|
[id](const ControllerData& cd) { return cd.player_id == id; });
|
2020-02-15 15:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::OpenGameController(int index)
|
|
|
|
{
|
|
|
|
SDL_GameController* gcontroller = SDL_GameControllerOpen(index);
|
2020-03-21 14:49:46 +00:00
|
|
|
SDL_Joystick* joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr;
|
|
|
|
if (!gcontroller || !joystick)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
|
|
|
Log_WarningPrintf("Failed to open controller %d", index);
|
2020-03-21 14:49:46 +00:00
|
|
|
if (gcontroller)
|
|
|
|
SDL_GameControllerClose(gcontroller);
|
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-03-22 11:29:58 +00:00
|
|
|
int joystick_id = SDL_JoystickInstanceID(joystick);
|
2020-03-22 02:27:58 +00:00
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
2020-03-22 11:29:58 +00:00
|
|
|
int player_id = SDL_GameControllerGetPlayerIndex(gcontroller);
|
2020-03-22 02:27:58 +00:00
|
|
|
#else
|
2020-03-22 11:29:58 +00:00
|
|
|
int player_id = -1;
|
2020-03-22 02:27:58 +00:00
|
|
|
#endif
|
2020-03-22 11:29:58 +00:00
|
|
|
if (player_id < 0)
|
|
|
|
{
|
|
|
|
Log_WarningPrintf("Controller %d (joystick %d) returned player ID %d. Setting to zero, but this may cause issues "
|
|
|
|
"if you try to use multiple controllers.",
|
|
|
|
index, joystick_id, player_id);
|
|
|
|
player_id = 0;
|
|
|
|
}
|
2020-03-21 14:49:46 +00:00
|
|
|
|
2020-03-22 11:29:58 +00:00
|
|
|
Log_InfoPrintf("Opened controller %d (instance id %d, player id %d): %s", index, joystick_id, player_id,
|
2020-03-21 14:49:46 +00:00
|
|
|
SDL_GameControllerName(gcontroller));
|
2020-02-15 15:14:53 +00:00
|
|
|
|
|
|
|
ControllerData cd = {};
|
|
|
|
cd.controller = gcontroller;
|
2020-03-22 11:29:58 +00:00
|
|
|
cd.player_id = player_id;
|
2020-03-21 14:49:46 +00:00
|
|
|
cd.joystick_id = joystick_id;
|
2020-02-15 15:14:53 +00:00
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
|
|
|
|
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) == 0)
|
|
|
|
cd.haptic = haptic;
|
|
|
|
else if (haptic)
|
|
|
|
SDL_HapticClose(haptic);
|
2020-02-15 15:14:53 +00:00
|
|
|
|
|
|
|
if (cd.haptic)
|
|
|
|
Log_InfoPrintf("Rumble is supported on '%s'", SDL_GameControllerName(gcontroller));
|
|
|
|
else
|
|
|
|
Log_WarningPrintf("Rumble is not supported on '%s'", SDL_GameControllerName(gcontroller));
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
m_controllers.push_back(std::move(cd));
|
2020-03-22 11:29:58 +00:00
|
|
|
OnControllerConnected(player_id);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-03-22 02:37:11 +00:00
|
|
|
bool SDLControllerInterface::CloseGameController(int joystick_index, bool notify)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForJoystickId(joystick_index);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
2020-03-22 11:29:58 +00:00
|
|
|
const int player_id = it->player_id;
|
2020-02-15 15:14:53 +00:00
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
if (it->haptic)
|
|
|
|
SDL_HapticClose(static_cast<SDL_Haptic*>(it->haptic));
|
|
|
|
|
|
|
|
SDL_GameControllerClose(static_cast<SDL_GameController*>(it->controller));
|
2020-02-15 15:14:53 +00:00
|
|
|
m_controllers.erase(it);
|
2020-03-21 14:49:46 +00:00
|
|
|
|
2020-03-22 02:37:11 +00:00
|
|
|
if (notify)
|
2020-03-22 11:29:58 +00:00
|
|
|
OnControllerDisconnected(player_id);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
void SDLControllerInterface::ClearBindings()
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
|
|
|
for (auto& it : m_controllers)
|
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
for (AxisCallback& ac : it.axis_mapping)
|
2020-02-15 15:14:53 +00:00
|
|
|
ac = {};
|
2020-03-21 14:49:46 +00:00
|
|
|
for (ButtonCallback& bc : it.button_mapping)
|
2020-02-15 15:14:53 +00:00
|
|
|
bc = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisCallback callback)
|
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForPlayerId(controller_index);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (axis_number < 0 || axis_number >= MAX_NUM_AXISES)
|
|
|
|
return false;
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
it->axis_mapping[axis_number] = std::move(callback);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback)
|
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForPlayerId(controller_index);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (button_number < 0 || button_number >= MAX_NUM_BUTTONS)
|
|
|
|
return false;
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
it->button_mapping[button_number] = std::move(callback);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
|
|
|
|
ButtonCallback callback)
|
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForPlayerId(controller_index);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (axis_number < 0 || axis_number >= MAX_NUM_AXISES)
|
|
|
|
return false;
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
it->axis_button_mapping[axis_number][BoolToUInt8(direction)] = std::move(callback);
|
2020-02-15 15:14:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev)
|
|
|
|
{
|
|
|
|
// TODO: Make deadzone customizable.
|
|
|
|
static constexpr float deadzone = 8192.0f / 32768.0f;
|
|
|
|
|
|
|
|
const float value = static_cast<float>(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f);
|
2020-02-17 15:06:11 +00:00
|
|
|
Log_DebugPrintf("controller %d axis %d %d %f", ev->caxis.which, ev->caxis.axis, ev->caxis.value, value);
|
2020-02-15 15:14:53 +00:00
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForJoystickId(ev->caxis.which);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
2020-04-04 15:28:06 +00:00
|
|
|
if (DoEventHook(Hook::Type::Axis, it->player_id, ev->caxis.axis, value))
|
|
|
|
return true;
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
const AxisCallback& cb = it->axis_mapping[ev->caxis.axis];
|
2020-02-15 15:14:53 +00:00
|
|
|
if (cb)
|
|
|
|
{
|
|
|
|
cb(value);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the other direction to false so large movements don't leave the opposite on
|
2020-02-17 15:06:11 +00:00
|
|
|
const bool outside_deadzone = (std::abs(value) >= deadzone);
|
2020-02-15 15:14:53 +00:00
|
|
|
const bool positive = (value >= 0.0f);
|
2020-03-21 14:49:46 +00:00
|
|
|
const ButtonCallback& other_button_cb = it->axis_button_mapping[ev->caxis.axis][BoolToUInt8(!positive)];
|
|
|
|
const ButtonCallback& button_cb = it->axis_button_mapping[ev->caxis.axis][BoolToUInt8(positive)];
|
2020-02-15 15:14:53 +00:00
|
|
|
if (button_cb)
|
|
|
|
{
|
|
|
|
button_cb(outside_deadzone);
|
|
|
|
if (other_button_cb)
|
|
|
|
other_button_cb(false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (other_button_cb)
|
|
|
|
{
|
|
|
|
other_button_cb(false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev)
|
|
|
|
{
|
|
|
|
Log_DebugPrintf("controller %d button %d %s", ev->cbutton.which, ev->cbutton.button,
|
|
|
|
ev->cbutton.state == SDL_PRESSED ? "pressed" : "released");
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
auto it = GetControllerDataForJoystickId(ev->caxis.which);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (it == m_controllers.end())
|
|
|
|
return false;
|
|
|
|
|
2020-04-04 15:28:06 +00:00
|
|
|
const bool pressed = (ev->cbutton.state == SDL_PRESSED);
|
|
|
|
if (DoEventHook(Hook::Type::Button, it->player_id, ev->cbutton.button, pressed ? 1.0f : 0.0f))
|
|
|
|
return true;
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
const ButtonCallback& cb = it->button_mapping[ev->cbutton.button];
|
2020-02-15 15:14:53 +00:00
|
|
|
if (!cb)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
cb(pressed);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SDLControllerInterface::UpdateControllerRumble()
|
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
for (auto& cd : m_controllers)
|
2020-02-15 15:14:53 +00:00
|
|
|
{
|
2020-03-21 14:49:46 +00:00
|
|
|
// TODO: FIXME proper binding
|
|
|
|
if (!cd.haptic || cd.player_id < 0 || cd.player_id >= 2)
|
2020-02-15 15:14:53 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
float new_strength = 0.0f;
|
2020-03-21 14:49:46 +00:00
|
|
|
Controller* controller = GetController(cd.player_id);
|
2020-02-15 15:14:53 +00:00
|
|
|
if (controller)
|
|
|
|
{
|
|
|
|
const u32 motor_count = controller->GetVibrationMotorCount();
|
|
|
|
for (u32 i = 0; i < motor_count; i++)
|
|
|
|
new_strength = std::max(new_strength, controller->GetVibrationMotorStrength(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cd.last_rumble_strength == new_strength)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (new_strength > 0.01f)
|
|
|
|
SDL_HapticRumblePlay(static_cast<SDL_Haptic*>(cd.haptic), new_strength, 100000);
|
|
|
|
else
|
|
|
|
SDL_HapticRumbleStop(static_cast<SDL_Haptic*>(cd.haptic));
|
|
|
|
|
|
|
|
cd.last_rumble_strength = new_strength;
|
|
|
|
}
|
|
|
|
}
|