Allow input configuration with SDL joysticks (#3116)
* Add infrastructure to poll joystick input and get ParamPackages * Generalize the callbacks in configure_input.cpp and add buttons for analog sticks * Use the polling classes in the input dialog * Fix includes * Formatting fix * Include real header instead of forward declaring, to fix compiler error * Split up pair and add deadzone for joystick configuration * Pass ParamPackages by reference to callback * fix formatting * getPollers -> GetPollers * Add forward declarations and simplify code a bit * Update joysticks before opening them * Fix mixup between joystick IDs and device indices
This commit is contained in:
parent
e165b5bb94
commit
e784434a25
7 changed files with 385 additions and 52 deletions
|
@ -71,4 +71,15 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
|
|||
return circle_pad_param.Serialize();
|
||||
}
|
||||
|
||||
namespace Polling {
|
||||
|
||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
|
||||
#ifdef HAVE_SDL2
|
||||
return SDL::Polling::GetPollers(type);
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Polling
|
||||
} // namespace InputCommon
|
||||
|
|
|
@ -4,7 +4,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
class ParamPackage;
|
||||
}
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
|
@ -31,4 +37,30 @@ std::string GenerateKeyboardParam(int key_code);
|
|||
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
||||
int key_modifier, float modifier_scale);
|
||||
|
||||
namespace Polling {
|
||||
|
||||
enum class DeviceType { Button, Analog };
|
||||
|
||||
/**
|
||||
* A class that can be used to get inputs from an input device like controllers without having to
|
||||
* poll the device's status yourself
|
||||
*/
|
||||
class DevicePoller {
|
||||
public:
|
||||
virtual ~DevicePoller() = default;
|
||||
/// Setup and start polling for inputs, should be called before GetNextInput
|
||||
virtual void Start() = 0;
|
||||
/// Stop polling
|
||||
virtual void Stop() = 0;
|
||||
/**
|
||||
* Every call to this function returns the next input recorded since calling Start
|
||||
* @return A ParamPackage of the recorded input, which can be used to create an InputDevice.
|
||||
* If there has been no input, the package is empty
|
||||
*/
|
||||
virtual Common::ParamPackage GetNextInput() = 0;
|
||||
};
|
||||
|
||||
// Get all DevicePoller from all backends for a specific device type
|
||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
|
||||
} // namespace Polling
|
||||
} // namespace InputCommon
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <SDL.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
@ -69,6 +71,10 @@ public:
|
|||
return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
|
||||
}
|
||||
|
||||
SDL_JoystickID GetJoystickID() const {
|
||||
return SDL_JoystickInstanceID(joystick.get());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
|
||||
};
|
||||
|
@ -247,5 +253,180 @@ void Shutdown() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function converts a joystick ID used in SDL events to the device index. This is necessary
|
||||
* because Citra opens joysticks using their indices, not their IDs.
|
||||
*/
|
||||
static int JoystickIDToDeviceIndex(SDL_JoystickID id) {
|
||||
int num_joysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < num_joysticks; i++) {
|
||||
auto joystick = GetJoystick(i);
|
||||
if (joystick->GetJoystickID() == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
|
||||
Common::ParamPackage params({{"engine", "sdl"}});
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION:
|
||||
params.Set("joystick", JoystickIDToDeviceIndex(event.jaxis.which));
|
||||
params.Set("axis", event.jaxis.axis);
|
||||
if (event.jaxis.value > 0) {
|
||||
params.Set("direction", "+");
|
||||
params.Set("threshold", "0.5");
|
||||
} else {
|
||||
params.Set("direction", "-");
|
||||
params.Set("threshold", "-0.5");
|
||||
}
|
||||
break;
|
||||
case SDL_JOYBUTTONUP:
|
||||
params.Set("joystick", JoystickIDToDeviceIndex(event.jbutton.which));
|
||||
params.Set("button", event.jbutton.button);
|
||||
break;
|
||||
case SDL_JOYHATMOTION:
|
||||
params.Set("joystick", JoystickIDToDeviceIndex(event.jhat.which));
|
||||
params.Set("hat", event.jhat.hat);
|
||||
switch (event.jhat.value) {
|
||||
case SDL_HAT_UP:
|
||||
params.Set("direction", "up");
|
||||
break;
|
||||
case SDL_HAT_DOWN:
|
||||
params.Set("direction", "down");
|
||||
break;
|
||||
case SDL_HAT_LEFT:
|
||||
params.Set("direction", "left");
|
||||
break;
|
||||
case SDL_HAT_RIGHT:
|
||||
params.Set("direction", "right");
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
namespace Polling {
|
||||
|
||||
class SDLPoller : public InputCommon::Polling::DevicePoller {
|
||||
public:
|
||||
SDLPoller() = default;
|
||||
|
||||
~SDLPoller() = default;
|
||||
|
||||
void Start() override {
|
||||
// SDL joysticks must be opened, otherwise they don't generate events
|
||||
SDL_JoystickUpdate();
|
||||
int num_joysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < num_joysticks; i++) {
|
||||
joysticks_opened.emplace_back(GetJoystick(i));
|
||||
}
|
||||
// Empty event queue to get rid of old events. citra-qt doesn't use the queue
|
||||
SDL_Event dummy;
|
||||
while (SDL_PollEvent(&dummy)) {
|
||||
}
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
joysticks_opened.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<SDLJoystick>> joysticks_opened;
|
||||
};
|
||||
|
||||
class SDLButtonPoller final : public SDLPoller {
|
||||
public:
|
||||
SDLButtonPoller() = default;
|
||||
|
||||
~SDLButtonPoller() = default;
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION:
|
||||
if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP:
|
||||
case SDL_JOYHATMOTION:
|
||||
return SDLEventToButtonParamPackage(event);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class SDLAnalogPoller final : public SDLPoller {
|
||||
public:
|
||||
SDLAnalogPoller() = default;
|
||||
|
||||
~SDLAnalogPoller() = default;
|
||||
|
||||
void Start() override {
|
||||
SDLPoller::Start();
|
||||
|
||||
// Reset stored axes
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
}
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
continue;
|
||||
}
|
||||
// An analog device needs two axes, so we need to store the axis for later and wait for
|
||||
// a second SDL event. The axes also must be from the same joystick.
|
||||
int axis = event.jaxis.axis;
|
||||
if (analog_xaxis == -1) {
|
||||
analog_xaxis = axis;
|
||||
analog_axes_joystick = event.jaxis.which;
|
||||
} else if (analog_yaxis == -1 && analog_xaxis != axis &&
|
||||
analog_axes_joystick == event.jaxis.which) {
|
||||
analog_yaxis = axis;
|
||||
}
|
||||
}
|
||||
Common::ParamPackage params;
|
||||
if (analog_xaxis != -1 && analog_yaxis != -1) {
|
||||
params.Set("engine", "sdl");
|
||||
params.Set("joystick", JoystickIDToDeviceIndex(analog_axes_joystick));
|
||||
params.Set("axis_x", analog_xaxis);
|
||||
params.Set("axis_y", analog_yaxis);
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private:
|
||||
int analog_xaxis = -1;
|
||||
int analog_yaxis = -1;
|
||||
SDL_JoystickID analog_axes_joystick = -1;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
|
||||
switch (type) {
|
||||
case InputCommon::Polling::DeviceType::Analog:
|
||||
pollers.push_back(std::make_unique<SDLAnalogPoller>());
|
||||
break;
|
||||
case InputCommon::Polling::DeviceType::Button:
|
||||
pollers.push_back(std::make_unique<SDLButtonPoller>());
|
||||
break;
|
||||
}
|
||||
return std::move(pollers);
|
||||
}
|
||||
} // namespace Polling
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
|
|
|
@ -4,8 +4,21 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "core/frontend/input.h"
|
||||
|
||||
union SDL_Event;
|
||||
namespace Common {
|
||||
class ParamPackage;
|
||||
}
|
||||
namespace InputCommon {
|
||||
namespace Polling {
|
||||
class DevicePoller;
|
||||
enum class DeviceType;
|
||||
} // namespace Polling
|
||||
} // namespace InputCommon
|
||||
|
||||
namespace InputCommon {
|
||||
namespace SDL {
|
||||
|
||||
|
@ -15,5 +28,15 @@ void Init();
|
|||
/// Unresisters SDL device factories and shut them down.
|
||||
void Shutdown();
|
||||
|
||||
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event);
|
||||
|
||||
namespace Polling {
|
||||
|
||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type);
|
||||
|
||||
} // namespace Polling
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue