You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

471 lines
14 KiB

#include "controls/devices/joystick.h"
#include <cstddef>
#ifdef USE_SDL3
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_joystick.h>
#else
#include <SDL.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#endif
#endif
#include "controls/controller_motion.h"
#include "utils/log.hpp"
#include "utils/sdl_compat.h"
#include "utils/stubs.h"
namespace devilution {
std::vector<Joystick> Joystick::joysticks_;
StaticVector<ControllerButtonEvent, 4> Joystick::ToControllerButtonEvents(const SDL_Event &event)
{
switch (event.type) {
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_EVENT_JOYSTICK_BUTTON_UP: {
#ifdef USE_SDL3
const bool up = !event.jbutton.down;
#else
const bool up = (event.jbutton.state == SDL_RELEASED);
#endif
#if defined(JOY_BUTTON_A) || defined(JOY_BUTTON_B) || defined(JOY_BUTTON_X) || defined(JOY_BUTTON_Y) \
|| defined(JOY_BUTTON_LEFTSTICK) || defined(JOY_BUTTON_RIGHTSTICK) || defined(JOY_BUTTON_LEFTSHOULDER) || defined(JOY_BUTTON_RIGHTSHOULDER) \
|| defined(JOY_BUTTON_TRIGGERLEFT) || defined(JOY_BUTTON_TRIGGERRIGHT) || defined(JOY_BUTTON_START) || defined(JOY_BUTTON_BACK) \
|| defined(JOY_BUTTON_DPAD_LEFT) || defined(JOY_BUTTON_DPAD_UP) || defined(JOY_BUTTON_DPAD_RIGHT) || defined(JOY_BUTTON_DPAD_DOWN)
switch (event.jbutton.button) {
#ifdef JOY_BUTTON_A
case JOY_BUTTON_A:
return { ControllerButtonEvent { ControllerButton_BUTTON_A, up } };
#endif
#ifdef JOY_BUTTON_B
case JOY_BUTTON_B:
return { ControllerButtonEvent { ControllerButton_BUTTON_B, up } };
#endif
#ifdef JOY_BUTTON_X
case JOY_BUTTON_X:
return { ControllerButtonEvent { ControllerButton_BUTTON_X, up } };
#endif
#ifdef JOY_BUTTON_Y
case JOY_BUTTON_Y:
return { ControllerButtonEvent { ControllerButton_BUTTON_Y, up } };
#endif
#ifdef JOY_BUTTON_LEFTSTICK
case JOY_BUTTON_LEFTSTICK:
return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSTICK, up } };
#endif
#ifdef JOY_BUTTON_RIGHTSTICK
case JOY_BUTTON_RIGHTSTICK:
return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSTICK, up } };
#endif
#ifdef JOY_BUTTON_LEFTSHOULDER
case JOY_BUTTON_LEFTSHOULDER:
return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSHOULDER, up } };
#endif
#ifdef JOY_BUTTON_RIGHTSHOULDER
case JOY_BUTTON_RIGHTSHOULDER:
return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSHOULDER, up } };
#endif
#ifdef JOY_BUTTON_TRIGGERLEFT
case JOY_BUTTON_TRIGGERLEFT:
return { ControllerButtonEvent { ControllerButton_AXIS_TRIGGERLEFT, up } };
#endif
#ifdef JOY_BUTTON_TRIGGERRIGHT
case JOY_BUTTON_TRIGGERRIGHT:
return { ControllerButtonEvent { ControllerButton_AXIS_TRIGGERRIGHT, up } };
#endif
#ifdef JOY_BUTTON_START
case JOY_BUTTON_START:
return { ControllerButtonEvent { ControllerButton_BUTTON_START, up } };
#endif
#ifdef JOY_BUTTON_BACK
case JOY_BUTTON_BACK:
return { ControllerButtonEvent { ControllerButton_BUTTON_BACK, up } };
#endif
#ifdef JOY_BUTTON_DPAD_LEFT
case JOY_BUTTON_DPAD_LEFT:
return { ControllerButtonEvent { ControllerButton_BUTTON_DPAD_LEFT, up } };
#endif
#ifdef JOY_BUTTON_DPAD_UP
case JOY_BUTTON_DPAD_UP:
return { ControllerButtonEvent { ControllerButton_BUTTON_DPAD_UP, up } };
#endif
#ifdef JOY_BUTTON_DPAD_RIGHT
case JOY_BUTTON_DPAD_RIGHT:
return { ControllerButtonEvent { ControllerButton_BUTTON_DPAD_RIGHT, up } };
#endif
#ifdef JOY_BUTTON_DPAD_DOWN
case JOY_BUTTON_DPAD_DOWN:
return { ControllerButtonEvent { ControllerButton_BUTTON_DPAD_DOWN, up } };
#endif
default:
return { ControllerButtonEvent { ControllerButton_IGNORE, up } };
}
#else
return { ControllerButtonEvent { ControllerButton_IGNORE, up } };
#endif
}
#ifdef USE_SDL3
case SDL_EVENT_JOYSTICK_HAT_MOTION:
#else
case SDL_JOYHATMOTION:
#endif
{
Joystick *joystick = Get(event);
if (joystick == nullptr)
return { ControllerButtonEvent { ControllerButton_IGNORE, false } };
joystick->UpdateHatState(event.jhat);
return joystick->GetHatEvents();
}
#ifdef USE_SDL3
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
case SDL_EVENT_JOYSTICK_BALL_MOTION:
#else
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
#endif
// ProcessAxisMotion() requires a ControllerButtonEvent parameter
// so provide one here using ControllerButton_NONE
return { ControllerButtonEvent { ControllerButton_NONE, false } };
default:
return {};
}
}
StaticVector<ControllerButtonEvent, 4> Joystick::GetHatEvents()
{
StaticVector<ControllerButtonEvent, 4> hatEvents;
if (hatState_[0].didStateChange)
hatEvents.emplace_back(ControllerButton_BUTTON_DPAD_UP, !hatState_[0].pressed);
if (hatState_[1].didStateChange)
hatEvents.emplace_back(ControllerButton_BUTTON_DPAD_DOWN, !hatState_[1].pressed);
if (hatState_[2].didStateChange)
hatEvents.emplace_back(ControllerButton_BUTTON_DPAD_LEFT, !hatState_[2].pressed);
if (hatState_[3].didStateChange)
hatEvents.emplace_back(ControllerButton_BUTTON_DPAD_RIGHT, !hatState_[3].pressed);
if (hatEvents.size() == 0)
hatEvents.emplace_back(ControllerButton_IGNORE, false);
return hatEvents;
}
void Joystick::UpdateHatState(const SDL_JoyHatEvent &event)
{
if (lockHatState_)
return;
#if defined(JOY_HAT_DPAD_UP_HAT) && defined(JOY_HAT_DPAD_UP)
if (event.hat == JOY_HAT_DPAD_UP_HAT) {
HatState &hatState = hatState_[0];
bool pressed = (event.value & JOY_HAT_DPAD_UP) != 0;
hatState.didStateChange = (pressed != hatState.pressed);
hatState.pressed = pressed;
}
#endif
#if defined(JOY_HAT_DPAD_DOWN_HAT) && defined(JOY_HAT_DPAD_DOWN)
if (event.hat == JOY_HAT_DPAD_DOWN_HAT) {
HatState &hatState = hatState_[1];
bool pressed = (event.value & JOY_HAT_DPAD_DOWN) != 0;
hatState.didStateChange = (pressed != hatState.pressed);
hatState.pressed = pressed;
}
#endif
#if defined(JOY_HAT_DPAD_LEFT_HAT) && defined(JOY_HAT_DPAD_LEFT)
if (event.hat == JOY_HAT_DPAD_LEFT_HAT) {
HatState &hatState = hatState_[2];
bool pressed = (event.value & JOY_HAT_DPAD_LEFT) != 0;
hatState.didStateChange = (pressed != hatState.pressed);
hatState.pressed = pressed;
}
#endif
#if defined(JOY_HAT_DPAD_RIGHT_HAT) && defined(JOY_HAT_DPAD_RIGHT)
if (event.hat == JOY_HAT_DPAD_RIGHT_HAT) {
HatState &hatState = hatState_[3];
bool pressed = (event.value & JOY_HAT_DPAD_RIGHT) != 0;
hatState.didStateChange = (pressed != hatState.pressed);
hatState.pressed = pressed;
}
#endif
lockHatState_ = true;
}
void Joystick::UnlockHatState()
{
lockHatState_ = false;
for (HatState &hatState : hatState_)
hatState.didStateChange = false;
}
int Joystick::ToSdlJoyButton(ControllerButton button)
{
#if defined(JOY_BUTTON_A) || defined(JOY_BUTTON_B) || defined(JOY_BUTTON_X) || defined(JOY_BUTTON_Y) \
|| defined(JOY_BUTTON_BACK) || defined(JOY_BUTTON_START) || defined(JOY_BUTTON_LEFTSTICK) || defined(JOY_BUTTON_RIGHTSTICK) \
|| defined(JOY_BUTTON_LEFTSHOULDER) || defined(JOY_BUTTON_RIGHTSHOULDER) || defined(JOY_BUTTON_TRIGGERLEFT) || defined(JOY_BUTTON_TRIGGERRIGHT) \
|| defined(JOY_BUTTON_DPAD_LEFT) || defined(JOY_BUTTON_DPAD_UP) || defined(JOY_BUTTON_DPAD_RIGHT) || defined(JOY_BUTTON_DPAD_DOWN)
switch (button) {
#ifdef JOY_BUTTON_A
case ControllerButton_BUTTON_A:
return JOY_BUTTON_A;
#endif
#ifdef JOY_BUTTON_B
case ControllerButton_BUTTON_B:
return JOY_BUTTON_B;
#endif
#ifdef JOY_BUTTON_X
case ControllerButton_BUTTON_X:
return JOY_BUTTON_X;
#endif
#ifdef JOY_BUTTON_Y
case ControllerButton_BUTTON_Y:
return JOY_BUTTON_Y;
#endif
#ifdef JOY_BUTTON_BACK
case ControllerButton_BUTTON_BACK:
return JOY_BUTTON_BACK;
#endif
#ifdef JOY_BUTTON_START
case ControllerButton_BUTTON_START:
return JOY_BUTTON_START;
#endif
#ifdef JOY_BUTTON_LEFTSTICK
case ControllerButton_BUTTON_LEFTSTICK:
return JOY_BUTTON_LEFTSTICK;
#endif
#ifdef JOY_BUTTON_RIGHTSTICK
case ControllerButton_BUTTON_RIGHTSTICK:
return JOY_BUTTON_RIGHTSTICK;
#endif
#ifdef JOY_BUTTON_LEFTSHOULDER
case ControllerButton_BUTTON_LEFTSHOULDER:
return JOY_BUTTON_LEFTSHOULDER;
#endif
#ifdef JOY_BUTTON_RIGHTSHOULDER
case ControllerButton_BUTTON_RIGHTSHOULDER:
return JOY_BUTTON_RIGHTSHOULDER;
#endif
#ifdef JOY_BUTTON_TRIGGERLEFT
case ControllerButton_AXIS_TRIGGERLEFT:
return JOY_BUTTON_TRIGGERLEFT;
#endif
#ifdef JOY_BUTTON_TRIGGERRIGHT
case ControllerButton_AXIS_TRIGGERRIGHT:
return JOY_BUTTON_TRIGGERRIGHT;
#endif
#ifdef JOY_BUTTON_DPAD_UP
case ControllerButton_BUTTON_DPAD_UP:
return JOY_BUTTON_DPAD_UP;
#endif
#ifdef JOY_BUTTON_DPAD_DOWN
case ControllerButton_BUTTON_DPAD_DOWN:
return JOY_BUTTON_DPAD_DOWN;
#endif
#ifdef JOY_BUTTON_DPAD_LEFT
case ControllerButton_BUTTON_DPAD_LEFT:
return JOY_BUTTON_DPAD_LEFT;
#endif
#ifdef JOY_BUTTON_DPAD_RIGHT
case ControllerButton_BUTTON_DPAD_RIGHT:
return JOY_BUTTON_DPAD_RIGHT;
#endif
default:
return -1;
}
#else
return -1;
#endif
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): Not static if joystick mappings are defined.
bool Joystick::IsHatButtonPressed(ControllerButton button) const
{
#if (defined(JOY_HAT_DPAD_UP_HAT) && defined(JOY_HAT_DPAD_UP)) || (defined(JOY_HAT_DPAD_DOWN_HAT) && defined(JOY_HAT_DPAD_DOWN)) || (defined(JOY_HAT_DPAD_LEFT_HAT) && defined(JOY_HAT_DPAD_LEFT)) || (defined(JOY_HAT_DPAD_RIGHT_HAT) && defined(JOY_HAT_DPAD_RIGHT))
switch (button) {
#if defined(JOY_HAT_DPAD_UP_HAT) && defined(JOY_HAT_DPAD_UP)
case ControllerButton_BUTTON_DPAD_UP:
return (SDL_JoystickGetHat(sdl_joystick_, JOY_HAT_DPAD_UP_HAT) & JOY_HAT_DPAD_UP) != 0;
#endif
#if defined(JOY_HAT_DPAD_DOWN_HAT) && defined(JOY_HAT_DPAD_DOWN)
case ControllerButton_BUTTON_DPAD_DOWN:
return (SDL_JoystickGetHat(sdl_joystick_, JOY_HAT_DPAD_DOWN_HAT) & JOY_HAT_DPAD_DOWN) != 0;
#endif
#if defined(JOY_HAT_DPAD_LEFT_HAT) && defined(JOY_HAT_DPAD_LEFT)
case ControllerButton_BUTTON_DPAD_LEFT:
return (SDL_JoystickGetHat(sdl_joystick_, JOY_HAT_DPAD_LEFT_HAT) & JOY_HAT_DPAD_LEFT) != 0;
#endif
#if defined(JOY_HAT_DPAD_RIGHT_HAT) && defined(JOY_HAT_DPAD_RIGHT)
case ControllerButton_BUTTON_DPAD_RIGHT:
return (SDL_JoystickGetHat(sdl_joystick_, JOY_HAT_DPAD_RIGHT_HAT) & JOY_HAT_DPAD_RIGHT) != 0;
#endif
default:
return false;
}
#else
return false;
#endif
}
bool Joystick::IsPressed(ControllerButton button) const
{
if (sdl_joystick_ == nullptr)
return false;
if (IsHatButtonPressed(button))
return true;
const int joyButton = ToSdlJoyButton(button);
if (joyButton == -1)
return false;
#ifdef USE_SDL3
const int numButtons = SDL_GetNumJoystickButtons(sdl_joystick_);
return joyButton < numButtons && SDL_GetJoystickButton(sdl_joystick_, joyButton);
#else
const int numButtons = SDL_JoystickNumButtons(sdl_joystick_);
return joyButton < numButtons && SDL_JoystickGetButton(sdl_joystick_, joyButton) != 0;
#endif
}
bool Joystick::ProcessAxisMotion(const SDL_Event &event)
{
if (event.type != SDL_EVENT_JOYSTICK_AXIS_MOTION) return false;
#if defined(JOY_AXIS_LEFTX) || defined(JOY_AXIS_LEFTY) || defined(JOY_AXIS_RIGHTX) || defined(JOY_AXIS_RIGHTY)
switch (event.jaxis.axis) {
#ifdef JOY_AXIS_LEFTX
case JOY_AXIS_LEFTX:
leftStickXUnscaled = event.jaxis.value;
leftStickNeedsScaling = true;
return true;
#endif
#ifdef JOY_AXIS_LEFTY
case JOY_AXIS_LEFTY:
leftStickYUnscaled = -event.jaxis.value;
leftStickNeedsScaling = true;
return true;
#endif
#ifdef JOY_AXIS_RIGHTX
case JOY_AXIS_RIGHTX:
rightStickXUnscaled = event.jaxis.value;
rightStickNeedsScaling = true;
return true;
#endif
#ifdef JOY_AXIS_RIGHTY
case JOY_AXIS_RIGHTY:
rightStickYUnscaled = -event.jaxis.value;
rightStickNeedsScaling = true;
return true;
#endif
default:
return false;
}
#else
return false;
#endif
}
#ifdef USE_SDL3
void Joystick::Add(SDL_JoystickID id)
{
Joystick result;
const char *name = SDL_GetJoystickNameForID(id);
if (name == nullptr) {
LogWarn("Error getting name for joystick {}", id, SDL_GetError());
SDL_ClearError();
} else {
Log("Adding joystick {}: {}", id, name);
}
result.sdl_joystick_ = SDL_OpenJoystick(id);
if (result.sdl_joystick_ == nullptr) {
LogError("{}", SDL_GetError());
SDL_ClearError();
return;
}
result.instance_id_ = id;
joysticks_.push_back(result);
}
#else
void Joystick::Add(int deviceIndex)
{
if (SDL_NumJoysticks() <= deviceIndex)
return;
Joystick result;
Log("Adding joystick {}: {}", deviceIndex,
SDL_JoystickNameForIndex(deviceIndex));
result.sdl_joystick_ = SDL_JoystickOpen(deviceIndex);
if (result.sdl_joystick_ == nullptr) {
LogError("{}", SDL_GetError());
SDL_ClearError();
return;
}
#ifndef USE_SDL1
result.instance_id_ = SDL_JoystickInstanceID(result.sdl_joystick_);
#endif
joysticks_.push_back(result);
}
#endif
void Joystick::Remove(SDL_JoystickID instanceId)
{
#ifndef USE_SDL1
Log("Removing joystick (instance id: {})", instanceId);
for (std::size_t i = 0; i < joysticks_.size(); ++i) {
const Joystick &joystick = joysticks_[i];
if (joystick.instance_id_ != instanceId)
continue;
joysticks_.erase(joysticks_.begin() + i);
return;
}
Log("Joystick not found with instance id: {}", instanceId);
#endif
}
const std::vector<Joystick> &Joystick::All()
{
return joysticks_;
}
Joystick *Joystick::Get(SDL_JoystickID instanceId)
{
for (auto &joystick : joysticks_) {
if (joystick.instance_id_ == instanceId)
return &joystick;
}
return nullptr;
}
Joystick *Joystick::Get(const SDL_Event &event)
{
switch (event.type) {
#ifndef USE_SDL1
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
return Get(event.jaxis.which);
case SDL_EVENT_JOYSTICK_BALL_MOTION:
return Get(event.jball.which);
case SDL_EVENT_JOYSTICK_HAT_MOTION:
return Get(event.jhat.which);
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_EVENT_JOYSTICK_BUTTON_UP:
return Get(event.jbutton.which);
default:
return nullptr;
#else
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
return joysticks_.empty() ? nullptr : &joysticks_[0];
default:
return nullptr;
#endif
}
}
bool Joystick::IsPressedOnAnyJoystick(ControllerButton button)
{
for (auto &joystick : joysticks_)
if (joystick.IsPressed(button))
return true;
return false;
}
} // namespace devilution